2015-07-10 19:10:29 +02:00
|
|
|
.. _server-side-websocket-support-scala:
|
|
|
|
|
|
2015-05-11 23:05:18 +02:00
|
|
|
Server-Side WebSocket Support
|
|
|
|
|
=============================
|
|
|
|
|
|
2015-05-17 15:39:12 +02:00
|
|
|
WebSocket is a protocol that provides a bi-directional channel between browser and webserver usually run over an
|
|
|
|
|
upgraded HTTP(S) connection. Data is exchanged in messages whereby a message can either be binary data or unicode text.
|
|
|
|
|
|
|
|
|
|
Akka HTTP provides a stream-based implementation of the WebSocket protocol that hides the low-level details of the
|
|
|
|
|
underlying binary framing wire-protocol and provides a simple API to implement services using WebSocket.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Model
|
|
|
|
|
-----
|
|
|
|
|
|
|
|
|
|
The basic unit of data exchange in the WebSocket protocol is a message. A message can either be binary message,
|
|
|
|
|
i.e. a sequence of octets or a text message, i.e. a sequence of unicode code points.
|
|
|
|
|
|
|
|
|
|
Akka HTTP provides a straight-forward model for this abstraction:
|
|
|
|
|
|
|
|
|
|
.. includecode:: /../../akka-http-core/src/main/scala/akka/http/scaladsl/model/ws/Message.scala
|
|
|
|
|
:include: message-model
|
|
|
|
|
|
|
|
|
|
The data of a message is provided as a stream because WebSocket messages do not have a predefined size and could
|
|
|
|
|
(in theory) be infinitely long. However, only one message can be open per direction of the WebSocket connection,
|
|
|
|
|
so that many application level protocols will want to make use of the delineation into (small) messages to transport
|
|
|
|
|
single application-level data units like "one event" or "one chat message".
|
|
|
|
|
|
|
|
|
|
Many messages are small enough to be sent or received in one go. As an opportunity for optimization, the model provides
|
|
|
|
|
a ``Strict`` subclass for each kind of message which contains data as a strict, i.e. non-streamed, ``ByteString`` or
|
|
|
|
|
``String``.
|
|
|
|
|
|
2015-07-17 17:48:55 +02:00
|
|
|
When receiving data from the network connection the WebSocket implementation tries to create a ``Strict`` message whenever
|
|
|
|
|
possible, i.e. when the complete data was received in one chunk. However, the actual chunking of messages over a network
|
|
|
|
|
connection and through the various streaming abstraction layers is not deterministic from the perspective of the
|
|
|
|
|
application. Therefore, application code must be able to handle both streaming and strict messages and not expect
|
|
|
|
|
certain messages to be strict. (Particularly, note that tests against ``localhost`` will behave differently than tests
|
|
|
|
|
against remote peers where data is received over a physical network connection.)
|
|
|
|
|
|
|
|
|
|
For sending data, use ``TextMessage.apply(text: String)`` to create a ``Strict`` message which is often the natural
|
|
|
|
|
choice when the complete message has already been assembled. Otherwise, use ``TextMessage.apply(textStream: Source[String, Any])``
|
|
|
|
|
to create a streaming message from an Akka Stream source.
|
|
|
|
|
|
|
|
|
|
Server API
|
|
|
|
|
----------
|
|
|
|
|
|
|
|
|
|
The entrypoint for the Websocket API is the synthetic ``UpgradeToWebsocket`` header which is added to a request
|
|
|
|
|
if Akka HTTP encounters a Websocket upgrade request.
|
|
|
|
|
|
|
|
|
|
The Websocket specification mandates that details of the Websocket connection are negotiated by placing special-purpose
|
|
|
|
|
HTTP-headers into request and response of the HTTP upgrade. In Akka HTTP these HTTP-level details of the WebSocket
|
|
|
|
|
handshake are hidden from the application and don't need to be managed manually.
|
|
|
|
|
|
|
|
|
|
Instead, the synthetic ``UpgradeToWebsocket`` represents a valid Websocket upgrade request. An application can detect
|
|
|
|
|
a Websocket upgrade request by looking for the ``UpgradeToWebsocket`` header. It can choose to accept the upgrade and
|
|
|
|
|
start a Websocket connection by responding to that request with an ``HttpResponse`` generated by one of the
|
|
|
|
|
``UpgradeToWebsocket.handleMessagesWith`` methods. In its most general form this method expects two arguments:
|
|
|
|
|
first, a handler ``Flow[Message, Message, Any]`` that will be used to handle Websocket messages on this connection.
|
|
|
|
|
Second, the application can optionally choose one of the proposed application-level sub-protocols by inspecting the
|
|
|
|
|
values of ``UpgradeToWebsocket.requestedProtocols`` and pass the chosen protocol value to ``handleMessages``.
|
2015-05-17 15:39:12 +02:00
|
|
|
|
|
|
|
|
Handling Messages
|
|
|
|
|
+++++++++++++++++
|
|
|
|
|
|
|
|
|
|
A message handler is expected to be implemented as a ``Flow[Message, Message, Any]``. For typical request-response
|
|
|
|
|
scenarios this fits very well and such a ``Flow`` can be constructed from a simple function by using
|
|
|
|
|
``Flow[Message].map`` or ``Flow[Message].mapAsync``.
|
|
|
|
|
|
2015-07-17 17:48:55 +02:00
|
|
|
There are other use-cases, e.g. in a server-push model, where a server message is sent spontaneously, or in a
|
|
|
|
|
true bi-directional scenario where input and output aren't logically connected. Providing the handler as a ``Flow`` in
|
|
|
|
|
these cases may not fit. Another method, ``UpgradeToWebsocket.handleMessagesWithSinkSource``, is provided
|
|
|
|
|
which allows to pass an output-generating ``Source[Message, Any]`` and an input-receiving ``Sink[Message, Any]`` independently.
|
2015-05-17 15:39:12 +02:00
|
|
|
|
|
|
|
|
Note that a handler is required to consume the data stream of each message to make place for new messages. Otherwise,
|
|
|
|
|
subsequent messages may be stuck and message traffic in this direction will stall.
|
|
|
|
|
|
2015-07-17 17:48:55 +02:00
|
|
|
Example
|
|
|
|
|
+++++++
|
|
|
|
|
|
|
|
|
|
Let's look at an example_.
|
|
|
|
|
|
|
|
|
|
Websocket requests come in like any other requests. In the example, requests to ``/greeter`` are expected to be
|
|
|
|
|
Websocket requests:
|
|
|
|
|
|
|
|
|
|
.. includecode:: ../../code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|
|
|
|
|
:include: websocket-request-handling
|
|
|
|
|
|
|
|
|
|
It uses pattern matching on the path and then inspects the request to query for the ``UpgradeToWebsocket`` header. If
|
|
|
|
|
such a header is found, it is used to generate a response by passing a handler for Websocket messages to the
|
|
|
|
|
``handleMessages`` method. If no such header is found a "400 Bad Request" response is generated.
|
|
|
|
|
|
|
|
|
|
The passed handler expects text messages where each message is expected to contain (a person's) name
|
|
|
|
|
and then responds with another text message that contains a greeting:
|
|
|
|
|
|
|
|
|
|
.. includecode:: ../../code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|
|
|
|
|
:include: websocket-handler
|
|
|
|
|
|
2015-05-17 15:39:12 +02:00
|
|
|
Routing support
|
|
|
|
|
---------------
|
|
|
|
|
|
|
|
|
|
The routing DSL provides the :ref:`-handleWebsocketMessages-` directive to install a WebSocket handler if the request
|
|
|
|
|
was a WebSocket request. Otherwise, the directive rejects the request.
|
|
|
|
|
|
2015-07-17 17:48:55 +02:00
|
|
|
Here's the above simple request handler rewritten as a route:
|
|
|
|
|
|
2015-08-24 12:03:37 +02:00
|
|
|
.. includecode2:: ../../code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala
|
|
|
|
|
:snippet: greeter-service
|
|
|
|
|
|
|
|
|
|
The example also includes code demonstrating the testkit support for Websocket services. It allows to create Websocket
|
|
|
|
|
requests to run against a route using `WS` which can be used to provide a mock Websocket probe that allows manual
|
|
|
|
|
testing of the Websocket handler's behavior if the request was accepted.
|
|
|
|
|
|
2015-05-17 15:39:12 +02:00
|
|
|
|
2015-07-17 17:48:55 +02:00
|
|
|
.. _example: @github@/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|