From 9399455601b0a30e91240568eb1af871285e1b73 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Mon, 24 Aug 2015 12:03:37 +0200 Subject: [PATCH] =doc add more documentation for WebsocketDirectives and testing --- .../server/WebsocketExampleSpec.scala | 11 +- .../WebsocketDirectivesExamplesSpec.scala | 104 ++++++++++++++++++ .../routing-dsl/directives/alphabetically.rst | 5 +- .../handleWebsocketMessages.rst | 15 ++- .../handleWebsocketMessagesForProtocol.rst | 32 ++++++ .../directives/websocket-directives/index.rst | 1 + .../http/routing-dsl/websocket-support.rst | 9 +- .../directives/WebsocketDirectives.scala | 8 +- 8 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala create mode 100644 akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessagesForProtocol.rst 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 bfd63cda70..d2fc0cab49 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 @@ -4,6 +4,8 @@ package docs.http.scaladsl.server +import akka.http.scaladsl.model.ws.BinaryMessage +import akka.stream.scaladsl.Sink import org.scalatest.{ Matchers, WordSpec } class WebsocketExampleSpec extends WordSpec with Matchers { @@ -27,13 +29,16 @@ class WebsocketExampleSpec extends WordSpec with Matchers { // returns a greeting message for that name val greeterWebsocketService = Flow[Message] - .collect { + .mapConcat { // we match but don't actually consume the text message here, // rather we simply stream it back as the tail of the response // this means we might start sending the response even before the // end of the incoming message has been received - case tm: TextMessage ⇒ TextMessage(Source.single("Hello ") ++ tm.textStream) - // ignore binary messages + case tm: TextMessage ⇒ TextMessage(Source.single("Hello ") ++ tm.textStream) :: Nil + case bm: BinaryMessage => + // ignore binary messages but drain content to avoid the stream being clogged + bm.dataStream.runWith(Sink.ignore) + Nil } //#websocket-handler diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala new file mode 100644 index 0000000000..e325d86389 --- /dev/null +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package docs.http.scaladsl.server.directives + +import scala.concurrent.duration._ + +import akka.util.ByteString + +import akka.stream.OverflowStrategy +import akka.stream.scaladsl.{ Sink, Source, Flow } + +import docs.http.scaladsl.server.RoutingSpec + +import akka.http.scaladsl.model.ws.{ TextMessage, Message, BinaryMessage } +import akka.http.scaladsl.testkit.WSProbe + +class WebsocketDirectivesExamplesSpec extends RoutingSpec { + "greeter-service" in { + def greeter: Flow[Message, Message, Any] = + Flow[Message].mapConcat { + case tm: TextMessage ⇒ + TextMessage(Source.single("Hello ") ++ tm.textStream ++ Source.single("!")) :: Nil + case bm: BinaryMessage ⇒ + // ignore binary messages but drain content to avoid the stream being clogged + bm.dataStream.runWith(Sink.ignore) + Nil + } + val websocketRoute = + path("greeter") { + handleWebsocketMessages(greeter) + } + + // create a testing probe representing the client-side + val wsClient = WSProbe() + + // WS creates a Websocket request for testing + WS("/greeter", wsClient.flow) ~> websocketRoute ~> + check { + // check response for WS Upgrade headers + isWebsocketUpgrade shouldEqual true + + // manually run a WS conversation + wsClient.sendMessage("Peter") + wsClient.expectMessage("Hello Peter!") + + wsClient.sendMessage(BinaryMessage(ByteString("abcdef"))) + wsClient.expectNoMessage(100.millis) + + wsClient.sendMessage("John") + wsClient.expectMessage("Hello John!") + + wsClient.sendCompletion() + wsClient.expectCompletion() + } + } + + "handle-multiple-protocols" in { + def greeterService: Flow[Message, Message, Any] = + Flow[Message].mapConcat { + case tm: TextMessage ⇒ + TextMessage(Source.single("Hello ") ++ tm.textStream ++ Source.single("!")) :: Nil + case bm: BinaryMessage ⇒ + // ignore binary messages but drain content to avoid the stream being clogged + bm.dataStream.runWith(Sink.ignore) + Nil + } + + def echoService: Flow[Message, Message, Any] = + Flow[Message] + // needed because a noop flow hasn't any buffer that would start processing in tests + .buffer(1, OverflowStrategy.backpressure) + + def websocketMultipleProtocolRoute = + path("services") { + handleWebsocketMessagesForProtocol(greeterService, "greeter") ~ + handleWebsocketMessagesForProtocol(echoService, "echo") + } + + val wsClient = WSProbe() + + // WS creates a Websocket request for testing + WS("/services", wsClient.flow, List("other", "echo")) ~> + websocketMultipleProtocolRoute ~> + check { + expectWebsocketUpgradeWithProtocol { protocol ⇒ + protocol shouldEqual "echo" + + wsClient.sendMessage("Peter") + wsClient.expectMessage("Peter") + + wsClient.sendMessage(BinaryMessage(ByteString("abcdef"))) + wsClient.expectMessage(ByteString("abcdef")) + + wsClient.sendMessage("John") + wsClient.expectMessage("John") + + wsClient.sendCompletion() + wsClient.expectCompletion() + } + } + } +} diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/alphabetically.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/alphabetically.rst index 52146ac713..2c9810ec79 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/alphabetically.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/alphabetically.rst @@ -72,7 +72,10 @@ Directive Description :ref:`-handleRejections-` Transforms rejections produced by the inner route using the given ``RejectionHandler`` :ref:`-handleWebsocketMessages-` Handles websocket requests with the given handler and rejects other requests - with a ``ExpectedWebsocketRequestRejection`` + with an ``ExpectedWebsocketRequestRejection`` +:ref:`-handleWebsocketMessagesForProtocol-` Handles websocket requests with the given handler if the subprotocol matches + and rejects other requests with an ``ExpectedWebsocketRequestRejection`` or + an ``UnsupportedWebsocketSubprotocolRejection``. :ref:`-handleWith-` Completes the request using a given function :ref:`-head-` Rejects all non-HEAD requests :ref:`-headerValue-` Extracts an HTTP header value using a given ``HttpHeader ⇒ Option[T]`` diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessages.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessages.rst index 8995998829..36b8a6ef3f 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessages.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessages.rst @@ -3,7 +3,8 @@ handleWebsocketMessages ======================= -... +Handles Websocket requests with the given handler and rejects other requests with an +``ExpectedWebsocketRequestRejection``. Signature --------- @@ -14,10 +15,16 @@ Signature Description ----------- -... +The directive first checks if the request was a valid Websocket handshake request and if yes, it completes the request +with the passed handler. Otherwise, the request is rejected with an ``ExpectedWebsocketRequestRejection``. + +Websocket subprotocols offered in the ``Sec-Websocket-Protocol`` header of the request are ignored. If you want to +support several protocols use the :ref:`-handleWebsocketMessagesForProtocol-` directive, instead. + +For more information about the Websocket support, see :ref:`server-side-websocket-support-scala`. Example ------- -... includecode2:: ../../../../code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala - :snippet: 0handleWebsocketMessages +.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala + :snippet: greeter-service diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessagesForProtocol.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessagesForProtocol.rst new file mode 100644 index 0000000000..b3717a8287 --- /dev/null +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/handleWebsocketMessagesForProtocol.rst @@ -0,0 +1,32 @@ +.. _-handleWebsocketMessagesForProtocol-: + +handleWebsocketMessagesForProtocol +================================== + +Handles Websocket requests with the given handler if the given subprotocol is offered in the ``Sec-Websocket-Protocol`` +header of the request and rejects other requests with an ``ExpectedWebsocketRequestRejection`` or an +``UnsupportedWebsocketSubprotocolRejection``. + +Signature +--------- + +.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/WebsocketDirectives.scala + :snippet: handleWebsocketMessagesForProtocol + +Description +----------- + +The directive first checks if the request was a valid Websocket handshake request and if the request offers the passed +subprotocol name. If yes, the directive completes the request with the passed handler. Otherwise, the request is +either rejected with an ``ExpectedWebsocketRequestRejection`` or an ``UnsupportedWebsocketSubprotocolRejection``. + +To support several subprotocols, for example at the same path, several instances of ``handleWebsocketMessagesForProtocol`` can +be chained using ``~`` as you can see in the below example. + +For more information about the Websocket support, see :ref:`server-side-websocket-support-scala`. + +Example +------- + +.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala + :snippet: handle-multiple-protocols diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/index.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/index.rst index c4c65ec684..82920ed198 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/index.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/websocket-directives/index.rst @@ -7,3 +7,4 @@ WebsocketDirectives :maxdepth: 1 handleWebsocketMessages + handleWebsocketMessagesForProtocol \ No newline at end of file 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 cf32dba206..547123c29c 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 @@ -103,7 +103,12 @@ was a WebSocket request. Otherwise, the directive rejects the request. Here's the above simple request handler rewritten as a route: -.. includecode:: ../../code/docs/http/scaladsl/server/WebsocketExampleSpec.scala - :include: websocket-routing +.. includecode2:: ../../code/docs/http/scaladsl/server/directives/WebsocketDirectivesExamplesSpec.scala + :snippet: greeter-service + +The example also includes code demonstrating the testkit support for Websocket services. It allows to create Websocket +requests to run against a route using `WS` which can be used to provide a mock Websocket probe that allows manual +testing of the Websocket handler's behavior if the request was accepted. + .. _example: @github@/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/WebsocketExampleSpec.scala diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/WebsocketDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/WebsocketDirectives.scala index bf527852d2..a2fb8a2688 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/WebsocketDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/WebsocketDirectives.scala @@ -31,7 +31,7 @@ trait WebsocketDirectives { def extractOfferedWsProtocols: Directive1[immutable.Seq[String]] = extractUpgradeToWebsocket.map(_.requestedProtocols) /** - * Handles Websocket requests with the given handler and rejects other requests with a + * Handles Websocket requests with the given handler and rejects other requests with an * [[ExpectedWebsocketRequestRejection]]. */ def handleWebsocketMessages(handler: Flow[Message, Message, Any]): Route = @@ -39,19 +39,19 @@ trait WebsocketDirectives { /** * Handles Websocket requests with the given handler if the given subprotocol is offered in the request and - * rejects other requests with a [[ExpectedWebsocketRequestRejection]] or a [[UnsupportedWebsocketSubprotocolRejection]]. + * rejects other requests with an [[ExpectedWebsocketRequestRejection]] or an [[UnsupportedWebsocketSubprotocolRejection]]. */ def handleWebsocketMessagesForProtocol(handler: Flow[Message, Message, Any], subprotocol: String): Route = handleWebsocketMessagesForOptionalProtocol(handler, Some(subprotocol)) /** - * Handles Websocket requests with the given handler and rejects other requests with a + * Handles Websocket requests with the given handler and rejects other requests with an * [[ExpectedWebsocketRequestRejection]]. * * If the `subprotocol` parameter is None any Websocket request is accepted. If the `subprotocol` parameter is * `Some(protocol)` a Websocket request is only accepted if the list of subprotocols supported by the client (as * announced in the Websocket request) contains `protocol`. If the client did not offer the protocol in question - * the request is rejected with a [[UnsupportedWebsocketSubprotocolRejection]] rejection. + * the request is rejected with an [[UnsupportedWebsocketSubprotocolRejection]] rejection. * * To support several subprotocols you may chain several `handleWebsocketMessage` Routes. */