git.iko.soy/typeable/servant
on master help sign in
docs for streaming (#852)

* docs for new streaming related combinators

commit7edd35c9f0c401f8cff5b9f1fb1a2c9bbcec3b90 authorgbaz <gershomb@gmail.com> dateMon 11 Dec 2017, 15:32 (9 years ago) parent6fe2c78
3 files changed+82−4
doc/tutorial/ApiType.lhs+13 doc/tutorial/Client.lhs+45−1 doc/tutorial/Server.lhs+24−3
doc/tutorial/ApiType.lhs+13
@@ -127,6 +127,19 @@ type UserAPI4 = "users" :> Get '[JSON] [User]
127127 :<|> "admins" :> Get '[JSON] [User]
128128```
129129
130+### `StreamGet` and `StreamPost`
131+
132+The `StreamGet` and `StreamPost` combinators are defined in terms of the more general `Stream`
133+
134+``` haskell ignore
135+data Stream (method :: k1) (framing :: *) (contentType :: *) a
136+type StreamGet = Stream 'GET
137+type StreamPost = Stream 'POST
138+```
139+
140+These describe endpoints that return a stream of values rather than just a single value. They not only take a single content type as a paremeter, but also a framing strategy -- this specifies how the individual results are deliniated from one another in the stream. The two standard strategies given with Servant are `NewlineFraming` and `NetstringFraming`, but others can be written to match other protocols.
141+
142+
130143### `Capture`
131144
132145URL captures are segments of the path of a URL that are variable and whose actual value is
doc/tutorial/Client.lhs+45−1
@@ -153,4 +153,48 @@ HelloMessage {msg = "Hello, servant"}
153153Email {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!"}
154154```
155155
156−The types of the arguments for the functions are the same as for (server-side) request handlers. You now know how to use **servant-client**!
156+The types of the arguments for the functions are the same as for (server-side) request handlers.
157+
158+## Querying Streaming APIs.
159+
160+Consider the following streaming API type:
161+
162+``` haskell
163+type StreamAPI = "positionStream" :> StreamGet NewlineFraming JSON (ResultStream Position)
164+```
165+
166+Note that when we declared an API to serve, we specified a `StreamGenerator` as a producer of streams. Now we specify our result type as a `ResultStream`. With types that can be used both ways, if appropriate adaptors are written (in the form of `ToStreamGenerator` and `BuildFromStream` instances), then this asymmetry isn't necessary. Otherwise, if you want to share the same API across clients and servers, you can parameterize it like so:
167+
168+``` haskell ignore
169+type StreamAPI f = "positionStream" :> StreamGet NewlineFraming JSON (f Position)
170+type ServerStreamAPI = StreamAPI StreamGenerator
171+type ClientStreamAPI = StreamAPI ResultStream
172+```
173+
174+In any case, here's how we write a function to query our API:
175+
176+``` haskell
177+streamAPI :: Proxy StreamAPI
178+streamAPI = Proxy
179+
180+posStream :: ClientM (ResultStream Position)
181+
182+posStream = client streamAPI
183+```
184+
185+And here's how to just print out all elements from a `ResultStream`, to give some idea of how to work with them.
186+
187+``` haskell
188+printResultStream :: Show a => ResultStream a -> IO ()
189+printResultStream (ResultStream k) = k $ \getResult ->
190+ let loop = do
191+ r <- getResult
192+ case r of
193+ Nothing -> return ()
194+ Just x -> print x >> loop
195+ in loop
196+```
197+
198+The stream is parsed and provided incrementally. So the above loop prints out each result as soon as it is received on the stream, rather than waiting until they are all available to print them at once.
199+
200+You now know how to use **servant-client**!
doc/tutorial/Server.lhs+24−3
@@ -204,7 +204,7 @@ And that's it! You can run this example in the same way that we showed for
204204
205205Fine, we can write trivial webservices easily, but none of the two above use
206206any "fancy" combinator from servant. Let's address this and use `QueryParam`,
207−`Capture` and `ReqBody` right away. You'll see how each occurence of these
207+`Capture` and `ReqBody` right away. You'll see how each occurrence of these
208208combinators in an endpoint makes the corresponding handler receive an
209209argument of the appropriate type automatically. You don't have to worry about
210210manually looking up URL captures or query string parameters, or
@@ -1092,7 +1092,7 @@ We can write some simple webservice with the handlers running in `Reader String`
10921092
10931093``` haskell
10941094type ReaderAPI = "a" :> Get '[JSON] Int
1095− :<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
1095+ :<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
10961096
10971097readerAPI :: Proxy ReaderAPI
10981098readerAPI = Proxy
@@ -1114,7 +1114,7 @@ We unfortunately can't use `readerServerT` as an argument of `serve`, because
11141114That's right. We have just written `readerToHandler`, which is exactly what we
11151115would need to apply to all handlers to make the handlers have the
11161116right type for `serve`. Being cumbersome to do by hand, we provide a function
1117−`hoistServer` which takes a natural transformation between two parametrized types `m`
1117+`hoistServer` which takes a natural transformation between two parameterized types `m`
11181118and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
11191119
11201120In our case, we can wrap up our little webservice by using
@@ -1164,6 +1164,27 @@ app5 :: Application
11641164app5 = serve readerAPI (hoistServer readerAPI funToHandler funServerT)
11651165```
11661166
1167+## Streaming endpoints
1168+
1169+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 `StreamGenerator`s. StreamGenerators are IO-based continuations that are handed two functions -- the first to write the first result back, and the second to write all subsequent results back. (This is to allow handling of situations where the entire stream is prefixed by a header, or where a boundary is written between elements, but not prior to the first element). 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.
1170+
1171+``` haskell
1172+type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (StreamGenerator User)
1173+streamAPI :: Proxy StreamAPI
1174+streamAPI = Proxy
1175+
1176+streamUsers :: StreamGenerator User
1177+streamUsers = StreamGenerator $ \sendFirst sendRest -> do
1178+ sendFirst isaac
1179+ sendRest albert
1180+ sendRest albert
1181+
1182+app6 :: Application
1183+app6 = serve streamAPI (return streamUsers)
1184+```
1185+
1186+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 third 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.
1187+
11671188## Conclusion
11681189
11691190You're now equipped to write webservices/web-applications using