Wai is:module

This module defines a generic web application interface. It is a common protocol between web servers and web applications. The overriding design principles here are performance and generality. To address performance, this library uses a streaming interface for request and response bodies, paired with bytestring's Builder type. The advantages of a streaming API over lazy IO have been debated elsewhere and so will not be addressed here. However, helper functions like responseLBS allow you to continue using lazy IO if you so desire. Generality is achieved by removing many variables commonly found in similar projects that are not universal to all servers. The goal is that the Request object contains only data which is meaningful in all circumstances. Please remember when using this package that, while your application may compile without a hitch against many different servers, there are other considerations to be taken when moving to a new backend. For example, if you transfer from a CGI application to a FastCGI one, you might suddenly find you have a memory leak. Conversely, a FastCGI application would be well served to preload all templates from disk when first starting; this would kill the performance of a CGI application. This package purposely provides very little functionality. You can find various middlewares, backends and utilities on Hackage. Some of the most commonly used include:
Have a look at the README for an example of how to use this library.
Test a Application Example usage:
exampleApplication :: Wai.Application
exampleApplication req sendResp = do
lb <- strictRequestBody req
sendResp $ responseLBS HTTP.ok200 (requestHeaders req) lb

spec :: Spec
spec =
waiClientSpec exampleApplication $
describe "get" $
it "can GET the root and get a 200" $ do
resp <- get "/"
liftIO $ responseStatus resp `shouldBe` ok200
Types and functions for testing wai endpoints using the tasty testing framework.
A light-weight wrapper around Network.Wai to provide easy pipes support.
This module provides remote monitoring of a running process over HTTP. It can be used to run an HTTP server that provides both a web-based user interface and a machine-readable API (e.g. JSON.) The former can be used by a human to get an overview of what the program is doing and the latter can be used by automated monitoring tools. Typical usage is to start the monitoring server at program startup
main = do
forkServer "localhost" 8000
...
and then periodically check the stats using a web browser or a command line tool (e.g. curl)
$ curl -H "Accept: application/json" http://localhost:8000/
Add information about the Request, Response, and the response time to Katip's LogContexts. Example setup:
import Control.Exception (bracket)
import Data.Proxy (Proxy (Proxy))
import Katip qualified
import Katip.Wai (ApplicationT, runApplication)
import Katip.Wai qualified
import Network.Wai.Handler.Warp qualified as Warp
import Servant qualified
import System.IO (stdout)
import UnliftIO (MonadUnliftIO (withRunInIO))


type Api = Servant.GetNoContent


server :: Servant.ServerT Api (Katip.KatipContextT Servant.Handler)
server = do
Katip.logLocM Katip.InfoS "This message should also have the request context"
pure Servant.NoContent


mkApplication :: ApplicationT (Katip.KatipContextT IO)
mkApplication = Katip.Wai.middleware Katip.InfoS $ request send -> do
logEnv <- Katip.getLogEnv
context <- Katip.getKatipContext
namespace <- Katip.getKatipNamespace

let hoistedApp =
let proxy = Proxy @Api
hoistedServer = Servant.hoistServer proxy (Katip.runKatipContextT logEnv context namespace) server
in Servant.serve proxy hoistedServer

withRunInIO $ toIO -> hoistedApp request (toIO . send)


withLogEnv :: (Katip.LogEnv -> IO a) -> IO a
withLogEnv useLogEnv = do
handleScribe <-
Katip.mkHandleScribeWithFormatter
Katip.jsonFormat
(Katip.ColorLog False)
stdout
(Katip.permitItem minBound)
Katip.V3

let makeLogEnv =
Katip.initLogEnv "example-app" "local-dev"
>>= Katip.registerScribe "stdout" handleScribe Katip.defaultScribeSettings

bracket makeLogEnv Katip.closeScribes useLogEnv


