Merge pull request #18029 from spray/w/java-side-documentation
+doc #18012 add Java-side Server Websocket documentation
This commit is contained in:
commit
d27679ac99
9 changed files with 396 additions and 62 deletions
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#websocket-example-using-core
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import akka.japi.Function;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
import akka.http.javadsl.model.ws.Websocket;
|
||||
|
||||
public class WebsocketCoreExample {
|
||||
//#websocket-handling
|
||||
public static HttpResponse handleRequest(HttpRequest request) {
|
||||
System.out.println("Handling request to " + request.getUri());
|
||||
|
||||
if (request.getUri().path().equals("/greeter"))
|
||||
return Websocket.handleWebsocketRequestWith(request, greeter());
|
||||
else
|
||||
return HttpResponse.create().withStatus(404);
|
||||
}
|
||||
//#websocket-handling
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ActorSystem system = ActorSystem.create();
|
||||
|
||||
try {
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
Http.get(system).bindAndHandleSync(
|
||||
new Function<HttpRequest, HttpResponse>() {
|
||||
public HttpResponse apply(HttpRequest request) throws Exception {
|
||||
return handleRequest(request);
|
||||
}
|
||||
}, "localhost", 8080, materializer);
|
||||
|
||||
// will throw if binding fails
|
||||
Await.result(serverBindingFuture, new FiniteDuration(1, TimeUnit.SECONDS));
|
||||
System.out.println("Press ENTER to stop.");
|
||||
new BufferedReader(new InputStreamReader(System.in)).readLine();
|
||||
} finally {
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
//#websocket-handler
|
||||
/**
|
||||
* A handler that treats incoming messages as a name,
|
||||
* and responds with a greeting to that name
|
||||
*/
|
||||
public static Flow<Message, Message, BoxedUnit> greeter() {
|
||||
return
|
||||
Flow.<Message>create()
|
||||
.collect(new JavaPartialFunction<Message, Message>() {
|
||||
@Override
|
||||
public Message apply(Message msg, boolean isCheck) throws Exception {
|
||||
if (isCheck)
|
||||
if (msg.isText()) return null;
|
||||
else throw noMatch();
|
||||
else
|
||||
return handleTextMessage(msg.asTextMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
public static TextMessage handleTextMessage(TextMessage msg) {
|
||||
if (msg.isStrict()) // optimization that directly creates a simple response...
|
||||
return TextMessage.create("Hello "+msg.getStrictText());
|
||||
else // ... this would suffice to handle all text messages in a streaming fashion
|
||||
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
|
||||
}
|
||||
//#websocket-handler
|
||||
}
|
||||
//#websocket-example-using-core
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.server.Route;
|
||||
|
||||
import akka.japi.JavaPartialFunction;
|
||||
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
|
||||
import akka.http.javadsl.server.HttpApp;
|
||||
|
||||
public class WebsocketRoutingExample extends HttpApp {
|
||||
//#websocket-route
|
||||
@Override
|
||||
public Route createRoute() {
|
||||
return
|
||||
path("greeter").route(
|
||||
handleWebsocketMessages(greeter())
|
||||
);
|
||||
}
|
||||
//#websocket-route
|
||||
|
||||
/**
|
||||
* A handler that treats incoming messages as a name,
|
||||
* and responds with a greeting to that name
|
||||
*/
|
||||
public static Flow<Message, Message, Object> greeter() {
|
||||
return
|
||||
upcastMaterializerToObject(Flow.<Message>create())
|
||||
.collect(new JavaPartialFunction<Message, Message>() {
|
||||
@Override
|
||||
public Message apply(Message msg, boolean isCheck) throws Exception {
|
||||
if (isCheck)
|
||||
if (msg.isText()) return null;
|
||||
else throw noMatch();
|
||||
else
|
||||
return handleTextMessage(msg.asTextMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
public static TextMessage handleTextMessage(TextMessage msg) {
|
||||
if (msg.isStrict()) // optimization that directly creates a simple response...
|
||||
return TextMessage.create("Hello "+msg.getStrictText());
|
||||
else // ... this would suffice to handle all text messages in a streaming fashion
|
||||
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
|
||||
}
|
||||
|
||||
// needed because of #18028, see https://github.com/akka/akka/issues/18028
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, U> Flow<T, U, Object> upcastMaterializerToObject(Flow< T, U, ?> flow) {
|
||||
return (Flow<T, U, Object>) flow;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,39 @@
|
|||
Client-Side HTTPS Support
|
||||
=========================
|
||||
|
||||
TODO
|
||||
Akka HTTP supports TLS encryption on the client-side as well as on the :ref:`server-side <serverSideHTTPS-java>`.
|
||||
|
||||
For the time being, :ref:`see the Scala chapter on the same topic <clientSideHTTPS>`.
|
||||
The central vehicle for configuring encryption is the ``HttpsContext``, which can be created using
|
||||
the static method ``HttpsContext.create`` which is defined like this:
|
||||
|
||||
.. includecode:: /../../akka-http-core/src/main/java/akka/http/javadsl/HttpsContext.java
|
||||
:include: http-context-creation
|
||||
|
||||
In addition to the ``outgoingConnection``, ``newHostConnectionPool`` and ``cachedHostConnectionPool`` methods the
|
||||
`akka.http.javadsl.Http`_ extension also defines ``outgoingConnectionTls``, ``newHostConnectionPoolTls`` and
|
||||
``cachedHostConnectionPoolTls``. These methods work identically to their counterparts without the ``-Tls`` suffix,
|
||||
with the exception that all connections will always be encrypted.
|
||||
|
||||
The ``singleRequest`` and ``superPool`` methods determine the encryption state via the scheme of the incoming request,
|
||||
i.e. requests to an "https" URI will be encrypted, while requests to an "http" URI won't.
|
||||
|
||||
The encryption configuration for all HTTPS connections, i.e. the ``HttpsContext`` is determined according to the
|
||||
following logic:
|
||||
|
||||
1. If the optional ``httpsContext`` method parameter is defined it contains the configuration to be used (and thus
|
||||
takes precedence over any potentially set default client-side ``HttpsContext``).
|
||||
|
||||
2. If the optional ``httpsContext`` method parameter is undefined (which is the default) the default client-side
|
||||
``HttpsContext`` is used, which can be set via the ``setDefaultClientHttpsContext`` on the ``Http`` extension.
|
||||
|
||||
3. If no default client-side ``HttpsContext`` has been set via the ``setDefaultClientHttpsContext`` on the ``Http``
|
||||
extension the default system configuration is used.
|
||||
|
||||
Usually the process is, if the default system TLS configuration is not good enough for your application's needs,
|
||||
that you configure a custom ``HttpsContext`` instance and set it via ``Http.get(system).setDefaultClientHttpsContext``.
|
||||
Afterwards you simply use ``outgoingConnectionTls``, ``newHostConnectionPoolTls``, ``cachedHostConnectionPoolTls``,
|
||||
``superPool`` or ``singleRequest`` without a specific ``httpsContext`` argument, which causes encrypted connections
|
||||
to rely on the configured default client-side ``HttpsContext``.
|
||||
|
||||
|
||||
.. _akka.http.javadsl.Http: @github@/akka-http-core/src/main/scala/akka/http/javadsl/Http.scala
|
||||
|
|
|
|||
|
|
@ -138,8 +138,8 @@ Server-Side HTTPS Support
|
|||
|
||||
Akka HTTP supports TLS encryption on the server-side as well as on the :ref:`client-side <clientSideHTTPS-java>`.
|
||||
|
||||
The central vehicle for configuring encryption is the ``HttpsContext``, which can be created using ``HttpsContext.create``
|
||||
which is defined like this:
|
||||
The central vehicle for configuring encryption is the ``HttpsContext``, which can be created using
|
||||
the static method ``HttpsContext.create`` which is defined like this:
|
||||
|
||||
.. includecode:: /../../akka-http-core/src/main/java/akka/http/javadsl/HttpsContext.java
|
||||
:include: http-context-creation
|
||||
|
|
|
|||
|
|
@ -3,6 +3,124 @@
|
|||
Server-Side WebSocket Support
|
||||
=============================
|
||||
|
||||
TODO
|
||||
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.
|
||||
|
||||
For the time being, :ref:`see the Scala chapter on the same topic <server-side-websocket-support-scala>`.
|
||||
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
|
||||
----------
|
||||
|
||||
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, ?>`` 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.getRequestedProtocols`` and pass the chosen protocol value to ``handleMessagesWith``.
|
||||
|
||||
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
|
||||
these cases may not fit. An overload of ``UpgradeToWebsocket.handleMessagesWith`` is provided, instead,
|
||||
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_.
|
||||
|
||||
Websocket requests come in like any other requests. In the example, requests to ``/greeter`` are expected to be
|
||||
Websocket requests:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/server/WebsocketCoreExample.java
|
||||
:include: websocket-handling
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/server/WebsocketCoreExample.java
|
||||
:include: websocket-handler
|
||||
|
||||
Routing support
|
||||
---------------
|
||||
|
||||
The routing DSL provides the ``handleWebsocketMessages`` directive to install a WebSocket handler if a request
|
||||
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
|
||||
uses the ``handleWebsocketRequests`` directive in place of the ``Websocket.handleWebsocketRequestWith``:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/server/WebsocketRoutingExample.java
|
||||
:include: websocket-route
|
||||
|
||||
The handling code itself will be the same as with using the low-level API.
|
||||
|
||||
See the `full routing example`_.
|
||||
|
||||
.. _example: @github@/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketCoreExample.java
|
||||
.. _full routing example: @github@/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketRoutingExample.java
|
||||
|
|
@ -22,6 +22,7 @@ class WebsocketExampleSpec extends WordSpec with Matchers {
|
|||
implicit val system = ActorSystem()
|
||||
implicit val materializer = ActorMaterializer()
|
||||
|
||||
//#websocket-handler
|
||||
// The Greeter WebSocket Service expects a "name" per message and
|
||||
// returns a greeting message for that name
|
||||
val greeterWebsocketService =
|
||||
|
|
@ -34,16 +35,21 @@ class WebsocketExampleSpec extends WordSpec with Matchers {
|
|||
case tm: TextMessage ⇒ TextMessage(Source.single("Hello ") ++ tm.textStream)
|
||||
// ignore binary messages
|
||||
}
|
||||
//#websocket-handler
|
||||
|
||||
val bindingFuture = Http().bindAndHandleSync({
|
||||
case req @ HttpRequest(GET, Uri.Path("/ws-greeter"), _, _, _) ⇒
|
||||
//#websocket-request-handling
|
||||
val requestHandler: HttpRequest ⇒ HttpResponse = {
|
||||
case req @ HttpRequest(GET, Uri.Path("/greeter"), _, _, _) ⇒
|
||||
req.header[UpgradeToWebsocket] match {
|
||||
case Some(upgrade) ⇒ upgrade.handleMessages(greeterWebsocketService)
|
||||
case None ⇒ HttpResponse(400, entity = "Not a valid websocket request!")
|
||||
}
|
||||
case _: HttpRequest ⇒ HttpResponse(404, entity = "Unknown resource!")
|
||||
}, interface = "localhost", port = 8080)
|
||||
//#websocket-example-using-core
|
||||
}
|
||||
//#websocket-request-handling
|
||||
|
||||
val bindingFuture =
|
||||
Http().bindAndHandleSync(requestHandler, interface = "localhost", port = 8080)
|
||||
|
||||
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
|
||||
Console.readLine()
|
||||
|
|
@ -55,7 +61,6 @@ class WebsocketExampleSpec extends WordSpec with Matchers {
|
|||
}
|
||||
"routing-example" in {
|
||||
pending // compile-time only test
|
||||
//#websocket-example-using-routing
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.ActorMaterializer
|
||||
import akka.stream.scaladsl.{ Source, Flow }
|
||||
|
|
@ -77,12 +82,14 @@ class WebsocketExampleSpec extends WordSpec with Matchers {
|
|||
// ignore binary messages
|
||||
}
|
||||
|
||||
//#websocket-routing
|
||||
val route =
|
||||
path("ws-greeter") {
|
||||
path("greeter") {
|
||||
get {
|
||||
handleWebsocketMessages(greeterWebsocketService)
|
||||
}
|
||||
}
|
||||
//#websocket-routing
|
||||
|
||||
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
|
||||
|
||||
|
|
@ -93,6 +100,5 @@ class WebsocketExampleSpec extends WordSpec with Matchers {
|
|||
bindingFuture
|
||||
.flatMap(_.unbind()) // trigger unbinding from the port
|
||||
.onComplete(_ ⇒ system.shutdown()) // and shutdown when done
|
||||
//#websocket-example-using-routing
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ i.e. requests to an "https" URI will be encrypted, while requests to an "http" U
|
|||
The encryption configuration for all HTTPS connections, i.e. the ``HttpsContext`` is determined according to the
|
||||
following logic:
|
||||
|
||||
1. If the optional ``httpContext`` method parameter is defined it contains the configuration to be used (and thus
|
||||
1. If the optional ``httpsContext`` method parameter is defined it contains the configuration to be used (and thus
|
||||
takes precedence over any potentially set default client-side ``HttpsContext``).
|
||||
|
||||
2. If the optional ``httpContext`` method parameter is undefined (which is the default) the default client-side
|
||||
2. If the optional ``httpsContext`` method parameter is undefined (which is the default) the default client-side
|
||||
``HttpsContext`` is used, which can be set via the ``setDefaultClientHttpsContext`` on the ``Http`` extension.
|
||||
|
||||
3. If no default client-side ``HttpsContext`` has been set via the ``setDefaultClientHttpsContext`` on the ``Http``
|
||||
|
|
@ -33,7 +33,7 @@ following logic:
|
|||
Usually the process is, if the default system TLS configuration is not good enough for your application's needs,
|
||||
that you configure a custom ``HttpsContext`` instance and set it via ``Http().setDefaultClientHttpsContext``.
|
||||
Afterwards you simply use ``outgoingConnectionTls``, ``newHostConnectionPoolTls``, ``cachedHostConnectionPoolTls``,
|
||||
``superPool`` or ``singleRequest`` without a specific ``httpContext`` argument, which causes encrypted connections
|
||||
``superPool`` or ``singleRequest`` without a specific ``httpsContext`` argument, which causes encrypted connections
|
||||
to rely on the configured default client-side ``HttpsContext``.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,37 +30,34 @@ Many messages are small enough to be sent or received in one go. As an opportuni
|
|||
a ``Strict`` subclass for each kind of message which contains data as a strict, i.e. non-streamed, ``ByteString`` or
|
||||
``String``.
|
||||
|
||||
For sending data, the ``Strict`` variant is often the natural choice when the complete message has already been
|
||||
assembled. While 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 from when data is received over a physical network connection.)
|
||||
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.
|
||||
|
||||
Core-level support
|
||||
------------------
|
||||
Server API
|
||||
----------
|
||||
|
||||
On the server-side a request to upgrade the connection to WebSocket is provided through a special header that is added
|
||||
to a request by the implementation. Whenever a request contains the synthetic
|
||||
``akka.http.scaldsl.model.ws.UpgradeToWebsocket``-header an HTTP request was a valid WebSocket upgrade request.
|
||||
Methods on this header can be used to create a response that will upgrade the connection to a WebSocket connection and
|
||||
install a ``Flow`` to handle WebSocket traffic on this connection.
|
||||
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 following example shows how to handle a WebSocket request using just the low-level http-core API:
|
||||
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.
|
||||
|
||||
.. includecode2:: ../../code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|
||||
:snippet: websocket-example-using-core
|
||||
|
||||
Handshake
|
||||
+++++++++
|
||||
|
||||
HTTP-level details of the WebSocket handshake are hidden from the application. The ``UpgradeToWebsocket`` represents a
|
||||
valid handshake request. The WebSocket protocol defines a facility to negotiate an application-level sub-protocol for
|
||||
the WebSocket connection. Use ``UpgradeToWebsocket.requestedProtocols`` to retrieve the protocols suggested by the
|
||||
client and pass one of the values to ``UpgradeToWebsocket.handleMessages`` or one of the other handling methods to
|
||||
select a sub-protocol.
|
||||
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``.
|
||||
|
||||
Handling Messages
|
||||
+++++++++++++++++
|
||||
|
|
@ -69,23 +66,44 @@ A message handler is expected to be implemented as a ``Flow[Message, Message, An
|
|||
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``.
|
||||
|
||||
There are other typical use-cases, however, like a server-push model where a server message is sent spontaneously, or
|
||||
true bi-directional use-cases where input and output aren't logically connected. Providing the handler as a ``Flow`` in
|
||||
these cases seems awkward. A variant of ``UpgradeToWebsocket.handleMessages``,
|
||||
``UpgradeToWebsocket.handleMessageWithSinkSource`` is provided instead, which allows for supplying a ``Sink[Message]``
|
||||
and a ``Source[Message]`` for input and output independently.
|
||||
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.
|
||||
|
||||
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_.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
Complete example
|
||||
----------------
|
||||
Here's the above simple request handler rewritten as a route:
|
||||
|
||||
.. includecode2:: ../../code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|
||||
:snippet: websocket-example-using-routing
|
||||
.. includecode:: ../../code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|
||||
:include: websocket-routing
|
||||
|
||||
.. _example: @github@/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala
|
||||
|
|
|
|||
|
|
@ -43,7 +43,13 @@ object Message {
|
|||
* the complete data is already available or it can be streamed in which case [[getStreamedText]]
|
||||
* will return a Source streaming the data as it comes in.
|
||||
*/
|
||||
//#message-model
|
||||
abstract class TextMessage extends Message {
|
||||
/**
|
||||
* Returns a source of the text message data.
|
||||
*/
|
||||
def getStreamedText: Source[String, _]
|
||||
|
||||
/** Is this message a strict one? */
|
||||
def isStrict: Boolean
|
||||
|
||||
|
|
@ -51,17 +57,14 @@ abstract class TextMessage extends Message {
|
|||
* Returns the strict message text if this message is strict, throws otherwise.
|
||||
*/
|
||||
def getStrictText: String
|
||||
|
||||
/**
|
||||
* Returns a source of the text message data.
|
||||
*/
|
||||
def getStreamedText: Source[String, _]
|
||||
|
||||
//#message-model
|
||||
def isText: Boolean = true
|
||||
def asTextMessage: TextMessage = this
|
||||
def asBinaryMessage: BinaryMessage = throw new ClassCastException("This message is not a binary message.")
|
||||
def asScala: sm.ws.TextMessage
|
||||
//#message-model
|
||||
}
|
||||
//#message-model
|
||||
|
||||
object TextMessage {
|
||||
/**
|
||||
|
|
@ -94,6 +97,11 @@ object TextMessage {
|
|||
}
|
||||
|
||||
abstract class BinaryMessage extends Message {
|
||||
/**
|
||||
* Returns a source of the binary message data.
|
||||
*/
|
||||
def getStreamedData: Source[ByteString, _]
|
||||
|
||||
/** Is this message a strict one? */
|
||||
def isStrict: Boolean
|
||||
|
||||
|
|
@ -102,11 +110,6 @@ abstract class BinaryMessage extends Message {
|
|||
*/
|
||||
def getStrictData: ByteString
|
||||
|
||||
/**
|
||||
* Returns a source of the binary message data.
|
||||
*/
|
||||
def getStreamedData: Source[ByteString, _]
|
||||
|
||||
def isText: Boolean = false
|
||||
def asTextMessage: TextMessage = throw new ClassCastException("This message is not a text message.")
|
||||
def asBinaryMessage: BinaryMessage = this
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue