Documentation

§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 としてフォーマットするとします。まず、InEventOutEvent 型の 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 をこの方法で組み立てる場合、inout の二つのチャンネルを返す必要があります。

この例では、受信した各メッセージをコンソールに出力するだけのシンプルなイテレートを作成しています。また、メッセージを送信するため、 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 チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。