Run Endpoint
Bootstrap
After application Endpoint
is combined and defined, it's time to convert it to WAI
application and run the server. Module Linnet.Bootstrap
contains set of functions for this purpose.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
import Control.Exception (SomeException)
import Data.Function ((&))
import Data.Text
import Linnet
import Linnet.Internal.Coproduct ((:+:))
import Network.Wai
instance Encode TextPlain SomeException where
encode _ = mempty
endpoint :: Endpoint IO Text
endpoint = get(pathEmpty) ~>> (return . ok $ "Hello, world")
app :: Application
app = bootstrap @TextPlain endpoint & compile & toApp
Linnet re-exports Warp's run
function to run WAI application:
main :: IO ()
main = run 9000 app
Here is what happened in the code above:
- Defined endpoint was "bootsrapped" for Content-Type
@Text/Plain
. Result of this endpoint will be encoded withEncode TextPlain
Bootstrap
was "compiled" intoReaderT (WriterT Trace m) Request (Either SomeException Response)
orCompiled m
for shortness. It could be useful to take a break here to add some middleware filters without leaving context of monadm
by transforming compiledReaderT
.Compiled m
was transformed to WAI application and run on Warp server
Serving multiple Content-Types
Often, server shall return different content depending on the expected Content-Type. Serving html page by encoding
it as JSON is likely incorrect behavior. Function serve
in combination with Bootstrap
allows to serve multiple
endpoints with different Content-Types:
indexHtml :: Endpoint IO Text
indexHtml = get(p'"index.html") ~>> (return . ok $ "<html><body>This is HTML</body></html>")
-- Need to define how to render exceptions in HTML encoding
instance Encode TextHtml SomeException where
encode _ = mempty
multipleContentTypesApp :: Application
multipleContentTypesApp =
bootstrap @TextPlain endpoint &
serve @TextHtml indexHtml &
compile &
toApp
Content-Type negotiation
It's also possible to serve different Content-Types depending on client's Accept
header as it's described in
RFC 2295:
contentTypeNegotiationApp :: Application
contentTypeNegotiationApp =
bootstrap @(TextPlain :+: ApplicationJson) endpoint & compile & toApp
Based on the client request and Accept
header values, Linnet picks the best matching encoder.
Important to mention that in the example above application/json
content would be returned in case of
failed negotiation.
If there is a need to enable 406 error signaling failed negotiation instead of falling back to the last option,
NotAcceptable406
comes in handy:
negotiateOr406App :: Application
negotiateOr406App =
bootstrap @(TextPlain :+: ApplicationJson :+: NotAcceptable406) endpoint & compile & toApp
Custom monad
Last step of converting Endpoint
into Application
is to call function toApp
that has a following signature:
toApp :: (NaturalTransformation m IO) => Compiled m -> Application
The constraint exposed is a natural transformation m ~> IO
to "run" custom monad as IO
.
It allows to use this monad until the very end of request resolution and could be useful to run logger,
create request-local context etc.
This example demonstrates how to make it work.