§WebSocket
WebSocket は、双方向の全二重通信を可能にするプロトコルに基づいて Web ブラウザから使用できるソケットです。サーバーとクライアントの間にアクティブな Web ソケット接続が存在する限り、クライアントはいつでもメッセージを送信することができ、サーバーはいつでもメッセージを受信することができます。
最新の HTML5 に準拠した Web ブラウザは、JavaScript WebSocket API を介して WebSocket をネイティブにサポートします。しかし、WebSocket は Web ブラウザだけで使用されているだけでなく、例えばサーバ同士で会話したり、ネイティブのモバイルアプリで WebSocket を使ったりすることのできる WebSocket クライアントライブラリがたくさんあります。このような状況において WebSocket を使用することには、Play サーバーが使用する既存の TCP ポートを再利用できるという利点があります。
§WebSocket の処理
これまでは、標準的な HTTP リクエストを処理し、標準的な HTTP レスポンスを送信するためには Action
を使っていました。WebSocket はそれと全く異なるので、通常の Action
では扱えません。
Play には、WebSocket を処理するための2つの異なる組み込みのメカニズムが用意されています。一つ目のメカニズムはアクターを使用し、二つ目はイテレートを使用します。これらの両方のメカニズムには、WebSocket で提供されるビルダーを使用してアクセスできます。
§アクターによる WebSocket の処理
アクターを使って WebSocket を処理するには、Play が WebSocket 接続を受け取ったときに作成するアクターが記述された akka.actor.Props
オブジェクトを Play に渡す必要があります。Play はアップストリームメッセージを送信するための akka.actor.ActorRef
を与えてくれるので、これを使って Props
オブジェクトの作成に役立てることができます。
import play.api.mvc._
import play.api.Play.current
def socket = WebSocket.acceptWithActor[String, String] { request => out =>
MyWebSocketActor.props(out)
}
この場合、ここに送るアクターは、このように見えます。
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)
}
}
クライアントから受信したメッセージはすべてアクターに送信され、Play から提供されたアクターに送信されたメッセージはすべてクライアントに送信されます。上記のアクターは、単にクライアントから受け取ったすべてのメッセージを I received your message:
を先頭に付けて送るだけです。
§WebSocket が閉じられたときの検出
WebSocket が閉じると、Playは自動的にアクターを停止します。つまり、WebSocket が消費した可能性のあるリソースをすべて片付ける処理を行うような、アクターの postStop
メソッドを実装することで、この状況を処理することができます。例を示します。
override def postStop() = {
someResource.close()
}
§WebSocket を閉じる
WebSocket を処理するアクターが終了すると、Play は WebSocket を自動的に閉じます。よって、WebSocket を閉じるには、自分のアクターに PoisonPill
を送ります。
import akka.actor.PoisonPill
self ! PoisonPill
§WebSocket の拒否
例えば WebSocket に接続するためにユーザーを認証する必要がある場合、または WebSocket がパスの中で ID を通過させるようなリソースに関連付けられている場合で、その ID を持つリソースが存在しない場合など、WebSocket リクエストを拒否したい場合もあるでしょう。Play はこれに対処するための tryAcceptWithActor
を提供し、結果(禁止されている、見つからないなど)、または WebSocket を処理するアクターを返すことができます。
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)
})
}
§異なる種類のメッセージの処理
これまでのところ、String
フレームの処理しか見ていませんでした。Play には Array[Byte]
フレームと、String
フレームから解析された JsValue
メッセージ用のハンドラが組み込まれています。これらを型パラメータとして WebSocket 作成メソッドに渡すことができます。たとえば、次のようにします。
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)
}
2つの型パラメータがあることに気づいたかもしれません。これにより、入力メッセージから出力メッセージへと、異なる型のメッセージを処理させることができます。これは通常、下位レベルのフレームタイプでは有用ではありませんが、上位レベルの型へメッセージを解析する場合に便利です。
たとえば、JSON メッセージを受け取りたいとします。受信メッセージを InEvent
として解析し、送信メッセージを OutEvent
としてフォーマットするとします。まず、InEvent
と OutEvent
型の JSON フォーマットを作成します。
import play.api.libs.json._
implicit val inEventFormat = Json.format[InEvent]
implicit val outEventFormat = Json.format[OutEvent]
これらの型に対して WebSocket FrameFormatter
を作成することができます。
import play.api.mvc.WebSocket.FrameFormatter
implicit val inEventFrameFormatter = FrameFormatter.jsonFrame[InEvent]
implicit val outEventFrameFormatter = FrameFormatter.jsonFrame[OutEvent]
最後に、WebSocket でこれらを使用することができます。
import play.api.mvc._
import play.api.Play.current
def socket = WebSocket.acceptWithActor[InEvent, OutEvent] { request => out =>
MyWebSocketActor.props(out)
}
このアクターによって、InEvent
型のメッセージを受け取り、OutEvent
型のメッセージを送ることができます。
§イテレートを使用した WebSocket の処理
アクターは、個別のメッセージを処理するためのより良い抽象化ですが、イテレートは、ストリームを処理するためのより良い抽象化であることがよくあります。
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
の二つのチャンネルを返す必要があります。
in
チャンネルは各メッセージについて通知されるIteratee[A,Unit]
(A
はメッセージタイプです - ここではString
を使用しています)であり、クライアント側でソケットが閉じられたときにEOF
を受け取ります。out
チャンネルは、Web クライアントに送信されるメッセージを生成するEnumerator[A]
です。EOF
を送信することで、サーバー側の接続を閉じることができます。
この例では、受信した各メッセージをコンソールに出力するだけのシンプルなイテレートを作成しています。また、メッセージを送信するため、 Hello! というメッセージを一回だけ送信する単純なダミーの Enumerator も作成しました。
ヒント: 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)
}
入力データが標準出力にロギングされ、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)
}
Next: テンプレートエンジン
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。