WebSockets是一種支持全雙工通訊的套接字。現代的html5經過js api使得瀏覽器天生支持webSocket。可是Websockets在移動端以及服務器之間的通訊也很是有用,在這些狀況下能夠複用一個已經存在的TCP鏈接。html
通常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
咱們可使用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提供的消息都會被髮送到客戶端中服務器
當WebSocket關閉時,Play會自動的中止actor。這意味着你能夠實現actor的postStop方法來處理這一狀況。websocket
override def postStop() = { someResource.close() }
當處理WebSocket的actor終止時,Play將自動關閉WebSocket。因此,爲了關閉WebSocket,發送一個PoisonPill
給你本身的actorsession
import akka.actor.PoisonPill self ! PoisonPill
有些狀況可能須要判斷是否接受一個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 InEvent
和OutEvent
type 建立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) } } }
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 } }
能夠經過配置play.server.websocket.frame.maxLength或在啓動時添加參數-Dwebsocket.frame.maxLength來配置幀的最大長度
sbt -Dwebsocket.frame.maxLength=64k run