内容简介:WebSockets[翻译]
原文: WebSockets
WebSockets
WebSockets 是一个可以被基于允许双向全双工通信协议的Web浏览器使用的套接字。只要在服务端和客户端之间有一个活跃的WebSocket 连接, 客户端可以发送信息,服务端可以在任何时候收到信息。
新的兼容HTML5的浏览器本身通过JavaScript WebSocket API 支持WebSockets 。然而WebSockets 不仅仅局限于被WebBrowsers使用,有很多的WebSocket客户端库可以用,例如允许服务端彼此通信,也允许本地的移动应用可以使用WebSockets。在这些环境中使用WebSockets 有可以复用Play服务端已使用的TCP端口的好处。
提示:查看 caniuse.com 了解更多关于浏览器支持WebSockets的已知问题和更多信息。
处理WebSockets
到目前为止,我们使用Action实例处理标准的HTTP 请求,并发送回标准的HTTP应答。而WebSockets 是完全不同的东西,并不能通过标准的Action处理。
Play提供了两个不同的内置机制来处理WebSockets。第一个是使用Akka Streams(通常用Actor),第二种是使用Iteratees。这两种机制都可以使用构建器提供的 WebSocket 访问。
使用Akka Streams 和 Actors处理 WebSockets
为了使用Actor处理WebSocket,我们需要给Play一个携带Actor信息的akka.actor.Props 对象,当Play接受到WebSocket 连接时就可以创建Actor。Play将会给我们一个 akka.actor.ActorRef来发送上行信息,如此我们可以使用它来帮助创建Props 对象:
import play.api.mvc._ import play.api.libs.streams._ class Controller1 @Inject() (implicit system: ActorSystem, materializer: Materializer) { def socket = WebSocket.accept[String, String] { request => ActorFlow.actorRef(out => MyWebSocketActor.props(out)) } }
注意ActorFlow.actorRef(...) 可以使用任何的Akka Streams Flow[In, Out, _]替换,但是一般情况下,Actor是最直接的方式。
在这种情况下,我们是这样发送的Actor的:
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提供的发送给Actor的任何信息都会被发送给客户端。上面简单的Actor发送从客户端收回的每一个信息都会附加一个 I received your message:
当检测到一个WebSocket已经关闭
当WebSocket 已经关闭时,Play会自动的停止Actor。那么你可以通过使用Actor的postStop 方法来处理这个状况,清理WebSocket消耗的任何资源,例如:
override def postStop() = { someResource.close() }
关闭WebSocket
当处理的WebSocket结束时,Play将自动的关闭WebSocket。 所以,Play发送PoisonPill给你自己的Actor ,来关闭WebSocket:
import akka.actor.PoisonPill self ! PoisonPill
拒绝WebSocket
有时你希望拒绝WebSocket 请求,例如,如果用户必须被验证了才能连接WebSocket,或者如果WebSocket与资源关联,它的ID通过路径传递,但是没有这个ID相关的资源。Play提供了acceptOrResult 来解决这个问题,允许你返回一个结果(如禁止,或没找到),或者处理WebSocket 的Actor:
import scala.concurrent.Future import play.api.mvc._ import play.api.libs.streams._ class Controller3 @Inject() (implicit system: ActorSystem, materializer: Materializer) extends play.api.mvc.Controller { def socket = WebSocket.acceptOrResult[String, String] { request => Future.successful(request.session.get("user") match { case None => Left(Forbidden) case Some(_) => Right(ActorFlow.actorRef(MyWebSocketActor.props)) }) } }
注意:WebSocket 协议没有实现 同原协议 ,所以没有防备 跨站点的WebSocket劫持 。为了保护WebSocket不被劫持,在请求中的Origin头必须被检查以防服务端的源,并且应该实现手动的验证(包括CSRF令牌),然后acceptOrResult通过返回Forbidden 结果拒绝请求。
处理不同类型的信息
到目前为止,我们只看到处理String 结构的信息。Play也已经内置了 Array[Byte] 结构的处理,和从String结构的信息解析的JsValue 信息。你可以把这些做为类型参数传递给WebSocket 的创建方法,例如:
import play.api.libs.json.JsValue import play.api.mvc._ import play.api.libs.streams._ class Controller4 @Inject() (implicit system: ActorSystem, materializer: Materializer) { import akka.actor._ class MyWebSocketActor(out: ActorRef) extends Actor { import play.api.libs.json.JsValue def receive = { case msg: JsValue => out ! msg } } object MyWebSocketActor { def props(out: ActorRef) = Props(new MyWebSocketActor(out)) } def socket = WebSocket.accept[JsValue, JsValue] { request => ActorFlow.actorRef(out => MyWebSocketActor.props(out)) } }
你也许已经注意到了,有两个类型参数,这让我们可以处理传入不同类型的信息给输出的信息。这通常对低级的结构类型无用,但是如果你把信息解析成高级类型就可以使用了。
例如,假如我们想接收JSON信息,我们想把传入的信息解析为InEvent 并格式化输出的信息为OutEvent。我们想做的第一件事情是为所有的InEvent 和 OutEvent 类型创建JSON格式:
import play.api.libs.json._ implicit val inEventFormat = Json.format[InEvent] implicit val outEventFormat = Json.format[OutEvent]
现在我们可以为这些类型创建一个MessageFlowTransformer :
import play.api.mvc.WebSocket.FrameFormatter implicit val messageFlowTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[InEvent, OutEvent]
最终,我在我们的WebSocket中使用这些:
import play.api.libs.json._ import play.api.mvc._ import play.api.libs.streams._ // Note: requires implicit ActorSystem and Materializer (inject into your controller) def socket = WebSocket.accept[InEvent, OutEvent] { request => ActorFlow.actorRef(out => MyWebSocketActor.props(out)) }
现在在我们的Actor中,我们将接收到InEvent类型的信息,并且我们可以发送OutEvent类型的信息。
使用Iteratees处理 WebSockets
为了处理WebSocket 请求,我们用WebSocket 替代了Action:
import play.api.mvc._ import play.api.libs.iteratee._ import play.api.libs.concurrent.Execution.Implicits.defaultContext def socket = WebSocket.using[String] { request => // Log events to the console val in = Iteratee.foreach[String](println).map { _ => println("Disconnected") } // Send a single 'Hello!' message val out = Enumerator("Hello!") (in, out) }
WebSocket能够访问请求的头(从启动WebSocket 连接的HTTP 请求),允许你可以取到标准的头和Session数据,然而,它不能访问请求Body和HTTP应答。
当构建一个这样的WebSocket ,我必须返回in 和out 通道。
-
in通道是一个Iteratee[A,Unit](A是信息的类型——这里我们用String),它将通知每一个信息,并且当套接字在客户端关闭时会EOF。
-
out通道是一个Enumerator[A],它将会生成发送到Web客户端的信息,它可以通过发送EOF关闭连接服务端连接。
在这个例子中我们创建了一个简单的Iteratee, 它在控制台上打印了每一个信息。为了发送信息,我们创建了一个简单的虚拟
Enumerator,它将会发送一个Hello!信息。
提示:只要设置location 为ws://localhost:9000, 你就可以在 https://www.websocket.org/echo.html ,测试WebSockets 。
让我们再写一个丢弃输入数据并发送Hello!信息后关闭套接字的例子:
import play.api.mvc._ import play.api.libs.iteratee._ def socket = WebSocket.using[String] { request => // Just ignore the input val in = Iteratee.ignore[String] // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out) }
这是另一个例子,在这个例子中输入数据被记录到标准输出,并广播到使用Concurrent.broadcast的客户端:
import play.api.mvc._ import play.api.libs.iteratee._ import play.api.libs.concurrent.Execution.Implicits.defaultContext def socket = WebSocket.using[String] { request => // Concurrent.broadcast returns (Enumerator, Concurrent.Channel) val (out, channel) = Concurrent.broadcast[String] // log the message to stdout and send response back to client val in = Iteratee.foreach[String] { msg => println(msg) // the Enumerator returned by Concurrent.broadcast subscribes to the channel and will // receive the pushed messages channel push("I received your message: " + msg) } (in,out) }
以上所述就是小编给大家介绍的《WebSockets[翻译]》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 基于 Laravel、Lumen 框架集成百度翻译、有道翻译、Google 翻译扩展包
- 腾讯发布人工智能辅助翻译 致敬人工翻译
- golang调用baidu翻译api实现自动翻译
- 监管机器翻译质量?且看阿里如何搭建翻译质量评估模型
- 机器翻译新突破:谷歌实现完全基于attention的翻译架构
- PendingIntent 是个啥?官方文档描述的很到位。我给翻译翻译
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。