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
|
||||
Loading…
Add table
Add a link
Reference in a new issue