main :: IO ()
main = withLogEnv $ logEnv ->
let
app = runApplication (Katip.runKatipContextT logEnv () "main") mkApplication
in
Warp.run 5555 app
Example output:
{"app":["example-app"],"at":"2024-09-07T18:44:10.411097829Z","data":{"request":{"headers":{Host:"localhost:5555","User-Agent":"curl8.9.1"},"httpVersion":"HTTP1.1","id":"7ec0fbc4-722c-4c70-a168-c2abe5c7b4fa","isSecure":false,"method":GET,"path":"/","queryString":[],"receivedAt":"2024-09-07T18:44:10.411057334Z","remoteHost":"127.0.0.1:51230"}},"env":"local-dev","host":"x1g11","loc":null,"msg":"Request received.","ns":["example-app","main"],"pid":"106249","sev":Info,"thread":"27"}
{"app":["example-app"],"at":"2024-09-07T18:44:10.411097829Z","data":{"request":{"headers":{Host:"localhost:5555","User-Agent":"curl8.9.1"},"httpVersion":"HTTP1.1","id":"7ec0fbc4-722c-4c70-a168-c2abe5c7b4fa","isSecure":false,"method":GET,"path":"","queryString":[],"receivedAt":"2024-09-07T18:44:10.411057334Z","remoteHost":"127.0.0.1:51230"}},"env":"local-dev","host":"x1g11","loc":{"loc_col":3,"loc_fn":"srcKatipWaiExample/Short.hs","loc_ln":19,"loc_mod":Katip.Wai.Example.Short,"loc_pkg":"my-katip-wai-example-0.1.0.0-inplace"},"msg":"This message should also have the request context","ns":["example-app","main"],"pid":"106249","sev":Info,"thread":"27"}
{"app":["example-app"],"at":"2024-09-07T18:44:10.411097829Z","data":{"request":{"headers":{Host:"localhost:5555","User-Agent":"curl8.9.1"},"httpVersion":"HTTP1.1","id":"7ec0fbc4-722c-4c70-a168-c2abe5c7b4fa","isSecure":false,"method":GET,"path":"/","queryString":[],"receivedAt":"2024-09-07T18:44:10.411057334Z","remoteHost":"127.0.0.1:51230"},"response":{"headers":{},"respondedAt":"2024-09-07T18:44:10.411199014Z","responseTime":{"time":0.137369,"unit":"ms"},"status":{"code":204,"message":"No Content"}}},"env":"local-dev","host":"x1g11","loc":null,"msg":"Response sent.","ns":["example-app","main"],"pid":"106249","sev":Info,"thread":"27"}
wai-session server-side session support. Please note that this frontend has some limitations:
  • Cookies use the Max-age field instead of Expires. The Max-age field is not supported by all browsers: some browsers will treat them as non-persistent cookies.
  • Also, the Max-age is fixed and does not take a given session into consideration.
Module for using a WAI Middleware as an X-Ray client
Waiting (for replies).
This module contains helper functions for waiting. It can be very useful in tests to retry something, with a reasonable backoff policy to prevent the test from consuming lots of CPU while waiting.
Waits until the specified long-running operation is done or reaches at most a specified timeout, returning the latest state. If the operation is already done, the latest state is immediately returned. If the timeout specified is greater than the default HTTP/RPC timeout, the HTTP/RPC timeout is used. If the server does not support this method, it returns google.rpc.Code.UNIMPLEMENTED. Note that this method is on a best-effort basis. It may return the latest state before the specified timeout (including immediately), meaning even an immediate response is no guarantee that the operation is done. See: Cloud Translation API Reference for translate.projects.locations.operations.wait.
This module contains computations which wait for some networked service to become available, subject to some retry policy from Control.Retry. The waitSocketWith function is the most general function exported by this module, but several variants exist for convenience. You may wish to start out with e.g. waitTcp or waitSocket initially and move on to the more feature-rich variants if you need their functionality.
HTTP(S)-specific wait functions, for waiting on servers.