=doc add more documentation for WebsocketDirectives and testing

This commit is contained in:
Johannes Rudolph 2015-08-24 12:03:37 +02:00
parent 060ea707c9
commit 9399455601
8 changed files with 171 additions and 14 deletions

View file

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

View file

@ -0,0 +1,104 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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()
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -7,3 +7,4 @@ WebsocketDirectives
:maxdepth: 1
handleWebsocketMessages
handleWebsocketMessagesForProtocol

View file

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

View file

@ -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.
*/