2015-07-10 19:10:29 +02:00
|
|
|
.. _server-side-websocket-support-java:
|
|
|
|
|
|
|
|
|
|
Server-Side WebSocket Support
|
|
|
|
|
=============================
|
|
|
|
|
|
2015-07-17 17:48:55 +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.
|
2015-07-10 19:10:29 +02:00
|
|
|
|
2015-07-17 17:48:55 +02:00
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
In the data model the two kinds of messages, binary and text messages, are represented by the two classes
|
|
|
|
|
``BinaryMessage`` and ``TextMessage`` deriving from a common superclass ``Message``. The superclass ``Message``
|
|
|
|
|
contains ``isText`` and ``isBinary`` methods to distinguish a message and ``asBinaryMessage`` and ``asTextMessage``
|
|
|
|
|
methods to cast a message.
|
|
|
|
|
|
|
|
|
|
The subclasses ``BinaryMessage`` and ``TextMessage`` contain methods to access the data. Take the API of
|
|
|
|
|
``TextMessage`` as an example (``BinaryMessage`` is very similar with ``String`` replaced by ``ByteString``):
|
|
|
|
|
|
|
|
|
|
.. includecode:: /../../akka-http-core/src/main/scala/akka/http/javadsl/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
|
|
|
|
|
the notion of a "strict" message to represent cases where a whole message was received in one go. If
|
|
|
|
|
``TextMessage.isStrict`` returns true, the complete data is already available and can be accessed with
|
|
|
|
|
``TextMessage.getStrictText`` (analogously for ``BinaryMessage``).
|
|
|
|
|
|
|
|
|
|
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, you can use the static ``TextMessage.create(String)`` method to create a strict message if the
|
|
|
|
|
complete message has already been assembled. Otherwise, use ``TextMessage.create(Source<String, ?>)`` to create
|
|
|
|
|
a streaming message from an Akka Stream source.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Server API
|
|
|
|
|
----------
|
|
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
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.
|
2015-07-17 17:48:55 +02:00
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
The WebSocket specification mandates that details of the WebSocket connection are negotiated by placing special-purpose
|
2015-07-17 17:48:55 +02:00
|
|
|
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.
|
|
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
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, ?>`` that will be used to handle WebSocket messages on this connection.
|
2015-07-17 17:48:55 +02:00
|
|
|
Second, the application can optionally choose one of the proposed application-level sub-protocols by inspecting the
|
2016-01-19 20:43:59 +01:00
|
|
|
values of ``UpgradeToWebSocket.getRequestedProtocols`` and pass the chosen protocol value to ``handleMessagesWith``.
|
2015-07-17 17:48:55 +02:00
|
|
|
|
|
|
|
|
Handling Messages
|
|
|
|
|
+++++++++++++++++
|
|
|
|
|
|
|
|
|
|
A message handler is expected to be implemented as a ``Flow<Message, Message, ?>``. For typical request-response
|
|
|
|
|
scenarios this fits very well and such a ``Flow`` can be constructed from a simple function by using
|
|
|
|
|
``Flow.<Message>create().map`` or ``Flow.<Message>create().mapAsync``.
|
|
|
|
|
|
|
|
|
|
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
|
2016-01-19 20:43:59 +01:00
|
|
|
these cases may not fit. An overload of ``UpgradeToWebSocket.handleMessagesWith`` is provided, instead,
|
2015-07-17 17:48:55 +02:00
|
|
|
which allows to pass an output-generating ``Source<Message, ?>`` and an input-receiving ``Sink<Message, ?>`` independently.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
Example
|
|
|
|
|
+++++++
|
|
|
|
|
|
|
|
|
|
Let's look at an example_.
|
|
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
WebSocket requests come in like any other requests. In the example, requests to ``/greeter`` are expected to be
|
|
|
|
|
WebSocket requests:
|
2015-07-17 17:48:55 +02:00
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
.. includecode:: ../../code/docs/http/javadsl/server/WebSocketCoreExample.java
|
2015-07-17 17:48:55 +02:00
|
|
|
:include: websocket-handling
|
|
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
It uses a helper method ``akka.http.javadsl.model.ws.WebSocket.handleWebSocketRequestWith`` which can be used if
|
|
|
|
|
only WebSocket requests are expected. The method looks for the ``UpgradeToWebSocket`` header and returns a response
|
|
|
|
|
that will install the passed WebSocket handler if the header is found. If the request is no WebSocket request it will
|
2015-07-17 17:48:55 +02:00
|
|
|
return a ``400 Bad Request`` error response.
|
|
|
|
|
|
|
|
|
|
In the example, 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:
|
|
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
.. includecode:: ../../code/docs/http/javadsl/server/WebSocketCoreExample.java
|
2015-07-17 17:48:55 +02:00
|
|
|
:include: websocket-handler
|
|
|
|
|
|
|
|
|
|
Routing support
|
|
|
|
|
---------------
|
|
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
The routing DSL provides the ``handleWebSocketMessages`` directive to install a WebSocket handler if a request
|
2015-07-17 17:48:55 +02:00
|
|
|
is a WebSocket request. Otherwise, the directive rejects the request.
|
|
|
|
|
|
|
|
|
|
Let's look at how the above example can be rewritten using the high-level routing DSL.
|
|
|
|
|
|
|
|
|
|
Instead of writing the request handler manually, the routing behavior of the app is defined by a route that
|
2016-01-19 20:43:59 +01:00
|
|
|
uses the ``handleWebSocketRequests`` directive in place of the ``WebSocket.handleWebSocketRequestWith``:
|
2015-07-17 17:48:55 +02:00
|
|
|
|
2016-01-19 20:43:59 +01:00
|
|
|
.. includecode:: ../../code/docs/http/javadsl/server/WebSocketRoutingExample.java
|
2015-07-17 17:48:55 +02:00
|
|
|
:include: websocket-route
|
|
|
|
|
|
|
|
|
|
The handling code itself will be the same as with using the low-level API.
|
|
|
|
|
|
|
|
|
|
See the `full routing example`_.
|
|
|
|
|
|
2016-04-24 20:05:14 -07:00
|
|
|
.. _example: @github@/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketCoreExample.java
|
|
|
|
|
.. _full routing example: @github@/akka-docs/rst/java/code/docs/http/javadsl/server/WebSocketCoreExample.java
|