3While defining handlers that [serve an API](Server.html) 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.html) 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)
25import qualified Servant.Client.Streaming as S
28Also, we need examples for some domain specific data types:
31data Position = Position
34 } deriving (Show, Generic)
36instance FromJSON Position
38newtype HelloMessage = HelloMessage { msg :: String }
39 deriving (Show, Generic)
41instance FromJSON HelloMessage
43data ClientInfo = ClientInfo
44 { clientName :: String
45 , clientEmail :: String
47 , clientInterestedIn :: [String]
50instance ToJSON ClientInfo
57 } deriving (Show, Generic)
59instance FromJSON Email
62Enough chitchat, let's see an example. Consider the following API type from the previous section:
65type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
66 :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
67 :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
70What we are going to get with **servant-client** here is three functions, one to query each endpoint:
73position :: Int -- ^ value for "x"
74 -> Int -- ^ value for "y"
77hello :: Maybe String -- ^ an optional value for "name"
78 -> ClientM HelloMessage
80marketing :: ClientInfo -- ^ value for the request body
84Each function makes available as an argument any value that the response may
85depend on, as evidenced in the API type. How do we get these functions? By calling
86the function `client`. It takes one argument:
88- a `Proxy` to your API,
94position :<|> hello :<|> marketing = client api
97`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.
99If you have an `EmptyAPI` in your API, servant-client will hand you a value of
100type `EmptyClient` in the corresponding slot, where `data EmptyClient =
101EmptyClient`, as a way to indicate that you can't do anything useful with it.
104type API' = API :<|> EmptyAPI
109(position' :<|> hello' :<|> marketing') :<|> EmptyClient = client api'
113-- | URI scheme to use
116 | Https -- ^ https://
119-- | Simple data type to represent the target of HTTP requests
120-- for servant's automatically-generated clients.
121data BaseUrl = BaseUrl
122 { baseUrlScheme :: Scheme -- ^ URI scheme to use
123 , baseUrlHost :: String -- ^ host (eg "haskell.org")
124 , baseUrlPort :: Int -- ^ port (eg 80)
125 , baseUrlPath :: String -- ^ path (eg "/a/b/c")
129That's it. Let's now write some code that uses our client functions.
132queries :: ClientM (Position, HelloMessage, Email)
134 pos <- position 10 10
135 message <- hello (Just "servant")
136 em <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])
137 return (pos, message, em)
141 manager' <- newManager defaultManagerSettings
142 res <- runClientM queries (mkClientEnv manager' (BaseUrl Http "localhost" 8081 ""))
144 Left err -> putStrLn $ "Error: " ++ show err
145 Right (pos, message, em) -> do
151Here's the output of the above code running against the appropriate server:
154Position {xCoord = 10, yCoord = 10}
155HelloMessage {msg = "Hello, servant"}
156Email {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!"}
159The types of the arguments for the functions are the same as for (server-side) request handlers.
161## Changing the monad the client functions live in
163Just like `hoistServer` allows us to change the monad in which request handlers
164of a web application live, we also have `hoistClient` for changing the monad
165in which _client functions_ live. Consider the following trivial API:
168type HoistClientAPI = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int
170hoistClientAPI :: Proxy HoistClientAPI
171hoistClientAPI = Proxy
174We already know how to derive client functions for this API, and as we have
175seen above they all return results in the `ClientM` monad when using `servant-client`.
176However, `ClientM` is rarely (or never) the actual monad we need to use the client
177functions in. Sometimes we need to run them in IO, sometimes in a custom monad
178stack. `hoistClient` is a very simple solution to the problem of "changing" the monad
183 :: HasClient ClientM api -- we need a valid API
184 => Proxy api -- a Proxy to the API type
185 -> (forall a. m a -> n a) -- a "monad conversion function" (natural transformation)
186 -> Client m api -- clients in the source monad
187 -> Client n api -- result: clients in the target monad
190The "conversion function" argument above, just like the ones given to `hoistServer`, must
191be able to turn an `m a` into an `n a` for any choice of type `a`.
193Let's see this in action on our example. We first derive our client functions as usual,
194with all of them returning a result in `ClientM`.
197getIntClientM :: ClientM Int
198postIntClientM :: Int -> ClientM Int
199getIntClientM :<|> postIntClientM = client hoistClientAPI
202And we finally decide that we want the handlers to run in IO instead, by
203"post-applying" `runClientM` to a fixed client environment.
206-- our conversion function has type: forall a. ClientM a -> IO a
207-- the result has type:
208-- Client IO HoistClientAPI = IO Int :<|> (Int -> IO Int)
209getClients :: ClientEnv -> Client IO HoistClientAPI
211 = hoistClient hoistClientAPI
212 ( fmap (either (error . show) id)
213 . flip runClientM clientEnv
215 (client hoistClientAPI)
218## Querying Streaming APIs.
220Consider the following streaming API type:
223type StreamAPI = "positionStream" :> StreamGet NewlineFraming JSON (SourceIO Position)
226Note that we use the same `SourceIO` type as on the server-side
227(this is different from `servant-0.14`).
228However, we have to use different client, `Servant.Client.Streaming`,
229which can stream (but has different API).
231In any case, here's how we write a function to query our API:
234streamAPI :: Proxy StreamAPI
237posStream :: S.ClientM (SourceIO Position)
238posStream = S.client streamAPI
241We'll get back a `SourceIO Position`. Instead of `runClientM`, the streaming
242client provides `withClientM`: the underlying HTTP connection is open only
243until the inner functions exits. Inside the block we can e.g just print out
244all elements from a `SourceIO`, to give some idea of how to work with them.
247printSourceIO :: Show a => ClientEnv -> S.ClientM (SourceIO a) -> IO ()
248printSourceIO env c = S.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