There are several options to create web applications with Haskell, for example: Yesod, Snap or Scotty. In this post I want to present a short primer on the basics with WAI.
WAI stands for “Web Application Interface”, and in short it’s an adapter between the web server and a web application. It decouples the application code from web servers, presenting a uniform and generic interface that the application can use without having to target a specific backend.
Indeed, there are several web servers in the Haskell space that supports WAI through an appropriate handler. Perhaps the major one is the fast and lightweight Warp1, and another interesting one is wai-handler-webkit.
WAI, as many other libraries, builds on the types defined by the http-types package. The core abstraction is Application
:
type Application =
Request ->
(Response -> IO ResponseReceived) ->
IO ResponseReceived
It’s quite straightforward: an application is basically just a Request -> Response
function. A Request
contains the requested path, the HTTP headers, query parameters the body, etc. The Response
contains instead an HTTP status, a list of headers and a body.
However, since with every request there is a certain amount of resource management that the web server has to perform in an exception-safe manner to avoid memory leaks, we use a continuation passing style for returning the response. The ResponseReceived
special type is used to ensure that the continuation is in fact called: when we produce a Response
, we must call the continuation (passed by the web server) to obtain the return value required.
The response body is a lazy value. Laziness, as with infinite lists, permits to create and manage large values without exhausting memory. For example you can have a value that represents the content of a very large file (maybe larger than memory), with a very low memory footprint.
However, serving many small files per second using laziness, the limiting factor is not memory anymore: it becomes file handles. Since file handles may not be freed immediately, leading to resource exhaustion, WAI provides its own streaming primitives:
responseBuilder
and its wrapper responseLBS
responseStream
responseFile
responseRaw
The request body can be obtained with (unsurprisingly) requestBody
. This accessor permits to read the request body chunk by chunk (that’s why it’s an IO
action), and the actual chunks are represented by a strict ByteString
since in this context we don’t want laziness.
An hello world example, shown by the official documentation. It’s as simple as it can get:
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.HTTP.Types (status200)
import Network.Wai.Handler.Warp (run)
application :: Application
application _ respond = respond $
responseLBS status200 [("Content-Type", "text/plain")] "Hello World"
main :: IO ()
main = run 3000 application
Here we use Warp as the server and use its WAI adapter to code our web “application” against it using the WAI primitives.
The Application
simply ignores the Request
and applies the supplied respond
continuation to a constant Response
value with:
The
responseLBS
composer requires a lazyByteString
as the body, which we could specify with aString
literal thanks to theOverloadedStrings
language extension.
You can find a slightly longer example here.
Other that allowing applications to run on multiple web servers without code changes2, we can also decorate our application with middlewares.
A Middleware
is essentially an Application
transformer:
type Middleware = Application -> Application
And enables the implementation of orthogonal, cross-cutting concerns like authentication & authorizations, GZIP compression, caching, etc. And best of all, as you can see from the type signature, they can be composed.
I don’t have time to get into the specifics here, but suffice to say that it’s a very powerful feature.
It supports HTTP 2! To know how it’s structured, see its chapter in the AOSA book. ↩
This is not entirely true since there are other considerations to take into account before porting a web application from one server to another. ↩