3While defining handlers that [serve an API](Server.lhs) has a lot to it, querying an API is simpler: we do not care about what happens inside the webserver, we just need to know how to talk to it and get a response back. That said, we usually have to write the querying functions by hand because the structure of the API isn't a first class citizen and can't be inspected to generate the client-side functions.
5**servant** however has a way to inspect APIs, because APIs are just Haskell types and (GHC) Haskell lets us do quite a few things with types. In the same way that we look at an API type to deduce the types the handlers should have, we can inspect the structure of the API to *derive* Haskell functions that take one argument for each occurrence of `Capture`, `ReqBody`, `QueryParam`
6and friends (see [the tutorial introduction](ApiType.lhs) for an overview). By *derive*, we mean that there's no code generation involved - the functions are defined just by the structure of the API type.
8The source for this tutorial section is a literate Haskell file, so first we need to have some language extensions and imports:
11{-# LANGUAGE DataKinds #-}
12{-# LANGUAGE DeriveGeneric #-}
13{-# LANGUAGE TypeOperators #-}
20import Network.HTTP.Client (newManager, defaultManagerSettings)
23import Servant.Types.SourceT (foreach)
24import Control.Monad.Codensity (Codensity)
27Also, we need examples for some domain specific data types:
30data Position = Position
33 } deriving (Show, Generic)
35instance FromJSON Position
37newtype HelloMessage = HelloMessage { msg :: String }
38 deriving (Show, Generic)
40instance FromJSON HelloMessage
42data ClientInfo = ClientInfo
43 { clientName :: String
44 , clientEmail :: String
46 , clientInterestedIn :: [String]
49instance ToJSON ClientInfo
56 } deriving (Show, Generic)
58instance FromJSON Email
61Enough chitchat, let's see an example. Consider the following API type from the previous section:
64type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
65 :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
66 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
69What we are going to get with **servant-client** here is three functions, one to query each endpoint:
72position :: Int -- ^ value for "x"
73 -> Int -- ^ value for "y"
76hello :: Maybe String -- ^ an optional value for "name"
77 -> ClientM HelloMessage
79marketing :: ClientInfo -- ^ value for the request body
83Each function makes available as an argument any value that the response may
84depend on, as evidenced in the API type. How do we get these functions? By calling
85the function `client`. It takes one argument:
87- a `Proxy` to your API,
93position :<|> hello :<|> marketing = client api
96`client api` returns client functions for our _entire_ API, combined with `:<|>`, which we can pattern match on as above. You could say `client` "calculates" the correct type and number of client functions for the API type it is given (via a `Proxy`), as well as their implementations.
98If you have an `EmptyAPI` in your API, servant-client will hand you a value of
99type `EmptyClient` in the corresponding slot, where `data EmptyClient =
100EmptyClient`, as a way to indicate that you can't do anything useful with it.
103type API' = API :<|> EmptyAPI
108(position' :<|> hello' :<|> marketing') :<|> EmptyClient = client api'
112-- | URI scheme to use
115 | Https -- ^ https://
118-- | Simple data type to represent the target of HTTP requests
119-- for servant's automatically-generated clients.
120data BaseUrl = BaseUrl
121 { baseUrlScheme :: Scheme -- ^ URI scheme to use
122 , baseUrlHost :: String -- ^ host (eg "haskell.org")
123 , baseUrlPort :: Int -- ^ port (eg 80)
124 , baseUrlPath :: String -- ^ path (eg "/a/b/c")
128That's it. Let's now write some code that uses our client functions.
131queries :: ClientM (Position, HelloMessage, Email)
133 pos <- position 10 10
134 message <- hello (Just "servant")
135 em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
136 return (pos, message, em)
140 manager' <- newManager defaultManagerSettings
141 res <- runClientM queries (mkClientEnv manager' (BaseUrl Http "localhost" 8081 ""))
143 Left err -> putStrLn $ "Error: " ++ show err
144 Right (pos, message, em) -> do
150Here's the output of the above code running against the appropriate server:
153Position {xCoord = 10, yCoord = 10}
154HelloMessage {msg = "Hello, servant"}
155Email {from = "great@company.com", to = "alp@foo.com", subject = "Hey Alp, we miss you!", body = "Hi Alp,\n\nSince you've recently turned 26, have you checked out our latest haskell, mathematics products? Give us a visit!"}
158The types of the arguments for the functions are the same as for (server-side) request handlers.
160## Changing the monad the client functions live in
162Just like `hoistServer` allows us to change the monad in which request handlers
163of a web application live in, we also have `hoistClient` for changing the monad
164in which _client functions_ live. Consider the following trivial API:
167type HoistClientAPI = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int
169hoistClientAPI :: Proxy HoistClientAPI
170hoistClientAPI = Proxy
173We already know how to derive client functions for this API, and as we have
174seen above they all return results in the `ClientM` monad when using `servant-client`.
175However, `ClientM` rarely (or never) is the actual monad we need to use the client
176functions in. Sometimes we need to run them in IO, sometimes in a custom monad
177stack. `hoistClient` is a very simple solution to the problem of "changing" the monad
182 :: HasClient ClientM api -- we need a valid API
183 => Proxy api -- a Proxy to the API type
184 -> (forall a. m a -> n a) -- a "monad conversion function" (natural transformation)
185 -> Client m api -- clients in the source monad
186 -> Client n api -- result: clients in the target monad
189The "conversion function" argument above, just like the ones given to `hoistServer`, must
190be able to turn an `m a` into an `n a` for any choice of type `a`.
192Let's see this in action on our example. We first derive our client functions as usual,
193with all of them returning a result in `ClientM`.
196getIntClientM :: ClientM Int
197postIntClientM :: Int -> ClientM Int
198getIntClientM :<|> postIntClientM = client hoistClientAPI
201And we finally decide that we want the handlers to run in IO instead, by
202"post-applying" `runClientM` to a fixed client environment.
205-- our conversion function has type: forall a. ClientM a -> IO a
206-- the result has type:
207-- Client IO HoistClientAPI = IO Int :<|> (Int -> IO Int)
208getClients :: ClientEnv -> Client IO HoistClientAPI
210 = hoistClient hoistClientAPI
211 ( fmap (either (error . show) id)
212 . flip runClientM clientEnv
214 (client hoistClientAPI)
217## Querying Streaming APIs.
219Consider the following streaming API type:
222type StreamAPI = "positionStream" :> StreamGet NewlineFraming JSON (SourceIO Position)
225Note that we use the same `SourceIO` type as on the server-side
226(this is different from `servant-0.14`).
228In any case, here's how we write a function to query our API:
231streamAPI :: Proxy StreamAPI
234posStream :: ClientM (Codensity IO (SourceIO Position))
235posStream = client streamAPI
238We'll get back a `Codensity IO (SourceIO Position)`. The wrapping in
239`Codensity` is generally necessary, as `Codensity` lets us `bracket` things
240properly. This is best explained by an example. To consume `ClientM (Codentity
241IO ...)` we can use `withClientM` helper: the underlying HTTP connection is
242open only until the inner functions exits. Inside the block we can e.g just
243print out all elements from a `SourceIO`, to give some idea of how to work with
247printSourceIO :: Show a => ClientEnv -> ClientM (Codensity IO (SourceIO a)) -> IO ()
248printSourceIO env c = withClientM c env $ \e -> case e of
249 Left err -> putStrLn $ "Error: " ++ show err
250 Right rs -> foreach fail print rs
253The stream is parsed and provided incrementally. So the above loop prints out
254each result as soon as it is received on the stream, rather than waiting until
255they are all available to print them at once.
257You now know how to use **servant-client**!
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list