PLAY2.6-SCALA(九) WebSockets
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 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)
}
}
}
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的更多相关文章
- play1.x vs play2.x 对比(转)
个人看到对比play1.x和play2.x比较的文章中,写的最深入,最清晰的一个.转自:http://freewind.me/blog/20120728/965.html 为了方便群中的Play初学者 ...
- Play1+angularjs+bootstrap ++ (idea + livereload)
我的web开发最强组合:Play1+angularjs+bootstrap ++ (idea + livereload) 时间 2012-12-26 20:57:26 Freewind.me原文 ...
- 给Java开发人员的Play Framework(2.4)介绍 Part1:Play的优缺点以及适用场景
1. 关于这篇系列 这篇系列不是Play框架的Hello World,由于这样的文章网上已经有非常多. 这篇系列会首先结合实际代码介绍Play的特点以及适用场景.然后会有几篇文章介绍Play与Spri ...
- play框架之简介
Play Framework是一个开源的Web框架,背后商业公司是Typesafe.要介绍Play之前,首先理清Play的两个不同的分支. Play 1.x 使用Java开发,最新版本是1.3.1,只 ...
- Scala学习十九——解析
一.本章要点 文法定义中的二选一.拼接.选项和重复在Scala组合子解析器中对应|.~.opt和rep 对于RegexParsers而言,字符串字面量和正则表达式匹配的是词法单元 用^^来处理解析结果 ...
- Scala学习九——文件和正则表达式
一.本章要点 Source.fromFile(...).getLines.toArray输出文件的所有行; Source.fromFile(...).mkString以字符串形式输出文件内容; 将字符 ...
- scala言语基础学习九
模式匹配 case _ =>不能放在函数的中间必须放在最后,否则scala会编译不通过 在case 里面使用if守卫 在模式匹配中获取输入的数据(在匹配不到的情况下) 对类型进行匹配 case ...
- Scala入门系列(九):函数式编程
引言 Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证.也真是因此让Scala具备了Java所不具备的更强大的功能和特性. 而之所以Scala一直 ...
- Programming In Scala笔记-第十九章、类型参数,协变逆变,上界下界
本章主要讲Scala中的类型参数化.本章主要分成三个部分,第一部分实现一个函数式队列的数据结构,第二部分实现该结构的内部细节,最后一个部分解释其中的关键知识点.接下来的实例中将该函数式队列命名为Que ...
随机推荐
- spring基于接口的代理报错
报错: 1.在service层加上@Transactional注解.浏览器端报错(如下),eclipse控制台无信息 2.去掉@Transactional注解,无报错,但是数据库没有信息插入. 解决方 ...
- Luogu P1311 选择客栈(前缀和)
P1311 选择客栈 题意 题目描述 丽江河边有\(n\)家很有特色的客栈,客栈按照其位置顺序从\(1\)到\(n\)编号.每家客栈都按照某一种色调进行装饰(总共\(k\)种,用整数\(0\)~\(k ...
- 跟我一起做一个vue的小项目(十)
接下来我们对城市列表页面进行优化,除了对数据优化,也会进行节流处理 //src\pages\city\components\Alphabet.vue <template> <ul c ...
- nslookup获取域名对应的的ip地址
1.先用nslookup获得域名对应的主机ip:nslookup 域名 2.再根据主机ip获得对应的主机名称: host ip $ nslookup baidu.com Server: 127.0.1 ...
- LUOGU P1779 魔鬼杀手_NOI导刊2010提高(03)
传送门 解题思路 背包,首先先用aoe都打残然后单伤补刀,用f[i]表示AOE打了i的伤害的最小花费,g[i]表示单伤打了i的伤害的最小花费. 代码 #include<iostream> ...
- webstorm 2017.3.5之后 激活
选择"license server" 输入:http://idea.codebeta.cn
- excel2013做数据透视表
excel2013做数据透视表 Excel最新版更新到2013,相比2003.2007和2010,2013的excel界面方面有一定变化,在操作方面也有一定的便捷性.那么如何使用excel20 ...
- 【arc072e】AtCoder Regular Contest 072 E - Alice in linear land
题意 给定一个D,以及一个长度为N的序列a,顺序执行这些数字: 对于一个数字x,会使得D=min(D,abs(D-x)) 有Q次询问,每次询问独立,给出i,能否修改a[i],使得D最后不为0. n,q ...
- 为什么要使用ul li布局网站导航条?使用ul li布局网站网页导航必要性
会布局的都知道网站导航条布局非常重要,可能一个导航条最终布局效果有时可以使用ul li列表标签布局,有时可以不用ul li布局,而是直接一个div盒子里直接放锚文本超链接的栏目名称,也能实现,看下图. ...
- python基础--包、logging、hashlib、openpyxl、深浅拷贝
包:它是一系列模块文件的结合体,表现形式就是一个文件夹,该文件夹内部通常会有一个__init__.py文件,包的本质还是一个模块. 首次导入包:(在导入语句中中 . 号的左边肯定是一个包(文件夹)) ...