diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketCoreExample.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketCoreExample.java new file mode 100644 index 0000000000..184668603a --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketCoreExample.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +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 serverBindingFuture = + Http.get(system).bindAndHandleSync( + new Function() { + 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 greeter() { + return + Flow.create() + .collect(new JavaPartialFunction() { + @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 \ No newline at end of file diff --git a/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketRoutingExample.java b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketRoutingExample.java new file mode 100644 index 0000000000..e16219628d --- /dev/null +++ b/akka-docs-dev/rst/java/code/docs/http/javadsl/server/WebsocketRoutingExample.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +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 greeter() { + return + upcastMaterializerToObject(Flow.create()) + .collect(new JavaPartialFunction() { + @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 Flow upcastMaterializerToObject(Flow< T, U, ?> flow) { + return (Flow) flow; + } +} diff --git a/akka-docs-dev/rst/java/http/client-side/https-support.rst b/akka-docs-dev/rst/java/http/client-side/https-support.rst index c9175c080d..da4c6baa78 100644 --- a/akka-docs-dev/rst/java/http/client-side/https-support.rst +++ b/akka-docs-dev/rst/java/http/client-side/https-support.rst @@ -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 `. -For the time being, :ref:`see the Scala chapter on the same topic `. \ No newline at end of file +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 diff --git a/akka-docs-dev/rst/java/http/server-side/low-level-server-side-api.rst b/akka-docs-dev/rst/java/http/server-side/low-level-server-side-api.rst index 2a5aa7e457..e6e85561c3 100644 --- a/akka-docs-dev/rst/java/http/server-side/low-level-server-side-api.rst +++ b/akka-docs-dev/rst/java/http/server-side/low-level-server-side-api.rst @@ -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 `. -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 diff --git a/akka-docs-dev/rst/java/http/server-side/websocket-support.rst b/akka-docs-dev/rst/java/http/server-side/websocket-support.rst index 898bc9dd6e..a0902c0dbc 100644 --- a/akka-docs-dev/rst/java/http/server-side/websocket-support.rst +++ b/akka-docs-dev/rst/java/http/server-side/websocket-support.rst @@ -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 `. \ No newline at end of file +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)`` 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`` 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``. For typical request-response +scenarios this fits very well and such a ``Flow`` can be constructed from a simple function by using +``Flow.create().map`` or ``Flow.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`` and an input-receiving ``Sink`` 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 \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala index c39adbdc58..bfd63cda70 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala @@ -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 } } diff --git a/akka-docs-dev/rst/scala/http/client-side/https-support.rst b/akka-docs-dev/rst/scala/http/client-side/https-support.rst index 45eccee592..f2627640b8 100644 --- a/akka-docs-dev/rst/scala/http/client-side/https-support.rst +++ b/akka-docs-dev/rst/scala/http/client-side/https-support.rst @@ -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``. diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/websocket-support.rst b/akka-docs-dev/rst/scala/http/routing-dsl/websocket-support.rst index 33cd90cb04..cf32dba206 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/websocket-support.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/websocket-support.rst @@ -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 \ No newline at end of file +.. 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 diff --git a/akka-http-core/src/main/scala/akka/http/javadsl/model/ws/Message.scala b/akka-http-core/src/main/scala/akka/http/javadsl/model/ws/Message.scala index 4aaf4b6160..92b89fef2d 100644 --- a/akka-http-core/src/main/scala/akka/http/javadsl/model/ws/Message.scala +++ b/akka-http-core/src/main/scala/akka/http/javadsl/model/ws/Message.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