diff --git a/manual/working/scalaGuide/main/async/ScalaWebSockets.md b/manual/working/scalaGuide/main/async/ScalaWebSockets.md index 34d84aea..d09a6746 100644 --- a/manual/working/scalaGuide/main/async/ScalaWebSockets.md +++ b/manual/working/scalaGuide/main/async/ScalaWebSockets.md @@ -1,91 +1,192 @@ + +# WebSocket + +[WebSocket](https://ja.wikipedia.org/wiki/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](api/scala/play/api/mvc/WebSocket$.html) で提供されるビルダーを使用してアクセスできます。 + +## アクターによる WebSocket の処理 + +アクターを使って WebSocket を処理するには、Play が WebSocket 接続を受け取ったときに作成するアクターが記述された `akka.actor.Props` オブジェクトを Play に渡す必要があります。Play はアップストリームメッセージを送信するための `akka.actor.ActorRef` を与えてくれるので、これを使って `Props` オブジェクトの作成に役立てることができます。 @[actor-accept](code/ScalaWebSockets.scala) + +この場合、ここに送るアクターは、このように見えます。 @[example-actor](code/ScalaWebSockets.scala) + +クライアントから受信したメッセージはすべてアクターに送信され、Play から提供されたアクターに送信されたメッセージはすべてクライアントに送信されます。上記のアクターは、単にクライアントから受け取ったすべてのメッセージを `I received your message: ` を先頭に付けて送るだけです。 + +### WebSocket が閉じられたときの検出 + +WebSocket が閉じると、Playは自動的にアクターを停止します。つまり、WebSocket が消費した可能性のあるリソースをすべて片付ける処理を行うような、アクターの `postStop` メソッドを実装することで、この状況を処理することができます。例を示します。 @[actor-post-stop](code/ScalaWebSockets.scala) + +### WebSocket を閉じる + +WebSocket を処理するアクターが終了すると、Play は WebSocket を自動的に閉じます。よって、WebSocket を閉じるには、自分のアクターに `PoisonPill` を送ります。 @[actor-stop](code/ScalaWebSockets.scala) + +### WebSocket の拒否 + +例えば WebSocket に接続するためにユーザーを認証する必要がある場合、または WebSocket がパスの中で ID を通過させるようなリソースに関連付けられている場合で、その ID を持つリソースが存在しない場合など、WebSocket リクエストを拒否したい場合もあるでしょう。Play はこれに対処するための `tryAcceptWithActor` を提供し、結果(禁止されている、見つからないなど)、または WebSocket を処理するアクターを返すことができます。 @[actor-try-accept](code/ScalaWebSockets.scala) + +### 異なる種類のメッセージの処理 + +これまでのところ、`String` フレームの処理しか見ていませんでした。Play には `Array[Byte]` フレームと、`String` フレームから解析された `JsValue` メッセージ用のハンドラが組み込まれています。これらを型パラメータとして WebSocket 作成メソッドに渡すことができます。たとえば、次のようにします。 @[actor-json](code/ScalaWebSockets.scala) + +2つの型パラメータがあることに気づいたかもしれません。これにより、入力メッセージから出力メッセージへと、異なる型のメッセージを処理させることができます。これは通常、下位レベルのフレームタイプでは有用ではありませんが、上位レベルの型へメッセージを解析する場合に便利です。 + +たとえば、JSON メッセージを受け取りたいとします。受信メッセージを `InEvent` として解析し、送信メッセージを `OutEvent` としてフォーマットするとします。まず、`InEvent` と `OutEvent` 型の JSON フォーマットを作成します。 @[actor-json-formats](code/ScalaWebSockets.scala) + +これらの型に対して WebSocket `FrameFormatter` を作成することができます。 @[actor-json-frames](code/ScalaWebSockets.scala) + +最後に、WebSocket でこれらを使用することができます。 @[actor-json-in-out](code/ScalaWebSockets.scala) + +このアクターによって、`InEvent` 型のメッセージを受け取り、`OutEvent` 型のメッセージを送ることができます。 + +## イテレートを使用した WebSocket の処理 + +アクターは、個別のメッセージを処理するためのより良い抽象化ですが、イテレートは、ストリームを処理するためのより良い抽象化であることがよくあります。 + +WebSocket リクエストを処理するためには、`Action` の代わりに `WebSocket` を使います。 @[iteratee1](code/ScalaWebSockets.scala) + +`WebSocket` からはリクエストヘッダ (WebSocket 接続を開始するための HTTP リクエストからの) を参照でき、標準的なヘッダやセッションデータを取得することが可能です。しかし、リクエストボディを参照したり、HTTP レスポンスを返すことはできません。 + +`WebSocket` をこの方法で組み立てる場合、`in` と `out` の二つのチャンネルを返す必要があります。 + +- `in` チャンネルは各メッセージについて通知される `Iteratee[A,Unit]` (`A` + はメッセージタイプです - ここでは `String` を使用しています)であり、クライアント側でソケットが閉じられたときに `EOF` を受け取ります。 +- `out` チャンネルは、Web クライアントに送信されるメッセージを生成する `Enumerator[A]` です。`EOF` を送信することで、サーバー側の接続を閉じることができます。 + +この例では、受信した各メッセージをコンソールに出力するだけのシンプルなイテレートを作成しています。また、メッセージを送信するため、 **Hello!** というメッセージを一回だけ送信する単純なダミーの Enumerator も作成しました。 + +> **ヒント:** WebSocket は でテストすることができます。location に `ws://localhost:9000` を設定してください。 + +次は、入力データを全て捨てつつ、 **Hello!** メッセージを送信した後すぐにソケットを閉じる例を書いてみましょう。 @[iteratee2](code/ScalaWebSockets.scala) + +入力データが標準出力にロギングされ、`Concurrent.broadcast` を利用してクライアントにブロードキャストされる別の例を次に示します。 @[iteratee3](code/ScalaWebSockets.scala)