§WebSocket
WebSockets are sockets that can be used from a web browser based on a protocol that allows two way full duplex communication. The client can send messages and the server can receive messages at any time, as long as there is an active WebSocket connection between the server and the client.
Modern HTML5 compliant web browsers natively support WebSockets via a JavaScript WebSocket API. However WebSockets are not limited in just being used by WebBrowsers, there are many WebSocket client libraries available, allowing for example servers to talk to each other, and also native mobile apps to use WebSockets. Using WebSockets in these contexts has the advantage of being able to reuse the existing TCP port that a Play server uses.
§WebSocket を扱う
これまでは、標準的な HTTP リクエストを処理し、標準的な HTTP レスポンスを送信するためには Action
を使っていました。WebSocket はそれと全く異なるので、通常の Action
では扱えません。
Play provides two different built in mechanisms for handling WebSockets. The first is using actors, the second is using iteratees. Both of these mechanisms can be accessed using the builders provided on WebSocket.
§Handling WebSockets with actors
To handle a WebSocket with an actor, we need to give Play a akka.actor.Props
object that describes the actor that Play should create when it receives the WebSocket connection. Play will give us an akka.actor.ActorRef
to send upstream messages to, so we can use that to help create the Props
object:
import play.api.mvc._
import play.api.Play.current
def socket = WebSocket.acceptWithActor[String, String] { request => out =>
MyWebSocketActor.props(out)
}
The actor that we’re sending to here in this case looks like this:
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)
}
}
Any messages received from the client will be sent to the actor, and any messages sent to the actor supplied by Play will be sent to the client. The actor above simply sends every message received from the client back with I received your message:
prepended to it.
§Detecting when a WebSocket has closed
When the WebSocket has closed, Play will automatically stop the actor. This means you can handle this situation by implementing the actors postStop
method, to clean up any resources the WebSocket might have consumed. For example:
override def postStop() = {
someResource.close()
}
§Closing a WebSocket
Play will automatically close the WebSocket when your actor that handles the WebSocket terminates. So, to close the WebSocket, send a PoisonPill
to your own actor:
import akka.actor.PoisonPill
self ! PoisonPill
§Rejecting a WebSocket
Sometimes you may wish to reject a WebSocket request, for example, if the user must be authenticated to connect to the WebSocket, or if the WebSocket is associated with some resource, whose id is passed in the path, but no resource with that id exists. Play provides tryAcceptWithActor
to address this, allowing you to return either a result (such as forbidden, or not found), or the actor to handle the WebSocket with:
import scala.concurrent.Future
import play.api.mvc._
import play.api.Play.current
def socket = WebSocket.tryAcceptWithActor[String, String] { request =>
Future.successful(request.session.get("user") match {
case None => Left(Forbidden)
case Some(_) => Right(MyWebSocketActor.props)
})
}
§Handling different types of messages
So far we have only seen handling String
frames. Play also has built in handlers for Array[Byte]
frames, and JsValue
messages parsed from String
frames. You can pass these as the type parameters to the WebSocket creation method, for example:
import play.api.mvc._
import play.api.libs.json._
import play.api.Play.current
def socket = WebSocket.acceptWithActor[JsValue, JsValue] { request => out =>
MyWebSocketActor.props(out)
}
You may have noticed that there are two type parameters, this allows us to handle differently typed messages coming in to messages going out. This is typically not useful with the lower level frame types, but can be useful if you parse the messages into a higher level type.
For example, let’s say we want to receive JSON messages, and we want to parse incoming messages as InEvent
and format outgoing messages as OutEvent
. The first thing we want to do is create JSON formats for out InEvent
and OutEvent
types:
import play.api.libs.json._
implicit val inEventFormat = Json.format[InEvent]
implicit val outEventFormat = Json.format[OutEvent]
Now we can create WebSocket FrameFormatter
’s for these types:
import play.api.mvc.WebSocket.FrameFormatter
implicit val inEventFrameFormatter = FrameFormatter.jsonFrame[InEvent]
implicit val outEventFrameFormatter = FrameFormatter.jsonFrame[OutEvent]
And finally, we can use these in our WebSocket:
import play.api.mvc._
import play.api.Play.current
def socket = WebSocket.acceptWithActor[InEvent, OutEvent] { request => out =>
MyWebSocketActor.props(out)
}
Now in our actor, we will receive messages of type InEvent
, and we can send messages of type OutEvent
.
§Handling WebSockets with iteratees
While actors are a better abstraction for handling discreet messages, iteratees are often a better abstraction for handling streams.
WebSocket リクエストを処理するためには、Action
の代わりに WebSocket
を使います。
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 リクエストからの。) を参照でき、標準的なヘッダやセッションデータを取得することが可能です。しかし、リクエストボディを参照したり、HTTP レスポンスを返すことはできません。
WebSocket
をこの方法で組み上げる場合、in
と out
の二つのチャンネルを返す必要があります。
- The
in
channel is anIteratee[A,Unit]
(whereA
is the message type - here we are usingString
) that will be notified for each message, and will receiveEOF
when the socket is closed on the client side. - The
out
channel is anEnumerator[A]
that will generate the messages to be sent to the Web client. It can close the connection on the server side by sendingEOF
.
この例では、受信した各メッセージを console に出力するだけのシンプルな Iteratee を作成しています。また、メッセージを送信するため、 Hello! というメッセージを一回だけ送信する単純なダミーの Enumerator も作成しました。
Tip: WebSocket は http://websocket.org/echo.html でテストすることができます。location に
ws://localhost:9000
を設定してください。
次は、入力データを全て捨てつつ、 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)
}
Here is another example in which the input data is logged to standard out and broadcast by to the client utilizing ‘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)
}
次ページ: テンプレートエンジン
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。