PLAY2.6-SCALA(九) WebSockets

WebSockets是一种支持全双工通讯的套接字。现代的html5经过js api使得浏览器天生支持webSocket。可是Websockets在移动端以及服务器之间的通讯也很是有用,在这些状况下能够复用一个已经存在的TCP链接。html

1.处理WebSockets

通常Play经过action来处理http请求,可是WebSockets是彻底不一样的,无法使用action来处理。html5

Play处理WebSockets的机制是创建在Akka Streams之上的。一个WebSockets被抽象为Flow,接收的信息被添加到flow中,flow产生的信息将发送到客户端中。java

注意在概念上,flow能够被视为一个接收某些信息,对信息进行一些处理再将信息传输出去的实体,没有理由为何必须如此,flow的输入和输出多是彻底断开的. Akka stream为了这个目的提供了一个构造器,Flow.fromSinkAndSource。而且在处理WebSockets时,输入与输出每每是不链接的。web

Play在WebSocket中提供了一些工厂方法用来构建WebSockets。json

2.使用Akka Streams和actors处理websockets

咱们可使用Play的工具ActorFlow来将一个ActorRef转换为一个flow,它接收一个函数,该函数将ActorRef转化为发送消息给一个akka.actor.Props 对象(来描述actor),当Play接收到websocket的链接时建立的对象api

import play.api.mvc._
import play.api.libs.streams.ActorFlow
import javax.inject.Inject
import akka.actor.ActorSystem
import akka.stream.Materializer

class Application @Inject()(cc:ControllerComponents) (implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {

  def socket = WebSocket.accept[String, String] { request =>
    ActorFlow.actorRef { out =>
      MyWebSocketActor.props(out)
    }
  }
}

注意 ActorFlow.actorRef(...)能够被任何akka streams Flow[In, Out, _]取代,可是actors是最直接的方式浏览器

import akka.actor._

object MyWebSocketActor {
  def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}

class MyWebSocketActor(out: ActorRef) extends Actor {
  def receive = {
    case msg: String =>
      out ! ("I received your message: " + msg)
  }
}

全部从客户端接收到的消息都会被发送到actor中,任何由Play提供的消息都会被发送到客户端中服务器

3.WebSocket的关闭

当WebSocket关闭时,Play会自动的中止actor。这意味着你能够实现actor的postStop方法来处理这一状况。websocket

override def postStop() = {
  someResource.close()
}

当处理WebSocket的actor终止时,Play将自动关闭WebSocket。因此,为了关闭WebSocket,发送一个PoisonPill给你本身的actorsession

import akka.actor.PoisonPill

self ! PoisonPill

4.拒绝一个WebSocket

有些状况可能须要判断是否接受一个WebSocket请求,这种状况下能够用acceptOrResult方法

import play.api.mvc._
  import play.api.libs.streams.ActorFlow
  import javax.inject.Inject
  import akka.actor.ActorSystem
  import akka.stream.Materializer

  class Application @Inject() (cc:ControllerComponents)(implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {

    def socket = WebSocket.acceptOrResult[String, String] { request =>
      Future.successful(request.session.get("user") match {
        case None => Left(Forbidden)
        case Some(_) => Right(ActorFlow.actorRef { out =>
          MyWebSocketActor.props(out)
        })
      })
    }
  }
}

5.处理不一样类型的信息

import play.api.libs.json._
import play.api.mvc._
import play.api.libs.streams.ActorFlow
import javax.inject.Inject
import akka.actor.ActorSystem
import akka.stream.Materializer

class Application @Inject()(cc:ControllerComponents)
                           (implicit system: ActorSystem, mat: Materializer)
  extends AbstractController(cc) {

  def socket = WebSocket.accept[JsValue, JsValue] { request =>
    ActorFlow.actorRef { out =>
      MyWebSocketActor.props(out)
    }
  }
}

假设咱们想要接收JSON消息,而且咱们想要将传入的消息解析为InEvent并将传出的消息格式化为OutEvent咱们想要作的第一件事是为out InEventOutEventtype 建立JSON格式

import play.api.libs.json._

implicit val inEventFormat = Json.format[InEvent]
implicit val outEventFormat = Json.format[OutEvent]

   //如今咱们能够为这些类型建立一个WebSocket  MessageFlowTransformer

import play.api.mvc.WebSocket.MessageFlowTransformer implicit val messageFlowTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[InEvent, OutEvent]

   //最后,咱们能够在咱们的WebSocket中使用它们:

import play.api.mvc._ import play.api.libs.streams.ActorFlow import javax.inject.Inject import akka.actor.ActorSystem import akka.stream.Materializer class Application @Inject()(cc:ControllerComponents)(implicit system: ActorSystem, mat: Materializer)extends AbstractController(cc) { def socket = WebSocket.accept[InEvent, OutEvent] { request => ActorFlow.actorRef { out => MyWebSocketActor.props(out) } } }

6.直接使用Akka Sreams处理WebSockets

import play.api.mvc._
import akka.stream.scaladsl._

def socket = WebSocket.accept[String, String] { request =>

  // Log events to the console
  val in = Sink.foreach[String](println)

  // Send a single 'Hello!' message and then leave the socket open
  val out = Source.single("Hello!").concat(Source.maybe)

  Flow.fromSinkAndSource(in, out)
}

一个WebSocket能够获取请求头部信息,这容许你读取标准的头部及session信息。可是没法获取请求体及响应信息。

这个例子中咱们建立了一个sink将全部的信息打印到控制台中。为了发送信息,建立了一个source发送一个hello。咱们也须要链接一个什么都不作的source,不然单个source会关闭flow,进而关闭连接。

能够在 https://www.websocket.org/echo.html上测试WebSocket,值须要将地址设为ws://localhost:9000

下面的例子会忽略全部的输入数据,在发送一个hello后关闭链接

import play.api.mvc._
import akka.stream.scaladsl._

def socket = WebSocket.accept[String, String] { request =>

  // Just ignore the input
  val in = Sink.ignore

  // Send a single 'Hello!' message and close
  val out = Source.single("Hello!")

  Flow.fromSinkAndSource(in, out)
}

将输入打印成标准输出,而后使用一个mapped flow返回给客户端

import play.api.mvc._
import akka.stream.scaladsl._

def socket =  WebSocket.accept[String, String] { request =>

  // log the message to stdout and send response back to client
  Flow[String].map { msg =>
    println(msg)
    "I received your message: " + msg
  }
} 

7.配置帧长度

能够经过配置play.server.websocket.frame.maxLength或在启动时添加参数-Dwebsocket.frame.maxLength来配置帧的最大长度

sbt -Dwebsocket.frame.maxLength=64k run
相关文章
相关标签/搜索