Merge pull request #18029 from spray/w/java-side-documentation

+doc #18012 add Java-side Server Websocket documentation
This commit is contained in:
Martynas Mickevičius 2015-07-20 17:58:50 +03:00
commit d27679ac99
9 changed files with 396 additions and 62 deletions

View file

@ -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
}
}

View file

@ -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``.

View file

@ -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