111 lines
6.2 KiB
ReStructuredText
111 lines
6.2 KiB
ReStructuredText
.. _json-streaming-scala:
|
|
|
|
Source Streaming
|
|
================
|
|
|
|
Akka HTTP supports completing a request with an Akka ``Source[T, _]``, which makes it possible to very easily build
|
|
streaming end-to-end APIs which apply back-pressure throughout the entire stack.
|
|
|
|
It is possible to complete requests with raw ``Source[ByteString, _]``, however often it is more convenient to
|
|
stream on an element-by-element basis, and allow Akka HTTP to handle the rendering internally - for example as a JSON array,
|
|
or CSV stream (where each element is separated by a new-line).
|
|
|
|
In the following sections we investigate how to make use of the JSON Streaming infrastructure,
|
|
however the general hints apply to any kind of element-by-element streaming you could imagine.
|
|
|
|
It is possible to implement your own framing for any content type you might need, including bianary formats
|
|
by implementing :class:`FramingWithContentType`.
|
|
|
|
JSON Streaming
|
|
==============
|
|
|
|
`JSON Streaming`_ is a term refering to streaming a (possibly infinite) stream of element as independent JSON
|
|
objects as a continuous HTTP request or response. The elements are most often separated using newlines,
|
|
however do not have to be. Concatenating elements side-by-side or emitting "very long" JSON array is also another
|
|
use case.
|
|
|
|
In the below examples, we'll be refering to the ``User`` and ``Measurement`` case classes as our model, which are defined as:
|
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/JsonStreamingExamplesSpec.scala
|
|
:snippet: models
|
|
|
|
And as always with spray-json, we provide our (Un)Marshaller instances as implicit values uding the ``jsonFormat##``
|
|
method to generate them statically:
|
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/JsonStreamingExamplesSpec.scala
|
|
:snippet: formats
|
|
|
|
.. _Json Streaming: https://en.wikipedia.org/wiki/JSON_Streaming
|
|
|
|
Responding with JSON Streams
|
|
----------------------------
|
|
|
|
In this example we implement an API representing an infinite stream of tweets, very much like Twitter's `Streaming API`_.
|
|
|
|
Firstly, we'll need to get some additional marshalling infrastructure set up, that is able to marshal to and from an
|
|
Akka Streams ``Source[T,_]``. One such trait, containing the needed marshallers is ``SprayJsonSupport``, which uses
|
|
spray-json (a high performance json parser library), and is shipped as part of Akka HTTP in the
|
|
``akka-http-spray-json-experimental`` module.
|
|
|
|
Next we import our model's marshallers, generated by spray-json.
|
|
|
|
The last bit of setup, before we can render a streaming json response
|
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/JsonStreamingExamplesSpec.scala
|
|
:snippet: spray-json-response-streaming
|
|
|
|
.. _Streaming API: https://dev.twitter.com/streaming/overview
|
|
|
|
Customising response rendering mode
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
The mode in which a response is marshalled and then rendered to the HttpResponse from the provided ``Source[T,_]``
|
|
is customisable (thanks to conversions originating from ``Directives`` via ``EntityStreamingDirectives``).
|
|
|
|
Since Marshalling is a potentially asynchronous operation in Akka HTTP (because transforming ``T`` to ``JsValue`` may
|
|
potentially take a long time (depending on your definition of "long time"), we allow to run marshalling concurrently
|
|
(up to ``parallelism`` concurrent marshallings) by using the ``renderAsync(parallelism)`` mode:
|
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/JsonStreamingExamplesSpec.scala
|
|
:snippet: async-rendering
|
|
|
|
The ``renderAsync`` mode perserves ordering of the Source's elements, which may sometimes be a required property,
|
|
for example when streaming a strictly ordered dataset. Sometimes the contept of strict-order does not apply to the
|
|
data being streamed though, which allows us to explit this property and use ``renderAsyncUnordered(parallelism)``,
|
|
which will concurrently marshall up to ``parallelism`` elements and emit the first which is marshalled onto
|
|
the HttpResponse:
|
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/JsonStreamingExamplesSpec.scala
|
|
:snippet: async-unordered-rendering
|
|
|
|
This allows us to _potentially_ render elements faster onto the HttpResponse, since it can avoid "head of line blocking",
|
|
in case one element in front of the stream takes a long time to marshall, yet others after it are very quick to marshall.
|
|
|
|
Consuming JSON Streaming uploads
|
|
--------------------------------
|
|
|
|
Sometimes the client may be sending a streaming request, for example an embedded device initiated a connection with
|
|
the server and is feeding it with one line of measurement data.
|
|
|
|
In this example, we want to consume this data in a streaming fashion from the request entity, and also apply
|
|
back-pressure to the underlying TCP connection, if the server can not cope with the rate of incoming data (back-pressure
|
|
will be applied automatically thanks to using Akka HTTP/Streams).
|
|
|
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/JsonStreamingExamplesSpec.scala
|
|
:snippet: spray-json-request-streaming
|
|
|
|
Implementing custom (Un)Marshaller support for JSON streaming
|
|
-------------------------------------------------------------
|
|
|
|
While not provided by Akka HTTP directly, the infrastructure is extensible and by investigating how ``SprayJsonSupport``
|
|
is implemented it is certainly possible to provide the same infrastructure for other marshaller implementations (such as
|
|
Play JSON, or Jackson directly for example). Such support traits will want to extend the ``JsonEntityStreamingSupport`` trait.
|
|
|
|
The following types that may need to be implemented by a custom framed-streaming support library are:
|
|
|
|
- ``SourceRenderingMode`` which can customise how to render the begining / between-elements and ending of such stream (while writing a response, i.e. by calling ``complete(source)``).
|
|
Implementations for JSON are available in ``akka.http.scaladsl.server.JsonSourceRenderingMode``.
|
|
- ``FramingWithContentType`` which is needed to be able to split incoming ``ByteString`` chunks into frames
|
|
of the higher-level data type format that is understood by the provided unmarshallers.
|
|
In the case of JSON it means chunking up ByteStrings such that each emitted element corresponds to exactly one JSON object,
|
|
this framing is implemented in ``JsonEntityStreamingSupport``.
|