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

1.处理WebSockets

一般Play通过action来处理http请求,但是WebSockets是完全不同的,没法使用action来处理。

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

注意在概念上,flow可以被视为一个接收某些信息,对信息进行一些处理再将信息传输出去的实体,没有理由为什么必须如此,flow的输入和输出可能是完全断开的. Akka stream为了这个目的提供了一个构造器,Flow.fromSinkAndSource。并且在处理WebSockets时,输入与输出往往是不连接的。

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

2.使用Akka Streams和actors处理websockets

我们可以使用Play的工具ActorFlow来将一个ActorRef转换为一个flow,它接收一个函数,该函数将ActorRef转化为发送消息给一个akka.actor.Props 对象(来描述actor),当Play接收到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[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方法来处理这一情况。

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

当处理WebSocket的actor终止时,Play将自动关闭WebSocket。所以,为了关闭WebSocket,发送一个PoisonPill给你自己的actor

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

PLAY2.6-SCALA(九) WebSockets的更多相关文章

  1. play1.x vs play2.x 对比(转)

    个人看到对比play1.x和play2.x比较的文章中,写的最深入,最清晰的一个.转自:http://freewind.me/blog/20120728/965.html 为了方便群中的Play初学者 ...

  2. Play1+angularjs+bootstrap ++ (idea + livereload)

    我的web开发最强组合:Play1+angularjs+bootstrap ++ (idea + livereload) 时间 2012-12-26 20:57:26  Freewind.me原文   ...

  3. 给Java开发人员的Play Framework(2.4)介绍 Part1:Play的优缺点以及适用场景

    1. 关于这篇系列 这篇系列不是Play框架的Hello World,由于这样的文章网上已经有非常多. 这篇系列会首先结合实际代码介绍Play的特点以及适用场景.然后会有几篇文章介绍Play与Spri ...

  4. play框架之简介

    Play Framework是一个开源的Web框架,背后商业公司是Typesafe.要介绍Play之前,首先理清Play的两个不同的分支. Play 1.x 使用Java开发,最新版本是1.3.1,只 ...

  5. Scala学习十九——解析

    一.本章要点 文法定义中的二选一.拼接.选项和重复在Scala组合子解析器中对应|.~.opt和rep 对于RegexParsers而言,字符串字面量和正则表达式匹配的是词法单元 用^^来处理解析结果 ...

  6. Scala学习九——文件和正则表达式

    一.本章要点 Source.fromFile(...).getLines.toArray输出文件的所有行; Source.fromFile(...).mkString以字符串形式输出文件内容; 将字符 ...

  7. scala言语基础学习九

    模式匹配 case _ =>不能放在函数的中间必须放在最后,否则scala会编译不通过 在case 里面使用if守卫 在模式匹配中获取输入的数据(在匹配不到的情况下) 对类型进行匹配 case ...

  8. Scala入门系列(九):函数式编程

    引言 Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证.也真是因此让Scala具备了Java所不具备的更强大的功能和特性. 而之所以Scala一直 ...

  9. Programming In Scala笔记-第十九章、类型参数,协变逆变,上界下界

    本章主要讲Scala中的类型参数化.本章主要分成三个部分,第一部分实现一个函数式队列的数据结构,第二部分实现该结构的内部细节,最后一个部分解释其中的关键知识点.接下来的实例中将该函数式队列命名为Que ...

随机推荐

  1. 约束布局ConstraintLayout加快布局速度

    Android Studio2.2更新布局设计器,同时,引人了约束布局ConstraintLayout. 简单来说,可以把它看做是相对布局的升级版本,但是区别与相对布局更加强调约束.何为约束,即控件之 ...

  2. KMP模板题 Number Sequence HDU1711

    模板...嗯 #include <iostream> #include <cstdio> #include <string.h> #pragma warning ( ...

  3. MySQL事务、锁机制、查询缓存

    MySQL事务 何为事务? 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit). 一个事务可以是一条SQL语句,一组SQL语句或整个程序. 事务的特性: 事 ...

  4. leetcode 563 - 653

    563. Binary Tree Tilt Input: 1 / \ 2 3 Output: 1 Explanation: Tilt of node 2 : 0 Tilt of node 3 : 0 ...

  5. js的几种继承方式

    1.原型链方式 function Super(){ this.val = 1; this.arr = [1]; } function Sub(){ // ... } Sub.prototype = n ...

  6. angular依赖注入(1)——从父元素到子元素的数据注入

    1.什么是依赖注入? 依赖注入是一种编程模式,可以让类从外部源中获得它的依赖,不必亲自创建他们. (这就达到了一个效果,我不知道我是怎么实现的,但我创建了一个实现他的接口,然后接口封装起来,1.可以分 ...

  7. javascript go()函数

    <a href="javascript:history.go(-1);">返回上一页</a> put this in your input tag < ...

  8. springmvc 支持对象与json 自动转换的配置

    基于maven的工程, 需要在pom.xml中添加如下依赖 <dependency> <groupId>javax.servlet</groupId> <ar ...

  9. hive-hbase表的建立

    HIVE-HBASE表CDH4的时候不成熟select*的时候会查询不出(必须通过字段名称),CDH5已经修复这点需要注意 接口表hive部分一般不做join select会占性能,因为hbase接口 ...

  10. NOIP模拟17.9.22

    NOIP模拟17.9.22 前进![问题描述]数轴的原点上有一只青蛙.青蛙要跳到数轴上≥