docs for streaming (#852)

* docs for new streaming related combinators
This commit is contained in:
gbaz 2017-12-11 15:32:17 -05:00 committed by Alp Mestanogullari
parent 6fe2c78567
commit 7edd35c9f0
3 changed files with 82 additions and 4 deletions

View file

@ -127,6 +127,19 @@ type UserAPI4 = "users" :> Get '[JSON] [User]
:<|> "admins" :> Get '[JSON] [User]
```
### `StreamGet` and `StreamPost`
The `StreamGet` and `StreamPost` combinators are defined in terms of the more general `Stream`
``` haskell ignore
data Stream (method :: k1) (framing :: *) (contentType :: *) a
type StreamGet = Stream 'GET
type StreamPost = Stream 'POST
```
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.
### `Capture`
URL captures are segments of the path of a URL that are variable and whose actual value is

View file

@ -153,4 +153,48 @@ HelloMessage {msg = "Hello, servant"}
Email {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!"}
```
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**!
The types of the arguments for the functions are the same as for (server-side) request handlers.
## Querying Streaming APIs.
Consider the following streaming API type:
``` haskell
type StreamAPI = "positionStream" :> StreamGet NewlineFraming JSON (ResultStream Position)
```
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:
``` haskell ignore
type StreamAPI f = "positionStream" :> StreamGet NewlineFraming JSON (f Position)
type ServerStreamAPI = StreamAPI StreamGenerator
type ClientStreamAPI = StreamAPI ResultStream
```
In any case, here's how we write a function to query our API:
``` haskell
streamAPI :: Proxy StreamAPI
streamAPI = Proxy
posStream :: ClientM (ResultStream Position)
posStream = client streamAPI
```
And here's how to just print out all elements from a `ResultStream`, to give some idea of how to work with them.
``` haskell
printResultStream :: Show a => ResultStream a -> IO ()
printResultStream (ResultStream k) = k $ \getResult ->
let loop = do
r <- getResult
case r of
Nothing -> return ()
Just x -> print x >> loop
in loop
```
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.
You now know how to use **servant-client**!

View file

@ -204,7 +204,7 @@ And that's it! You can run this example in the same way that we showed for
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 occurence of these
`Capture` and `ReqBody` right away. You'll see how each occurrence of these
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
@ -1092,7 +1092,7 @@ We can write some simple webservice with the handlers running in `Reader String`
``` haskell
type ReaderAPI = "a" :> Get '[JSON] Int
:<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
:<|> "b" :> ReqBody '[JSON] Double :> Get '[JSON] Bool
readerAPI :: Proxy ReaderAPI
readerAPI = Proxy
@ -1114,7 +1114,7 @@ We unfortunately can't use `readerServerT` as an argument of `serve`, because
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
right type for `serve`. Being cumbersome to do by hand, we provide a function
`hoistServer` which takes a natural transformation between two parametrized types `m`
`hoistServer` which takes a natural transformation between two parameterized types `m`
and `n` and a `ServerT someapi m`, and returns a `ServerT someapi n`.
In our case, we can wrap up our little webservice by using
@ -1164,6 +1164,27 @@ 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 `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.
``` haskell
type StreamAPI = "userStream" :> StreamGet NewlineFraming JSON (StreamGenerator User)
streamAPI :: Proxy StreamAPI
streamAPI = Proxy
streamUsers :: StreamGenerator User
streamUsers = StreamGenerator $ \sendFirst sendRest -> do
sendFirst isaac
sendRest albert
sendRest 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 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.
## Conclusion
You're now equipped to write webservices/web-applications using