servant/doc/tutorial/Server.lhs

1201 lines
39 KiB
Text
Raw Permalink Normal View History

# Serving an API
2016-01-25 14:11:40 +01:00
Enough chit-chat about type-level combinators and representing an API as a
type. Can we have a webservice already?
2016-02-18 00:39:40 +01:00
## A first example
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Equipped with some basic knowledge about the way we represent APIs, let's now
2016-02-18 00:48:05 +01:00
write our first webservice.
2016-01-25 14:11:40 +01:00
The source for this tutorial section is a literate haskell file, so first we
need to have some language extensions and imports:
2016-01-27 22:28:58 +01:00
``` haskell
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
2017-10-01 18:20:09 +02:00
{-# LANGUAGE RankNTypes #-}
2016-01-27 22:28:58 +01:00
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Server where
2016-01-28 14:39:54 +01:00
import Prelude ()
import Prelude.Compat
2016-02-27 19:53:03 +01:00
import Control.Monad.Except
2016-01-27 22:28:58 +01:00
import Control.Monad.Reader
import Data.Aeson
2016-01-27 22:28:58 +01:00
import Data.Aeson.Types
import Data.Attoparsec.ByteString
import Data.ByteString (ByteString)
import Data.List
2016-02-27 19:53:03 +01:00
import Data.Maybe
2016-01-27 22:28:58 +01:00
import Data.String.Conversions
import Data.Time.Calendar
import GHC.Generics
import Lucid
import Network.HTTP.Media ((//), (/:))
import Network.Wai
import Network.Wai.Handler.Warp
import Servant
import System.Directory
import Text.Blaze
import Text.Blaze.Html.Renderer.Utf8
import Servant.Types.SourceT (source)
2016-01-27 22:28:58 +01:00
import qualified Data.Aeson.Parser
import qualified Text.Blaze.Html
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
**Important**: the `Servant` module comes from the **servant-server** package,
2016-02-18 00:48:05 +01:00
the one that lets us run webservers that implement a particular API type. It
2016-02-27 19:53:03 +01:00
reexports all the types from the **servant** package that let you declare API
2016-02-18 00:48:05 +01:00
types as well as everything you need to turn your request handlers into a
fully-fledged webserver. This means that in your applications, you can just add
2016-02-27 19:53:03 +01:00
**servant-server** as a dependency, import `Servant` and not worry about anything
2016-02-18 00:48:05 +01:00
else.
2016-01-25 14:11:40 +01:00
We will write a server that will serve the following API.
2016-01-27 22:28:58 +01:00
``` haskell
type UserAPI1 = "users" :> Get '[JSON] [User]
```
2016-01-25 14:11:40 +01:00
Here's what we would like to see when making a GET request to `/users`.
``` javascript
[ {"name": "Isaac Newton", "age": 372, "email": "isaac@newton.co.uk", "registration_date": "1683-03-01"}
, {"name": "Albert Einstein", "age": 136, "email": "ae@mc2.org", "registration_date": "1905-12-01"}
]
```
Now let's define our `User` data type and write some instances for it.
2016-01-27 22:28:58 +01:00
``` haskell
data User = User
{ name :: String
, age :: Int
, email :: String
2016-02-18 00:48:05 +01:00
, registration_date :: Day
2016-01-27 22:28:58 +01:00
} deriving (Eq, Show, Generic)
instance ToJSON User
```
2016-01-25 14:11:40 +01:00
Nothing funny going on here. But we now can define our list of two users.
2016-01-27 22:28:58 +01:00
``` haskell
users1 :: [User]
users1 =
[ User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
, User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
]
```
2016-01-25 14:11:40 +01:00
We can now take care of writing the actual webservice that will handle requests
to such an API. This one will be very simple, being reduced to just a single
endpoint. The type of the web application is determined by the API type,
through a *type family* named `Server`. (Type families are just functions that
take types as input and return types.) The `Server` type family will compute
the right type that a bunch of request handlers should have just from the
corresponding API type.
The first thing to know about the `Server` type family is that behind the
scenes it will drive the routing, letting you focus only on the business
logic. The second thing to know is that for each endpoint, your handlers will
by default run in the `Handler` monad. This is overridable very
2016-01-25 14:11:40 +01:00
easily, as explained near the end of this guide. Third thing, the type of the
value returned in that monad must be the same as the second argument of the
HTTP method combinator used for the corresponding endpoint. In our case, it
means we must provide a handler of type `Handler [User]`. Well,
2016-01-25 14:11:40 +01:00
we have a monad, let's just `return` our list:
2016-01-27 22:28:58 +01:00
``` haskell
server1 :: Server UserAPI1
server1 = return users1
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
That's it. Now we can turn `server` into an actual webserver using
[wai](http://hackage.haskell.org/package/wai) and
[warp](http://hackage.haskell.org/package/warp):
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
userAPI :: Proxy UserAPI1
userAPI = Proxy
-- 'serve' comes from servant and hands you a WAI Application,
-- which you can think of as an "abstract" web application,
-- not yet a webserver.
app1 :: Application
app1 = serve userAPI server1
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
The `userAPI` bit is, alas, boilerplate (we need it to guide type inference).
But that's about as much boilerplate as you get.
And we're done! Let's run our webservice on the port 8081.
2016-01-27 22:28:58 +01:00
``` haskell
main :: IO ()
main = run 8081 app1
```
2016-01-25 14:11:40 +01:00
You can put this all into a file or just grab [servant's
repo](http://github.com/haskell-servant/servant) and look at the
2016-02-27 19:53:03 +01:00
*doc/tutorial* directory. This code (the source of this web page) is in
*doc/tutorial/Server.lhs*.
2016-01-25 14:11:40 +01:00
If you run it, you can go to `http://localhost:8081/users` in your browser or
query it with curl and you see:
``` bash
$ curl http://localhost:8081/users
[{"email":"isaac@newton.co.uk","registration_date":"1683-03-01","age":372,"name":"Isaac Newton"},{"email":"ae@mc2.org","registration_date":"1905-12-01","age":136,"name":"Albert Einstein"}]
```
2016-02-18 00:39:40 +01:00
## More endpoints
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
What if we want more than one endpoint? Let's add `/albert` and `/isaac` to
view the corresponding users encoded in JSON.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
type UserAPI2 = "users" :> Get '[JSON] [User]
:<|> "albert" :> Get '[JSON] User
:<|> "isaac" :> Get '[JSON] User
```
2016-01-25 14:11:40 +01:00
And let's adapt our code a bit.
2016-01-27 22:28:58 +01:00
``` haskell
isaac :: User
isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)
albert :: User
albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)
users2 :: [User]
users2 = [isaac, albert]
```
2016-01-25 14:11:40 +01:00
Now, just like we separate the various endpoints in `UserAPI` with `:<|>`, we
are going to separate the handlers with `:<|>` too! They must be provided in
the same order as in the API type.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
server2 :: Server UserAPI2
server2 = return users2
:<|> return albert
:<|> return isaac
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
And that's it! You can run this example in the same way that we showed for
`server1` and check out the data available at `/users`, `/albert` and `/isaac`.
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
## From combinators to handler arguments
2016-01-25 14:11:40 +01:00
Fine, we can write trivial webservices easily, but none of the two above use
any "fancy" combinator from servant. Let's address this and use `QueryParam`,
`Capture` and `ReqBody` right away. You'll see how each occurrence of these
2016-01-25 14:11:40 +01:00
combinators in an endpoint makes the corresponding handler receive an
argument of the appropriate type automatically. You don't have to worry about
manually looking up URL captures or query string parameters, or
decoding/encoding data from/to JSON. Never.
2016-02-18 00:48:05 +01:00
We are going to use the following data types and functions to implement a
server for `API`.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
:<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
:<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
data Position = Position
2016-02-18 00:39:40 +01:00
{ xCoord :: Int
, yCoord :: Int
2016-01-27 22:28:58 +01:00
} deriving Generic
instance ToJSON Position
newtype HelloMessage = HelloMessage { msg :: String }
deriving Generic
instance ToJSON HelloMessage
data ClientInfo = ClientInfo
{ clientName :: String
, clientEmail :: String
, clientAge :: Int
, clientInterestedIn :: [String]
} deriving Generic
instance FromJSON ClientInfo
instance ToJSON ClientInfo
data Email = Email
{ from :: String
, to :: String
, subject :: String
, body :: String
} deriving Generic
instance ToJSON Email
emailForClient :: ClientInfo -> Email
emailForClient c = Email from' to' subject' body'
where from' = "great@company.com"
to' = clientEmail c
subject' = "Hey " ++ clientName c ++ ", we miss you!"
body' = "Hi " ++ clientName c ++ ",\n\n"
++ "Since you've recently turned " ++ show (clientAge c)
++ ", have you checked out our latest "
++ intercalate ", " (clientInterestedIn c)
++ " products? Give us a visit!"
```
2016-01-25 14:11:40 +01:00
We can implement handlers for the three endpoints:
2016-01-27 22:28:58 +01:00
``` haskell
server3 :: Server API
server3 = position
:<|> hello
:<|> marketing
where position :: Int -> Int -> Handler Position
2016-01-27 22:28:58 +01:00
position x y = return (Position x y)
hello :: Maybe String -> Handler HelloMessage
2016-01-27 22:28:58 +01:00
hello mname = return . HelloMessage $ case mname of
Nothing -> "Hello, anonymous coward"
Just n -> "Hello, " ++ n
marketing :: ClientInfo -> Handler Email
2016-01-27 22:28:58 +01:00
marketing clientinfo = return (emailForClient clientinfo)
```
2016-01-25 14:11:40 +01:00
Did you see that? The types for your handlers changed to be just what we
needed! In particular:
- a `Capture "something" a` becomes an argument of type `a` (for `position`);
- a `QueryParam "something" a` becomes an argument of type `Maybe a` (because
an endpoint can technically be accessed without specifying any query
string parameter, we decided to "force" handlers to be aware that the
parameter might not always be there);
- a `ReqBody contentTypeList a` becomes an argument of type `a`;
2016-02-27 19:53:03 +01:00
And that's it. Here's the example in action:
2016-01-25 14:11:40 +01:00
``` bash
$ curl http://localhost:8081/position/1/2
2016-03-01 12:47:14 +01:00
{"xCoord":1,"yCoord":2}
2016-01-25 14:11:40 +01:00
$ curl http://localhost:8081/hello
{"msg":"Hello, anonymous coward"}
$ curl http://localhost:8081/hello?name=Alp
{"msg":"Hello, Alp"}
2016-03-01 12:47:14 +01:00
$ curl -X POST -d '{"clientName":"Alp Mestanogullari", "clientEmail" : "alp@foo.com", "clientAge": 25, "clientInterestedIn": ["haskell", "mathematics"]}' -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8081/marketing
2016-01-25 14:11:40 +01:00
{"subject":"Hey Alp Mestanogullari, we miss you!","body":"Hi Alp Mestanogullari,\n\nSince you've recently turned 25, have you checked out our latest haskell, mathematics products? Give us a visit!","to":"alp@foo.com","from":"great@company.com"}
```
2016-02-27 19:53:03 +01:00
For reference, here's a list of some combinators from **servant**:
2016-01-25 14:11:40 +01:00
> - `Delete`, `Get`, `Patch`, `Post`, `Put`: these do not become arguments. They provide the return type of handlers, which usually is `Handler <something>`.
2016-01-25 14:11:40 +01:00
> - `Capture "something" a` becomes an argument of type `a`.
2016-02-27 19:53:03 +01:00
> - `QueryParam "something" a`, `Header "something" a` all become arguments of type `Maybe a`, because there might be no value at all specified by the client for these.
> - `QueryFlag "something"` gets turned into an argument of type `Bool`.
> - `QueryParams "something" a` gets turned into an argument of type `[a]`.
2016-01-25 14:11:40 +01:00
> - `ReqBody contentTypes a` gets turned into an argument of type `a`.
2016-02-18 00:39:40 +01:00
## The `FromHttpApiData`/`ToHttpApiData` classes
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Wait... How does **servant** know how to decode the `Int`s from the URL? Or how
to decode a `ClientInfo` value from the request body? The following three sections will
help us answer these questions.
2016-01-25 14:11:40 +01:00
`Capture`s and `QueryParam`s are represented by some textual value in URLs.
`Header`s are similarly represented by a pair of a header name and a
2016-02-18 00:39:40 +01:00
corresponding (textual) value in the request's "metadata". How types are
decoded from headers, captures, and query params is expressed in a class
`FromHttpApiData` (from the package
2016-02-27 19:53:03 +01:00
[**http-api-data**](http://hackage.haskell.org/package/http-api-data)):
2016-01-25 14:11:40 +01:00
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-02-18 00:39:40 +01:00
class FromHttpApiData a where
{-# MINIMAL parseUrlPiece | parseQueryParam #-}
-- | Parse URL path piece.
parseUrlPiece :: Text -> Either Text a
parseUrlPiece = parseQueryParam
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
-- | Parse HTTP header value.
parseHeader :: ByteString -> Either Text a
parseHeader = parseUrlPiece . decodeUtf8
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
-- | Parse query param value.
parseQueryParam :: Text -> Either Text a
parseQueryParam = parseUrlPiece
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
As you can see, as long as you provide either `parseUrlPiece` (for `Capture`s)
or `parseQueryParam` (for `QueryParam`s), the other methods will be defined in
terms of this.
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
**http-api-data** provides a decent number of instances, helpers for defining new
2016-02-18 00:39:40 +01:00
ones, and wonderful documentation.
2016-01-25 14:11:40 +01:00
There's not much else to say about these classes. You will need instances for
2016-02-18 00:39:40 +01:00
them when using `Capture`, `QueryParam`, `QueryParams`, and `Header` with your
types. You will need `FromHttpApiData` instances for server-side request
handlers and `ToHttpApiData` instances only when using
2016-02-27 19:53:03 +01:00
**servant-client**, as described in the [section about deriving haskell
functions to query an API](Client.html).
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
## Using content-types with your data types
2016-01-25 14:11:40 +01:00
The same principle was operating when decoding request bodies from JSON, and
responses *into* JSON. (JSON is just the running example - you can do this with
any content-type.)
2016-02-27 19:53:03 +01:00
This section introduces a couple of typeclasses provided by **servant** that make
2016-01-25 14:11:40 +01:00
all of this work.
2016-02-18 00:39:40 +01:00
### The truth behind `JSON`
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
What exactly is `JSON` (the type as used in `Get '[JSON] User`)? Like the 3
other content-types provided out of the box by **servant**, it's a really dumb
data type.
2016-01-25 14:11:40 +01:00
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
data JSON
data PlainText
data FormUrlEncoded
data OctetStream
```
Obviously, this is not all there is to `JSON`, otherwise it would be quite
2016-02-27 19:53:03 +01:00
pointless. Like most of the data types in **servant**, `JSON` is mostly there as
2016-01-25 14:11:40 +01:00
a special *symbol* that's associated with encoding (resp. decoding) to (resp.
from) the *JSON* format. The way this association is performed can be
decomposed into two steps.
The first step is to provide a proper
2016-02-27 19:53:03 +01:00
`MediaType` (from
[**http-media**](https://hackage.haskell.org/package/http-media-0.6.2/docs/Network-HTTP-Media.html))
representation for `JSON`, or for your own content-types. If you look at the
2016-01-25 14:11:40 +01:00
haddocks from this link, you can see that we just have to specify
`application/json` using the appropriate functions. In our case, we can just
use `(//) :: ByteString -> ByteString -> MediaType`. The precise way to specify
the `MediaType` is to write an instance for the `Accept` class:
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
-- for reference:
class Accept ctype where
contentType :: Proxy ctype -> MediaType
instance Accept JSON where
contentType _ = "application" // "json"
```
The second step is centered around the `MimeRender` and `MimeUnrender` classes.
2016-02-27 19:53:03 +01:00
These classes just let you specify a way to encode and decode
values into or from your content-type's representation.
2016-01-25 14:11:40 +01:00
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
class Accept ctype => MimeRender ctype a where
2016-02-27 19:53:03 +01:00
mimeRender :: Proxy ctype -> a -> ByteString
2016-01-25 14:11:40 +01:00
-- alternatively readable as:
2016-02-27 19:53:03 +01:00
mimeRender :: Proxy ctype -> (a -> ByteString)
2016-01-25 14:11:40 +01:00
```
Given a content-type and some user type, `MimeRender` provides a function that
encodes values of type `a` to lazy `ByteString`s.
In the case of `JSON`, this is easily dealt with! For any type `a` with a
`ToJSON` instance, we can render values of that type to JSON using
`Data.Aeson.encode`.
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
instance ToJSON a => MimeRender JSON a where
mimeRender _ = encode
```
And now the `MimeUnrender` class, which lets us extract values from lazy
`ByteString`s, alternatively failing with an error string.
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
class Accept ctype => MimeUnrender ctype a where
mimeUnrender :: Proxy ctype -> ByteString -> Either String a
```
We don't have much work to do there either, `Data.Aeson.eitherDecode` is
precisely what we need. However, it only allows arrays and objects as toplevel
JSON values and this has proven to get in our way more than help us so we wrote
2016-02-27 19:53:03 +01:00
our own little function around **aeson** and **attoparsec** that allows any type of
2016-01-25 14:11:40 +01:00
JSON value at the toplevel of a "JSON document". Here's the definition in case
you are curious.
2016-01-27 22:28:58 +01:00
``` haskell
eitherDecodeLenient :: FromJSON a => ByteString -> Either String a
eitherDecodeLenient input = do
v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
parseEither parseJSON v
```
2016-01-25 14:11:40 +01:00
This function is exactly what we need for our `MimeUnrender` instance.
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
instance FromJSON a => MimeUnrender JSON a where
mimeUnrender _ = eitherDecodeLenient
```
2016-02-27 19:53:03 +01:00
And this is all the code that lets you use `JSON` with `ReqBody`, `Get`,
2016-01-25 14:11:40 +01:00
`Post` and friends. We can check our understanding by implementing support
2016-02-27 19:53:03 +01:00
for an `HTML` content-type, so that users of your webservice can access an
2016-01-25 14:11:40 +01:00
HTML representation of the data they want, ready to be included in any HTML
2016-02-18 00:48:05 +01:00
document, e.g. using [jQuery's `load` function](https://api.jquery.com/load/),
simply by adding `Accept: text/html` to their request headers.
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
### Case-studies: **servant-blaze** and **servant-lucid**
2016-01-25 14:11:40 +01:00
These days, most of the haskellers who write their HTML UIs directly from
2016-02-27 19:53:03 +01:00
Haskell use either [**blaze-html**](http://hackage.haskell.org/package/blaze-html)
or [**lucid**](http://hackage.haskell.org/package/lucid). The best option for
**servant** is obviously to support both (and hopefully other templating
solutions!). We're first going to look at **lucid**:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
data HTMLLucid
```
2016-01-25 14:11:40 +01:00
Once again, the data type is just there as a symbol for the encoding/decoding
functions, except that this time we will only worry about encoding since
2016-02-27 19:53:03 +01:00
**lucid** doesn't provide a way to extract data from HTML.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
instance Accept HTMLLucid where
contentType _ = "text" // "html" /: ("charset", "utf-8")
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Note that this instance uses the `(/:)` operator from **http-media** which lets
2016-01-25 14:11:40 +01:00
us specify additional information about a content-type, like the charset here.
2016-02-27 19:53:03 +01:00
The rendering instances call similar functions that take
2016-01-25 14:11:40 +01:00
types with an appropriate instance to an "abstract" HTML representation and
then write that to a `ByteString`.
2016-01-27 22:28:58 +01:00
``` haskell
instance ToHtml a => MimeRender HTMLLucid a where
mimeRender _ = renderBS . toHtml
-- let's also provide an instance for lucid's
-- 'Html' wrapper.
instance MimeRender HTMLLucid (Html a) where
mimeRender _ = renderBS
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
For **blaze-html** everything works very similarly:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
-- For this tutorial to compile 'HTMLLucid' and 'HTMLBlaze' have to be
-- distinct. Usually you would stick to one html rendering library and then
-- you can go with one 'HTML' type.
data HTMLBlaze
instance Accept HTMLBlaze where
contentType _ = "text" // "html" /: ("charset", "utf-8")
instance ToMarkup a => MimeRender HTMLBlaze a where
mimeRender _ = renderHtml . Text.Blaze.Html.toHtml
-- while we're at it, just like for lucid we can
-- provide an instance for rendering blaze's 'Html' type
instance MimeRender HTMLBlaze Text.Blaze.Html.Html where
mimeRender _ = renderHtml
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Both [**servant-blaze**](http://hackage.haskell.org/package/servant-blaze) and
[**servant-lucid**](http://hackage.haskell.org/package/servant-lucid) let you use
`HTMLLucid` and `HTMLBlaze` in any content-type list as long as you provide an instance of the
appropriate class (`ToMarkup` for **blaze-html**, `ToHtml` for **lucid**).
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
We can now write a webservice that uses **servant-lucid** to show the `HTMLLucid`
content-type in action. We will be serving the following API:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
type PersonAPI = "persons" :> Get '[JSON, HTMLLucid] [Person]
```
2016-01-25 14:11:40 +01:00
where `Person` is defined as follows:
2016-01-27 22:28:58 +01:00
``` haskell
data Person = Person
{ firstName :: String
, lastName :: String
} deriving Generic -- for the JSON instance
instance ToJSON Person
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Now, let's teach **lucid** how to render a `Person` as a row in a table, and then
2016-01-25 14:11:40 +01:00
a list of `Person`s as a table with a row per person.
2016-01-27 22:28:58 +01:00
``` haskell
-- HTML serialization of a single person
instance ToHtml Person where
toHtml person =
tr_ $ do
td_ (toHtml $ firstName person)
td_ (toHtml $ lastName person)
-- do not worry too much about this
toHtmlRaw = toHtml
-- HTML serialization of a list of persons
instance ToHtml [Person] where
toHtml persons = table_ $ do
tr_ $ do
th_ "first name"
th_ "last name"
-- this just calls toHtml on each person of the list
-- and concatenates the resulting pieces of HTML together
foldMap toHtml persons
toHtmlRaw = toHtml
```
2016-01-25 14:11:40 +01:00
We create some `Person` values and serve them as a list:
2016-01-27 22:28:58 +01:00
``` haskell
2016-02-18 00:39:40 +01:00
people :: [Person]
people =
2016-01-27 22:28:58 +01:00
[ Person "Isaac" "Newton"
, Person "Albert" "Einstein"
]
personAPI :: Proxy PersonAPI
personAPI = Proxy
server4 :: Server PersonAPI
2016-02-18 00:39:40 +01:00
server4 = return people
2016-01-27 22:28:58 +01:00
app2 :: Application
2016-02-27 19:53:03 +01:00
app2 = serve personAPI server4
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
And we're good to go:
2016-01-25 14:11:40 +01:00
``` bash
2016-02-28 22:21:04 +01:00
$ curl http://localhost:8081/persons
[{"lastName":"Newton","firstName":"Isaac"},{"lastName":"Einstein","firstName":"Albert"}]
$ curl -H 'Accept: text/html' http://localhost:8081/persons
<table><tr><td>first name</td><td>last name</td></tr><tr><td>Isaac</td><td>Newton</td></tr><tr><td>Albert</td><td>Einstein</td></tr></table>
# or just point your browser to http://localhost:8081/persons
2016-01-25 14:11:40 +01:00
```
## The `Handler` monad
2016-01-25 14:11:40 +01:00
At the heart of the handlers is the monad they run in, namely a newtype `Handler` around `ExceptT ServerError IO`
([haddock documentation for `ExceptT`](http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Except.html#t:ExceptT)).
2016-02-27 19:53:03 +01:00
One might wonder: why this monad? The answer is that it is the
2016-01-25 14:11:40 +01:00
simplest monad with the following properties:
2016-02-27 19:53:03 +01:00
- it lets us both return a successful result (using `return`)
or "fail" with a descriptive error (using `throwError`);
2016-01-25 14:11:40 +01:00
- it lets us perform IO, which is absolutely vital since most webservices exist
2016-02-27 19:53:03 +01:00
as interfaces to databases that we interact with in `IO`.
2016-01-25 14:11:40 +01:00
Let's recall some definitions.
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-02-18 00:39:40 +01:00
-- from the 'mtl' package at
2016-02-27 19:53:03 +01:00
newtype ExceptT e m a = ExceptT (m (Either e a))
2016-01-25 14:11:40 +01:00
```
In short, this means that a handler of type `Handler a` is simply
equivalent to a computation of type `IO (Either ServerError a)`, that is, an IO
2016-01-25 14:11:40 +01:00
action that either returns an error or a result.
The module [`Control.Monad.Except`](https://hackage.haskell.org/package/mtl/docs/Control-Monad-Except.html#t:ExceptT)
2016-02-18 00:39:40 +01:00
from which `ExceptT` comes is worth looking at.
Perhaps most importantly, `ExceptT` and `Handler` are instances of `MonadError`, so
2016-02-18 00:39:40 +01:00
`throwError` can be used to return an error from your handler (whereas `return`
is enough to return a success).
2016-01-25 14:11:40 +01:00
Most of what you'll be doing in your handlers is running some IO and,
depending on the result, you might sometimes want to throw an error of some
kind and abort early. The next two sections cover how to do just that.
2016-02-18 00:39:40 +01:00
### Performing IO
2016-01-25 14:11:40 +01:00
Other important instances from the list above are `MonadIO m => MonadIO
(ExceptT e m)`, and therefore also `MonadIO Handler` as there is a `MonadIO IO` instance.
[`MonadIO`](http://hackage.haskell.org/package/base/docs/Control-Monad-IO-Class.html#t:MonadIO)
2016-02-27 19:53:03 +01:00
is a class from the **transformers** package defined as:
2016-01-25 14:11:40 +01:00
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
class Monad m => MonadIO m where
liftIO :: IO a -> m a
```
So if you want to run any kind of
2016-02-18 00:39:40 +01:00
IO computation in your handlers, just use `liftIO`:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
type IOAPI1 = "myfile.txt" :> Get '[JSON] FileContent
newtype FileContent = FileContent
{ content :: String }
deriving Generic
instance ToJSON FileContent
server5 :: Server IOAPI1
server5 = do
filecontent <- liftIO (readFile "myfile.txt")
return (FileContent filecontent)
```
2016-01-25 14:11:40 +01:00
### Failing, through `ServerError`
2016-01-25 14:11:40 +01:00
If you want to explicitly fail at providing the result promised by an endpoint
using the appropriate HTTP status code (not found, unauthorized, etc) and some
2016-02-27 19:53:03 +01:00
error message, all you have to do is use the `throwError` function mentioned above
and provide it with the appropriate value of type `ServerError`, which is
2016-01-25 14:11:40 +01:00
defined as:
2016-01-27 22:26:59 +01:00
``` haskell ignore
data ServerError = ServerError
2016-01-25 14:11:40 +01:00
{ errHTTPCode :: Int
, errReasonPhrase :: String
, errBody :: ByteString -- lazy bytestring
, errHeaders :: [Header]
}
```
Many standard values are provided out of the box by the `Servant.Server`
module. If you want to use these values but add a body or some headers, just
use record update syntax:
2016-01-27 22:28:58 +01:00
``` haskell
failingHandler :: Handler ()
2016-02-27 19:53:03 +01:00
failingHandler = throwError myerr
2016-01-27 22:28:58 +01:00
where myerr :: ServerError
2016-01-27 22:28:58 +01:00
myerr = err503 { errBody = "Sorry dear user." }
```
2016-01-25 14:11:40 +01:00
Here's an example where we return a customised 404-Not-Found error message in
the response body if "myfile.txt" isn't there:
2016-01-27 22:28:58 +01:00
``` haskell
server6 :: Server IOAPI1
server6 = do
exists <- liftIO (doesFileExist "myfile.txt")
if exists
then liftIO (readFile "myfile.txt") >>= return . FileContent
2016-02-27 19:53:03 +01:00
else throwError custom404Err
2016-01-27 22:28:58 +01:00
where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Here's how that server looks in action:
2016-01-25 14:11:40 +01:00
``` bash
2016-02-28 22:21:04 +01:00
$ curl --verbose http://localhost:8081/myfile.txt
[snip]
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /myfile.txt HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8081
> Accept: */*
>
< HTTP/1.1 404 Not Found
[snip]
myfile.txt just isn't there, please leave this server alone.
2016-02-28 22:21:04 +01:00
$ echo Hello > myfile.txt
$ curl --verbose http://localhost:8081/myfile.txt
[snip]
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /myfile.txt HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8081
> Accept: */*
>
< HTTP/1.1 200 OK
[snip]
< Content-Type: application/json
[snip]
{"content":"Hello\n"}
2016-01-25 14:11:40 +01:00
```
2016-02-18 00:39:40 +01:00
## Response headers
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
To add headers to your response, use
[addHeader](http://hackage.haskell.org/package/servant/docs/Servant-API-ResponseHeaders.html).
2016-01-25 14:11:40 +01:00
Note that this changes the type of your API, as we can see in the following example:
2016-01-27 22:28:58 +01:00
``` haskell
type MyHandler = Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
myHandler :: Server MyHandler
myHandler = return $ addHeader 1797 albert
```
2016-01-25 14:11:40 +01:00
Note that the type of `addHeader header x` is different than the type of `x`!
And if you add more headers, more headers will appear in the header list:
``` haskell
type MyHeadfulHandler = Get '[JSON] (Headers '[Header "X-A-Bool" Bool, Header "X-An-Int" Int] User)
myHeadfulHandler :: Server MyHeadfulHandler
myHeadfulHandler = return $ addHeader True $ addHeader 1797 albert
```
But what if your handler only *sometimes* adds a header? If you declare that
your handler adds headers, and you don't add one, the return type of your
handler will be different than expected. To solve this, you have to explicitly
*not* add a header by using `noHeader`:
``` haskell
type MyMaybeHeaderHandler
= Capture "withHeader" Bool :> Get '[JSON] (Headers '[Header "X-An-Int" Int] User)
myMaybeHeaderHandler :: Server MyMaybeHeaderHandler
myMaybeHeaderHandler x = return $ if x then addHeader 1797 albert
else noHeader albert
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
## Serving static files
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
**servant-server** also provides a way to just serve the content of a directory
2016-01-25 14:11:40 +01:00
under some path in your web API. As mentioned earlier in this document, the
`Raw` combinator can be used in your APIs to mean "plug here any WAI
2016-02-27 19:53:03 +01:00
application". Well, **servant-server** provides a function to get a file and
2016-01-25 14:11:40 +01:00
directory serving WAI application, namely:
2016-01-27 22:26:59 +01:00
``` haskell ignore
2016-01-25 14:11:40 +01:00
-- exported by Servant and Servant.Server
serveDirectoryWebApp :: FilePath -> Server Raw
2016-01-25 14:11:40 +01:00
```
`serveDirectoryWebApp`'s argument must be a path to a valid directory.
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
Here's an example API that will serve some static files:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
2016-02-27 19:53:03 +01:00
type StaticAPI = "static" :> Raw
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
And the server:
2016-01-27 22:28:58 +01:00
``` haskell
2016-02-27 19:53:03 +01:00
staticAPI :: Proxy StaticAPI
staticAPI = Proxy
2016-01-27 22:28:58 +01:00
```
``` haskell
2016-02-27 19:53:03 +01:00
server7 :: Server StaticAPI
server7 = serveDirectoryWebApp "static-files"
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
app3 :: Application
2016-02-27 19:53:03 +01:00
app3 = serve staticAPI server7
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
This server will match any request whose path starts with `/static` and will look
2016-02-18 00:39:40 +01:00
for a file at the path described by the rest of the request path, inside the
2016-02-27 19:53:03 +01:00
*static-files/* directory of the path you run the program from.
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
In other words: If a client requests `/static/foo.txt`, the server will look for a file at
`./static-files/foo.txt`. If that file exists it'll succeed and serve the file.
If it doesn't exist, the handler will fail with a `404` status code.
2016-01-25 14:11:40 +01:00
`serveDirectoryWebApp` uses some standard settings that fit the use case of
serving static files for most web apps. You can find out about the other
options in the documentation of the `Servant.Server.StaticFiles` module.
2016-02-18 00:39:40 +01:00
## Nested APIs
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
Let's see how you can define APIs in a modular way, while avoiding repetition.
Consider this simple example:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
type UserAPI3 = -- view the user with given userid, in JSON
Capture "userid" Int :> Get '[JSON] User
:<|> -- delete the user with given userid. empty response
2019-09-08 12:32:25 +02:00
Capture "userid" Int :> DeleteNoContent
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
We can instead factor out the `userid`:
2016-01-27 22:28:58 +01:00
``` haskell
type UserAPI4 = Capture "userid" Int :>
( Get '[JSON] User
2019-09-08 12:32:25 +02:00
:<|> DeleteNoContent
2016-01-27 22:28:58 +01:00
)
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
However, you have to be aware that this has an effect on the type of the
corresponding `Server`:
2016-01-25 14:11:40 +01:00
2016-01-27 22:26:59 +01:00
``` haskell ignore
Server UserAPI3 = (Int -> Handler User)
:<|> (Int -> Handler NoContent)
2016-01-25 14:11:40 +01:00
Server UserAPI4 = Int -> ( Handler User
:<|> Handler NoContent
2016-01-25 14:11:40 +01:00
)
```
In the first case, each handler receives the *userid* argument. In the latter,
2016-02-18 00:48:05 +01:00
the whole `Server` takes the *userid* and has handlers that are just
computations in `Handler`, with no arguments. In other words:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
server8 :: Server UserAPI3
server8 = getUser :<|> deleteUser
where getUser :: Int -> Handler User
2016-01-27 22:28:58 +01:00
getUser _userid = error "..."
deleteUser :: Int -> Handler NoContent
2016-01-27 22:28:58 +01:00
deleteUser _userid = error "..."
-- notice how getUser and deleteUser
-- have a different type! no argument anymore,
-- the argument directly goes to the whole Server
server9 :: Server UserAPI4
server9 userid = getUser userid :<|> deleteUser userid
where getUser :: Int -> Handler User
2016-01-27 22:28:58 +01:00
getUser = error "..."
deleteUser :: Int -> Handler NoContent
2016-01-27 22:28:58 +01:00
deleteUser = error "..."
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
Note that there's nothing special about `Capture` that lets you "factor it
out": this can be done with any combinator. Here are a few examples of APIs
with a combinator factored out for which we can write a perfectly valid
`Server`.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
-- we just factor out the "users" path fragment
type API1 = "users" :>
( Get '[JSON] [User] -- user listing
:<|> Capture "userid" Int :> Get '[JSON] User -- view a particular user
)
-- we factor out the Request Body
type API2 = ReqBody '[JSON] User :>
( Get '[JSON] User -- just display the same user back, don't register it
2019-09-08 12:32:25 +02:00
:<|> PostNoContent -- register the user. empty response
2016-01-27 22:28:58 +01:00
)
-- we factor out a Header
type API3 = Header "Authorization" Token :>
( Get '[JSON] SecretData -- get some secret data, if authorized
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] SecretData :> PostNoContent -- add some secret data, if authorized
2016-01-27 22:28:58 +01:00
)
newtype Token = Token ByteString
newtype SecretData = SecretData ByteString
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
This approach lets you define APIs modularly and assemble them all into one big
API type only at the end.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
type UsersAPI =
Get '[JSON] [User] -- list users
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] User :> PostNoContent -- add a user
2016-01-27 22:28:58 +01:00
:<|> Capture "userid" Int :>
( Get '[JSON] User -- view a user
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] User :> PutNoContent -- update a user
:<|> DeleteNoContent -- delete a user
2016-01-27 22:28:58 +01:00
)
usersServer :: Server UsersAPI
usersServer = getUsers :<|> newUser :<|> userOperations
where getUsers :: Handler [User]
2016-01-27 22:28:58 +01:00
getUsers = error "..."
newUser :: User -> Handler NoContent
2016-01-27 22:28:58 +01:00
newUser = error "..."
userOperations userid =
viewUser userid :<|> updateUser userid :<|> deleteUser userid
where
viewUser :: Int -> Handler User
2016-01-27 22:28:58 +01:00
viewUser = error "..."
updateUser :: Int -> User -> Handler NoContent
2016-01-27 22:28:58 +01:00
updateUser = error "..."
deleteUser :: Int -> Handler NoContent
2016-01-27 22:28:58 +01:00
deleteUser = error "..."
```
``` haskell
type ProductsAPI =
Get '[JSON] [Product] -- list products
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] Product :> PostNoContent -- add a product
2016-01-27 22:28:58 +01:00
:<|> Capture "productid" Int :>
( Get '[JSON] Product -- view a product
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] Product :> PutNoContent -- update a product
:<|> DeleteNoContent -- delete a product
2016-01-27 22:28:58 +01:00
)
data Product = Product { productId :: Int }
productsServer :: Server ProductsAPI
productsServer = getProducts :<|> newProduct :<|> productOperations
where getProducts :: Handler [Product]
2016-01-27 22:28:58 +01:00
getProducts = error "..."
newProduct :: Product -> Handler NoContent
2016-01-27 22:28:58 +01:00
newProduct = error "..."
productOperations productid =
viewProduct productid :<|> updateProduct productid :<|> deleteProduct productid
where
viewProduct :: Int -> Handler Product
2016-01-27 22:28:58 +01:00
viewProduct = error "..."
updateProduct :: Int -> Product -> Handler NoContent
2016-01-27 22:28:58 +01:00
updateProduct = error "..."
deleteProduct :: Int -> Handler NoContent
2016-01-27 22:28:58 +01:00
deleteProduct = error "..."
```
``` haskell
type CombinedAPI = "users" :> UsersAPI
:<|> "products" :> ProductsAPI
server10 :: Server CombinedAPI
server10 = usersServer :<|> productsServer
```
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
Finally, we can realize the user and product APIs are quite similar and
abstract that away:
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
-- API for values of type 'a'
-- indexed by values of type 'i'
type APIFor a i =
Get '[JSON] [a] -- list 'a's
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] a :> PostNoContent -- add an 'a'
2016-01-27 22:28:58 +01:00
:<|> Capture "id" i :>
( Get '[JSON] a -- view an 'a' given its "identifier" of type 'i'
2019-09-08 12:32:25 +02:00
:<|> ReqBody '[JSON] a :> PutNoContent -- update an 'a'
:<|> DeleteNoContent -- delete an 'a'
2016-01-27 22:28:58 +01:00
)
-- Build the appropriate 'Server'
-- given the handlers of the right type.
serverFor :: Handler [a] -- handler for listing of 'a's
-> (a -> Handler NoContent) -- handler for adding an 'a'
-> (i -> Handler a) -- handler for viewing an 'a' given its identifier of type 'i'
-> (i -> a -> Handler NoContent) -- updating an 'a' with given id
-> (i -> Handler NoContent) -- deleting an 'a' given its id
2016-01-27 22:28:58 +01:00
-> Server (APIFor a i)
serverFor = error "..."
-- implementation left as an exercise. contact us on IRC
-- or the mailing list if you get stuck!
```
2016-01-25 14:11:40 +01:00
2017-05-17 10:24:04 +02:00
When your API contains the `EmptyAPI` combinator, you'll want to use
`emptyServer` in the corresponding slot for your server, which will simply fail
with 404 whenever a request reaches it:
2017-05-16 11:34:07 +02:00
``` haskell
2017-05-17 07:50:38 +02:00
type CombinedAPI2 = API :<|> "empty" :> EmptyAPI
2017-05-16 11:34:07 +02:00
server11 :: Server CombinedAPI2
2017-05-16 17:59:41 +02:00
server11 = server3 :<|> emptyServer
2017-05-16 11:34:07 +02:00
```
2016-02-18 00:39:40 +01:00
## Using another monad for your handlers
2016-01-25 14:11:40 +01:00
Remember how `Server` turns combinators for HTTP methods into `Handler`? Well, actually, there's more to that. `Server` is actually a
2016-02-18 00:48:05 +01:00
simple type synonym.
2016-01-25 14:11:40 +01:00
2016-01-27 22:26:59 +01:00
``` haskell ignore
type Server api = ServerT api Handler
2016-01-25 14:11:40 +01:00
```
2016-02-18 00:48:05 +01:00
`ServerT` is the actual type family that computes the required types for the
handlers that's part of the `HasServer` class. It's like `Server` except that
2016-02-27 19:53:03 +01:00
it takes another parameter which is the monad you want your handlers to run in,
2016-02-18 00:48:05 +01:00
or more generally the return types of your handlers. This third parameter is
used for specifying the return type of the handler for an endpoint, e.g when
computing `ServerT (Get '[JSON] Person) SomeMonad`. The result would be
`SomeMonad Person`.
2016-01-25 14:11:40 +01:00
2016-02-18 00:48:05 +01:00
The first and main question one might have then is: how do we write handlers
that run in another monad? How can we "bring back" the value from a given monad
2016-02-27 19:53:03 +01:00
into something **servant** can understand?
2016-01-25 14:11:40 +01:00
2016-02-18 00:39:40 +01:00
### Natural transformations
2016-01-25 14:11:40 +01:00
If we have a function that gets us from an `m a` to an `n a`, for any `a`, what
do we have?
2017-10-01 18:20:09 +02:00
``` haskell
type (~>) m n = forall a. m a -> n a
2016-02-27 19:53:03 +01:00
```
For example:
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
``` haskell
2017-10-01 18:20:09 +02:00
listToMaybe' :: [] ~> Maybe
listToMaybe' = listToMaybe -- from Data.Maybe
2016-01-25 14:11:40 +01:00
```
2016-02-27 19:53:03 +01:00
2017-10-01 18:20:09 +02:00
Note that `servant` doesn't declare the `~>` type-alias, as the unfolded
variant isn't much longer to write, as we'll see shortly.
2016-01-25 14:11:40 +01:00
So if you want to write handlers using another monad/type than `Handler`, say the `Reader String` monad, the first thing you have to
2016-01-25 14:11:40 +01:00
prepare is a function:
2016-01-27 22:26:59 +01:00
``` haskell ignore
2017-10-01 18:20:09 +02:00
readerToHandler :: Reader String a -> Handler a
2016-01-25 14:11:40 +01:00
```
2017-10-01 18:20:09 +02:00
We obviously have to run the `Reader` computation by supplying it with a
`String`, like `"hi"`. We get an `a` out from that and can then just `return`
it into `Handler`.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
2017-10-01 18:20:09 +02:00
readerToHandler :: Reader String a -> Handler a
readerToHandler r = return (runReader r "hi")
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
We can write some simple webservice with the handlers running in `Reader String`.
2016-01-27 22:28:58 +01:00
``` haskell
type ReaderAPI = "a" :> Get '[JSON] Int
:<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
2016-01-27 22:28:58 +01:00
readerAPI :: Proxy ReaderAPI
readerAPI = Proxy
readerServerT :: ServerT ReaderAPI (Reader String)
2017-10-01 18:20:09 +02:00
readerServerT = a :<|> b where
a :: Reader String Int
a = return 1797
2016-01-27 22:28:58 +01:00
2017-10-01 18:20:09 +02:00
b :: Double -> Reader String Bool
b _ = asks (== "hi")
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
We unfortunately can't use `readerServerT` as an argument of `serve`, because
`serve` wants a `Server ReaderAPI`, i.e., with handlers running in `Handler`. But there's a simple solution to this.
2016-01-25 14:11:40 +01:00
2017-10-01 18:20:09 +02:00
### Welcome `hoistServer`
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
That's right. We have just written `readerToHandler`, which is exactly what we
would need to apply to all handlers to make the handlers have the
2016-01-25 14:11:40 +01:00
right type for `serve`. Being cumbersome to do by hand, we provide a function
`hoistServer` which takes a natural transformation between two parameterized types `m`
2016-01-25 14:11:40 +01:00
and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
2017-10-01 18:20:09 +02:00
In our case, we can wrap up our little webservice by using
`hoistServer readerAPI readerToHandler` on our handlers.
2016-01-25 14:11:40 +01:00
2016-01-27 22:28:58 +01:00
``` haskell
readerServer :: Server ReaderAPI
2017-10-01 18:20:09 +02:00
readerServer = hoistServer readerAPI readerToHandler readerServerT
2016-01-27 22:28:58 +01:00
app4 :: Application
2016-02-27 19:53:03 +01:00
app4 = serve readerAPI readerServer
2016-01-27 22:28:58 +01:00
```
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
This is the webservice in action:
2016-01-25 14:11:40 +01:00
``` bash
$ curl http://localhost:8081/a
1797
$ curl http://localhost:8081/b -X GET -d '42.0' -H 'Content-Type: application/json'
true
2016-01-25 14:11:40 +01:00
```
2017-10-01 18:20:09 +02:00
### An arrow is a reader too.
In previous versions of `servant` we had an `enter` to do what `hoistServer`
does now. `enter` had an ambitious design goals, but was problematic in practice.
2017-10-01 18:20:09 +02:00
One problematic situation was when the source monad was `(->) r`, yet it's
handy in practice, because `(->) r` is isomorphic to `Reader r`.
We can rewrite the previous example without `Reader`:
```haskell
funServerT :: ServerT ReaderAPI ((->) String)
funServerT = a :<|> b where
a :: String -> Int
a _ = 1797
-- unfortunately, we cannot make `String` the first argument.
b :: Double -> String -> Bool
b _ s = s == "hi"
funToHandler :: (String -> a) -> Handler a
funToHandler f = return (f "hi")
app5 :: Application
app5 = serve readerAPI (hoistServer readerAPI funToHandler funServerT)
```
## Streaming endpoints
We can create endpoints that don't just give back a single result, but give
back a *stream* of results, served one at a time. Stream endpoints only provide
a single content type, and also specify what framing strategy is used to
delineate the results. To serve these results, we need to give back a stream
producer. Adapters can be written to *Pipes*, *Conduit* and the like, or
written directly as `SourceIO`s. SourceIO builds upon servant's own `SourceT`
stream type (it's simpler than *Pipes* or *Conduit*).
The API of a streaming endpoint needs to explicitly specify which sort of
generator it produces. Note that the generator itself is returned by a
`Handler` action, so that additional IO may be done in the creation of one.
``` haskell
type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (SourceIO User)
streamAPI :: Proxy StreamAPI
streamAPI = Proxy
streamUsers :: SourceIO User
streamUsers = source [isaac, albert, albert]
app6 :: Application
app6 = serve streamAPI (return streamUsers)
```
This simple application returns a stream of `User` values encoded in JSON
format, with each value separated by a newline. In this case, the stream will
consist of the value of `isaac`, followed by the value of `albert`, then the
value of `albert` a second time. Importantly, the stream is written back as
results are produced, rather than all at once. This means first that results
are delivered when they are available, and second, that if an exception
interrupts production of the full stream, nonetheless partial results have
already been written back.
2016-02-18 00:39:40 +01:00
## Conclusion
2016-01-25 14:11:40 +01:00
2016-02-27 19:53:03 +01:00
You're now equipped to write webservices/web-applications using
**servant**. The rest of this document focuses on **servant-client**,
**servant-js** and **servant-docs**.