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
相關文章
相關標籤/搜索