Play Specs2: FakeRequest to WebSocket server - scala
I have a controller with the following method
def subscribe(id: String) = WebSocket.tryAcceptWithActor[JsValue, JsValue]
I've written the following route in conf/routes:
GET /subscribe/:id #controllers.WsManager.subscribe(id: String)
The application works but I want to do a specific test through Specs2.
I tried to "subscribe" to websocket endpoint with:
val request = FakeRequest(GET, "/subscribe/1234")
val response = route(request)
or
val request = FakeRequest(GET, "ws://localhost:3333/subscribe/1234")
val response = route(request)
But in both case it doesn't work: I receive None as Response (response.isDefined is false).
Is there a correct way to connect to WebSocket endpoint in Specs2 test?
From Typesafe you have an example to test WebSockets
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package play.it.http.websocket
import play.api.test._
import play.api.Application
import scala.concurrent.{Future, Promise}
import play.api.mvc.{Handler, Results, WebSocket}
import play.api.libs.iteratee._
import java.net.URI
import org.jboss.netty.handler.codec.http.websocketx._
import org.specs2.matcher.Matcher
import akka.actor.{ActorRef, PoisonPill, Actor, Props}
import play.mvc.WebSocket.{Out, In}
import play.core.Router.HandlerDef
import java.util.concurrent.atomic.AtomicReference
import org.jboss.netty.buffer.ChannelBuffers
object WebSocketSpec extends PlaySpecification with WsTestClient {
sequential
def withServer[A](webSocket: Application => Handler)(block: => A): A = {
val currentApp = new AtomicReference[FakeApplication]
val app = FakeApplication(
withRoutes = {
case (_, _) => webSocket(currentApp.get())
}
)
currentApp.set(app)
running(TestServer(testServerPort, app))(block)
}
def runWebSocket[A](handler: (Enumerator[WebSocketFrame], Iteratee[WebSocketFrame, _]) => Future[A]): A = {
val innerResult = Promise[A]()
WebSocketClient { client =>
await(client.connect(URI.create("ws://localhost:" + testServerPort + "/stream")) { (in, out) =>
innerResult.completeWith(handler(in, out))
})
}
await(innerResult.future)
}
def textFrame(matcher: Matcher[String]): Matcher[WebSocketFrame] = beLike {
case t: TextWebSocketFrame => t.getText must matcher
}
def closeFrame(status: Int = 1000): Matcher[WebSocketFrame] = beLike {
case close: CloseWebSocketFrame => close.getStatusCode must_== status
}
def binaryBuffer(text: String) = ChannelBuffers.wrappedBuffer(text.getBytes("utf-8"))
/**
* Iteratee getChunks that invokes a callback as soon as it's done.
*/
def getChunks[A](chunks: List[A], onDone: List[A] => _): Iteratee[A, List[A]] = Cont {
case Input.El(c) => getChunks(c :: chunks, onDone)
case Input.EOF =>
val result = chunks.reverse
onDone(result)
Done(result, Input.EOF)
case Input.Empty => getChunks(chunks, onDone)
}
/*
* Shared tests
*/
def allowConsumingMessages(webSocket: Application => Promise[List[String]] => Handler) = {
val consumed = Promise[List[String]]()
withServer(app => webSocket(app)(consumed)) {
val result = runWebSocket { (in, out) =>
Enumerator(new TextWebSocketFrame("a"), new TextWebSocketFrame("b"), new CloseWebSocketFrame(1000, "")) |>>> out
consumed.future
}
result must_== Seq("a", "b")
}
}
def allowSendingMessages(webSocket: Application => List[String] => Handler) = {
withServer(app => webSocket(app)(List("a", "b"))) {
val frames = runWebSocket { (in, out) =>
in |>>> Iteratee.getChunks[WebSocketFrame]
}
frames must contain(exactly(
textFrame(be_==("a")),
textFrame(be_==("b")),
closeFrame()
).inOrder)
}
}
def cleanUpWhenClosed(webSocket: Application => Promise[Boolean] => Handler) = {
val cleanedUp = Promise[Boolean]()
withServer(app => webSocket(app)(cleanedUp)) {
runWebSocket { (in, out) =>
out.run
cleanedUp.future
} must beTrue
}
}
def closeWhenTheConsumerIsDone(webSocket: Application => Handler) = {
withServer(app => webSocket(app)) {
val frames = runWebSocket { (in, out) =>
Enumerator[WebSocketFrame](new TextWebSocketFrame("foo")) |>> out
in |>>> Iteratee.getChunks[WebSocketFrame]
}
frames must contain(exactly(
closeFrame()
))
}
}
def allowRejectingTheWebSocketWithAResult(webSocket: Application => Int => Handler) = {
withServer(app => webSocket(app)(FORBIDDEN)) {
implicit val port = testServerPort
await(wsUrl("/stream").withHeaders(
"Upgrade" -> "websocket",
"Connection" -> "upgrade"
).get()).status must_== FORBIDDEN
}
}
"Plays WebSockets" should {
"allow consuming messages" in allowConsumingMessages { _ => consumed =>
WebSocket.using[String] { req =>
(getChunks[String](Nil, consumed.success _), Enumerator.empty)
}
}
"allow sending messages" in allowSendingMessages { _ => messages =>
WebSocket.using[String] { req =>
(Iteratee.ignore, Enumerator.enumerate(messages) >>> Enumerator.eof)
}
}
"close when the consumer is done" in closeWhenTheConsumerIsDone { _ =>
WebSocket.using[String] { req =>
(Iteratee.head, Enumerator.empty)
}
}
"clean up when closed" in cleanUpWhenClosed { _ => cleanedUp =>
WebSocket.using[String] { req =>
(Iteratee.ignore, Enumerator.empty[String].onDoneEnumerating(cleanedUp.success(true)))
}
}
"allow rejecting a websocket with a result" in allowRejectingTheWebSocketWithAResult { _ => statusCode =>
WebSocket.tryAccept[String] { req =>
Future.successful(Left(Results.Status(statusCode)))
}
}
"allow handling a WebSocket with an actor" in {
"allow consuming messages" in allowConsumingMessages { implicit app => consumed =>
WebSocket.acceptWithActor[String, String] { req => out =>
Props(new Actor() {
var messages = List.empty[String]
def receive = {
case msg: String =>
messages = msg :: messages
}
override def postStop() = {
consumed.success(messages.reverse)
}
})
}
}
"allow sending messages" in allowSendingMessages { implicit app => messages =>
WebSocket.acceptWithActor[String, String] { req => out =>
Props(new Actor() {
messages.foreach { msg =>
out ! msg
}
out ! PoisonPill
def receive = PartialFunction.empty
})
}
}
"close when the consumer is done" in closeWhenTheConsumerIsDone { implicit app =>
WebSocket.acceptWithActor[String, String] { req => out =>
Props(new Actor() {
out ! PoisonPill
def receive = PartialFunction.empty
})
}
}
"clean up when closed" in cleanUpWhenClosed { implicit app => cleanedUp =>
WebSocket.acceptWithActor[String, String] { req => out =>
Props(new Actor() {
def receive = PartialFunction.empty
override def postStop() = {
cleanedUp.success(true)
}
})
}
}
"allow rejecting a websocket with a result" in allowRejectingTheWebSocketWithAResult { implicit app => statusCode =>
WebSocket.tryAcceptWithActor[String, String] { req =>
Future.successful(Left(Results.Status(statusCode)))
}
}
"aggregate text frames" in {
val consumed = Promise[List[String]]()
withServer(app => WebSocket.using[String] { req =>
(getChunks[String](Nil, consumed.success _), Enumerator.empty)
}) {
val result = runWebSocket { (in, out) =>
Enumerator(
new TextWebSocketFrame("first"),
new TextWebSocketFrame(false, 0, "se"),
new ContinuationWebSocketFrame(false, 0, "co"),
new ContinuationWebSocketFrame(true, 0, "nd"),
new TextWebSocketFrame("third"),
new CloseWebSocketFrame(1000, "")) |>>> out
consumed.future
}
result must_== Seq("first", "second", "third")
}
}
"aggregate binary frames" in {
val consumed = Promise[List[Array[Byte]]]()
withServer(app => WebSocket.using[Array[Byte]] { req =>
(getChunks[Array[Byte]](Nil, consumed.success _), Enumerator.empty)
}) {
val result = runWebSocket { (in, out) =>
Enumerator(
new BinaryWebSocketFrame(binaryBuffer("first")),
new BinaryWebSocketFrame(false, 0, binaryBuffer("se")),
new ContinuationWebSocketFrame(false, 0, binaryBuffer("co")),
new ContinuationWebSocketFrame(true, 0, binaryBuffer("nd")),
new BinaryWebSocketFrame(binaryBuffer("third")),
new CloseWebSocketFrame(1000, "")) |>>> out
consumed.future
}
result.map(b => b.toSeq) must_== Seq("first".getBytes("utf-8").toSeq, "second".getBytes("utf-8").toSeq, "third".getBytes("utf-8").toSeq)
}
}
"close the websocket when the buffer limit is exceeded" in {
withServer(app => WebSocket.using[String] { req =>
(Iteratee.ignore, Enumerator.empty)
}) {
val frames = runWebSocket { (in, out) =>
Enumerator[WebSocketFrame](
new TextWebSocketFrame(false, 0, "first frame"),
new ContinuationWebSocketFrame(true, 0, new String(Array.range(1, 65530).map(_ => 'a')))
) |>> out
in |>>> Iteratee.getChunks[WebSocketFrame]
}
frames must contain(exactly(
closeFrame(1009)
))
}
}
}
"allow handling a WebSocket in java" in {
import play.core.Router.HandlerInvokerFactory
import play.core.Router.HandlerInvokerFactory._
import play.mvc.{ WebSocket => JWebSocket, Results => JResults }
import play.libs.F
implicit def toHandler[J <: AnyRef](javaHandler: J)(implicit factory: HandlerInvokerFactory[J]): Handler = {
val invoker = factory.createInvoker(
javaHandler,
new HandlerDef(javaHandler.getClass.getClassLoader, "package", "controller", "method", Nil, "GET", "", "/stream")
)
invoker.call(javaHandler)
}
"allow consuming messages" in allowConsumingMessages { _ => consumed =>
new JWebSocket[String] {
#volatile var messages = List.empty[String]
def onReady(in: In[String], out: Out[String]) = {
in.onMessage(new F.Callback[String] {
def invoke(msg: String) = messages = msg :: messages
})
in.onClose(new F.Callback0 {
def invoke() = consumed.success(messages.reverse)
})
}
}
}
"allow sending messages" in allowSendingMessages { _ => messages =>
new JWebSocket[String] {
def onReady(in: In[String], out: Out[String]) = {
messages.foreach { msg =>
out.write(msg)
}
out.close()
}
}
}
"clean up when closed" in cleanUpWhenClosed { _ => cleanedUp =>
new JWebSocket[String] {
def onReady(in: In[String], out: Out[String]) = {
in.onClose(new F.Callback0 {
def invoke() = cleanedUp.success(true)
})
}
}
}
"allow rejecting a websocket with a result" in allowRejectingTheWebSocketWithAResult { _ => statusCode =>
JWebSocket.reject[String](JResults.status(statusCode))
}
"allow handling a websocket with an actor" in allowSendingMessages { _ => messages =>
JWebSocket.withActor[String](new F.Function[ActorRef, Props]() {
def apply(out: ActorRef) = {
Props(new Actor() {
messages.foreach { msg =>
out ! msg
}
out ! PoisonPill
def receive = PartialFunction.empty
})
}
})
}
}
}
Have a look at this code and WebSocketClient.scala (needed class) at https://github.com/playframework/playframework/tree/ca828431ab2c2c47bf0efede071dc9abced129b9/framework/src/play-integration-test/src/test/scala/play/it/http/websocket for play version 2.3.9.
Related
Akka stream flow recover from `failStage`
I have GraphStage taken from https://stackoverflow.com/a/40962834/772249 that looks like this. It's working as WebSocket server: class TerminateFlowStage[T]( predicate: T => Boolean, forwardTerminatingMessage: Boolean = false, terminate: Boolean = true) extends GraphStage[FlowShape[T, T]] { val in = Inlet[T]("TerminateFlowStage.in") val out = Outlet[T]("TerminateFlowStage.out") override val shape = FlowShape.of(in, out) override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) { setHandlers(in, out, new InHandler with OutHandler { override def onPull(): Unit = { pull(in) } override def onPush(): Unit = { val chunk = grab(in) if (predicate(chunk)) { if (forwardTerminatingMessage) { push(out, chunk) } if (terminate) { failStage(new RuntimeException("Flow terminated by TerminateFlowStage")) } else { completeStage() } } else { push(out, chunk) } } }) } } val termOnKillMe = new TerminateFlowStage[Message]((chunk: Message) => chunk match { case TextMessage.Strict(text) => text.toInt > 5 case _ => false }) val route = path("") { get { extractUpgradeToWebSocket { upgrade => complete(upgrade.handleMessagesWithSinkSource( Sink.ignore, Source(1 to 10). map(i => TextMessage(i.toString)).throttle(1, 1.second, 1, ThrottleMode.shaping).via(termOnKillMe) )) } } } So, after 5 messages, WebSocket server drops connection. I have this flow for WebSocket client: val flow: Flow[Message, Message, Future[Seq[Message]]] = Flow.fromSinkAndSourceMat( Sink.seq[Message], Source.maybe[Message])(Keep.left) val (upgradeResponse, promise) = Http().singleWebSocketRequest( WebSocketRequest("ws://localhost:8080/"), flow.recoverWithRetries(1, { case _ => Source.empty }) ) The problem is, that WebSocket client flow cannot recover from an exception raised by TerminateFlowStage. Getting Future(Failure(akka.http.scaladsl.model.ws.PeerClosedConnectionException: Peer closed connection with code 1011 'internal error')) Without exception raised, everything works great.
Scala isolate certain flatmap
I have a certain flatMap that I use at about 20 places. And I am sure it will be 20 more in the future. It is to throw an exception when a Option is empty. Example: def get(serverId: UUID, sessionId: UUID) = authAction.async { implicit request => val user = request.user.get serverService.findByIdAndUserId(serverId, user.id.get) flatMap { s => if (s.isEmpty) { Future.failed(new NotFoundException) } else { Future.successful(s.get) } } flatMap { _ => serverSessionService.findByIdAndServerId(sessionId, serverId) } flatMap { s => if (s.isEmpty) { Future.failed(new NotFoundException) } else { Future.successful(s.get) } } map { s => Ok(Json.toJson(s)) } } I am doing the flatMap for Option checking twice in one controller method... How can I isolate this part: flatMap { s => if (s.isEmpty) { Future.failed(new NotFoundException) } else { Future.successful(s.get) } }
Here's an approach using implicit class: implicit class OptionFuture[T](f: Future[Option[T]]) { def optionFuture(t: Throwable): Future[T] = f.flatMap{ case Some(x) => Future.successful(x) case _ => Future.failed(t) } } Future{ Some(1) }.optionFuture(new Exception("failed")) // Success(1) Future{ None }.optionFuture(new Exception("failed")) // Failure(java.lang.Exception: failed)
I would propose to add implicit class to fail if empty: implicit class FutureFailer[T <: Option[_]](f: Future[T]) { def failIfEmpty = { f.flatMap { case None => Future.failed(new NotFoundException) case k => Future.successful(k) } } } Future.successful(Option.empty[String]).failIfEmpty. flatMap(_ => Future.successful(Option.empty[String])).failIfEmpty
Akka actors always times out waiting for future
I have the following actor as defined below meant to "login" a user. object AuthenticationActor { def props = Props[AuthenticationActor] case class LoginUser(id: UUID) } class AuthenticationActor #Inject()(cache: CacheApi, userService: UserService) extends Actor{ import AuthenticationActor._ def receive = { case LoginEmployee(id: UUID) => { userService.getUserById(id).foreach { case Some(e) => { println("Logged user in") val sessionId = UUID.randomUUID() cache.set(sessionId.toString, e) sender() ! Some(e, sessionId) } case None => println("No user was found") } } } } Note: userService.getUserById returns a Future[Option[User]] And the following very simplistic API cal to it class EmployeeController #Inject()(#Named("authentication-actor") authActor: ActorRef)(implicit ec: ExecutionContext) extends Controller { override implicit val timeout: Timeout = 5.seconds def login(id: UUID) = Action.async { implicit request => (authActor ? LoginUser(id)).mapTo[Option[(User, UUID)]].map { case Some(authInfo) => Ok("Authenticated").withSession(request.session + ("auth" -> authInfo._2.toString)) case None => Forbidden("Not Authenticated") } } } Both println calls will execute, but login call will always fail saying that the ask has time out. Any suggestions?
When you do such thing (accessing sender within Futures callback) you need to store sender in a val in outter scope when you receive request because it is very likely change before Future completes. def receive = { case LoginEmployee(id: UUID) => { val recipient = sender userService.getUserById(id).foreach { case Some(e) => { ... recipient ! Some(e, sessionId) } ... } } } You also never send a result when user wasn't found. What you actually should do here is pipe the Future result to the sender def receive = { case LoginEmployee(id: UUID) => { userService.getUserById(id) map { _.map { e => val sessionId = UUID.randomUUID() cache.set(sessionId.toString, e) (e, sessionId) } } pipeTo sender } } or with prints def receive = { case LoginEmployee(id: UUID) => { userService.getUserById(id) map { case Some(e) => println("logged user in") val sessionId = UUID.randomUUID() cache.set(sessionId.toString, e) Some(e, sessionId) case None => println("user not found") None } pipeTo sender } }
Upload file Scala Spray
Looking to create functionality to upload a file using multipart/form-data. However I cannot grasp how to change the MultipartFormData and store it in the file system. Below is what I have so far. trait Service extends HttpService { private final lazy val fileWorker = actorRefFactory.actorOf(Props[FileServicesActor]) implicit def executionContext = actorRefFactory.dispatcher val demoRoute: Route = { path("file") { post { respondWithMediaType(`application/json`) { entity(as[MultipartFormData]) { formData => uploadFile(formData) } } } } } private def uploadFile(data: MultipartFormData)= { val response = (fileWorker ? UploadFile(data)).mapTo[Any].map { case t: Success => t case e: Error => Error.outputError(e) case _ => Failure(_) }.recover { case e: Exception => Failure(e) } complete(response) } } The function resolves to this def uploadFile(data: MultipartFormData) = { val file = data.get("file") //not sure what to do with data here... }
How to use actor to receive http requests and requests from other actors?
I want TestHttp class to be able to receive http requests or messages from other actors. How can I do it? Code: object Main extends App with SimpleRoutingApp { implicit val system = ActorSystem("system") import system.dispatcher implicit val timeout = Timeout(240.seconds) startServer(interface = "localhost", port = 3000) { get { path("register" / IntNumber) { n => respondWithMediaType(MediaTypes.`application/json`) { ctx => val future = IO(Http) ? Bind(system.actorOf(Props[TestHttp]), interface = "localhost", port = 3000 + n) future onSuccess { case Http.Bound(msg) => ctx.complete(s"Ok:"+msg) case _ => ctx.complete("...") } } } // : Route == RequestContext => Unit } // : Route } } trait TestHttpService extends HttpService { val oneRoute = { path("test") { complete("test") } } } class TestHttp extends Actor with TestHttpService { def actorRefFactory = context val sealedRoute = sealRoute(oneRoute) def receive = { // case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => //not working // sender ! HttpResponse(entity = "PONG") case ctx: RequestContext => sealedRoute(ctx) //not working } // def receive = runRoute(oneRoute) //it works }
Actor.Receive is a partial function that takes Any value and returns Unit (PartialFunction[Any, Unit]), so you can do it by regular PF composition. HttpService.runRoute returns Actor.Receive (see https://github.com/spray/spray/blob/master/spray-routing/src/main/scala/spray/routing/HttpService.scala#L31) So, your solution would be: class TestHttp extends Actor with TestHttpService { def actorRefFactory = context val sealedRoute = sealRoute(oneRoute) def receive = { case s: String => println(s"Just got string $s") } orElse runRoute(oneRoute) }