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