Play2+nginx/Akka/WebSocketで高速双方向通信:Scala+Play 2.0でWebアプリ開発入門(11)(2/2 ページ)
Play framework 2.xを既存のWebサーバーと連携させる方法、並列処理や双方向通信を行う方法を紹介します。
Play2アプリでWebSocketを使う
WebSocketとは
WebSocketとは、ネットワーク用に定義された比較的新しい通信規格で、W3CとIETFが策定に関わっている、クライアント/サーバー間の双方向通信用技術規格です。
WebSocketはサーバーとクライアントが最初に接続を行った後、その後の通信を全てその接続で行います。その際にはHTTPより軽いプロトコルを使用するため、接続や通信のための負荷が低減されます。
WebSocketを使用すると、従来の双方向通信「XMLHttpRequest(Ajax)」「Comet」が持っていた欠点を解消できるといわれています。
なお、この記事ではWebSocketについての詳細な解説は行いません。WebSocketについてもっと詳しく知りたい方は、記事「node.jsの衝撃とWebSocketが拓く未来」などの記事をご覧ください。
Play2ではWebSocketを使用するために「play.api.mvc.WebSocket」オブジェクトが用意されています。この章では、Play2のGitHubにあるWebSocketを使用したチャットのサンプルをベースに、チャット機能を実装してみましょう。
Play2アプリからWebSocketを使ってみる
ではチャット機能を実装していきましょう。これもgyroプロジェクトに機能を追加していきます。
まずは「conf/routes」ファイルに、チャット用の新しいコントローラーを登録します。
GET /room/:nickName controllers.ChatController.showRoom(nickName:String) GET /room/socket/:nickName controllers.ChatController.chatSocket(nickName:String)
最初のroute定義ではチャット画面初期表示、2番目のroute定義でWebSocketの接続を行うためのURL定義を行います。
次に、app/controllersディレクトリにChatController.scalaファイルを作成し、その中に次のような定義を実装します。
package controllers import akka.actor._ import akka.pattern.ask import akka.util.Timeout import play.api.libs.iteratee._ import play.api.libs.concurrent._ import play.api.mvc.WebSocket import play.api.Play.current import play.api.mvc.Controller import play.api.mvc.Action object ChatController extends Controller { implicit val timeout = Timeout(1) val room = Akka.system.actorOf(Props[ChatRoom]) def showRoom(nickName: String) = Action { implicit request => Ok(views.html.chat(nickName)) } def chatSocket(nickName: String) = WebSocket.async { request => val channelsFuture = room ? Join(nickName) channelsFuture.mapTo[(Iteratee[String, _], Enumerator[String])] } }
このチャットサンプルでは、Akkaを使用してチャット機能を実装しています。WebSocket通信をするためのchatSocket関数を見てみましょう。
普通のHTTPリクエストを処理するためにはActionを使っていましたが、WebSocketリクエストを処理するためには、ActionではなくWebSocketを使います。そして、WebSocketはinとoutの2つのチャンネルを返す必要があります。
inチャンネル(Iteratee[String, _])は、Iteratee[A,Unit]型(Aはメッセージの型)で、メッセージを受信するたびに通知を受け取ります。tチャンネル(Enumerator[String])はEnumerator[A]型で、クライアントへ送信するメッセージを生成します。
このチャンネルにEOFを送信することで、通信をサーバー側から切断できます。chatSocket関数ではどちらもString型を指定していますね。
例えば次のサンプルコードでは、受信した各メッセージを出力するだけのIterateeとメッセージを送信するためのEnumeratorを作成しています。
・ ・ def sampleCode = WebSocket.using[String] { request => // in channel val in = Iteratee.foreach[String](println).mapDone { _ => // クライアント切断時の処理 println("Disconnected") } // out channel val out = Enumerator("Hello WebSocket") (in, out) }
次に、Actorでメッセージ種類を判定するためのcase classを定義しておきます。
case class Join(nickName: String) case class Leave(nickName: String) case class Broadcast(msg: String)
そして、Controllerから利用するActorを定義します。下記アクターのreceiveはWebSocketからメッセージを受け取るたびに処理されます。
class ChatRoom extends Actor { var users = Set[String]() val (enumerator, channel) = Concurrent.broadcast[String] def receive = { case Join(nickName) => { if (!users.contains(nick)) { val iteratee = Iteratee.foreach[String] { message => self ! Broadcast("%s: %s" format (nickName, message)) }.mapDone { _ => self ! Leave(nickName) } users += nickName channel.push("User %s has joined the room, now %s users" format (nickName, users.size)) sender ! (iteratee, enumerator) } else { val enumerator = Enumerator( "Nickname %s is already in use." format nickName) val iteratee = Iteratee.ignore sender ! (iteratee, enumerator) } } case Leave(nickName) => { users -= nickName channel.push("User %s has left the room, %s users left" format (nickName, users.size)) } case Broadcast(msg: String) => channel.push(msg) } }
Join/Leave/Broadcast、それぞれメッセージを受け取ると、クライアントにその処理に応じたString文字列を返します。最後に、「views/chat.scala.html」を作成しましょう。
@(nickName: String)(implicit request: RequestHeader) @main("Chatroom for " + nickName) { <h1>Chatroom - You are @nickName</h1> <form id="chatform"> <input id="text" placeholder="Say something..." /> <button type="submit">Say</button> </form> <ul id="messages"></ul> <script type="text/javascript"> $(function() { ws = new WebSocket("@routes.ChatController.chatSocket(nickName).webSocketURL()"); //ws = new WebSocket("ws://localhost:9000/room/socket/{nickName}")になる ws.onmessage = function(msg) { $('<li />').text(msg.data).appendTo('#messages') } $('#chatform').submit(function(){ ws.send($('#text').val()) $('#text').val("").focus() return false; }); }); </script> }
クライアント側では普通にWebSocket接続を行っています。「@routes.ChatController〜」と、webSocketURL関数を使用して記述していますが、このように記述すると、指定したWebSocket用関数に接続するためのURLを逆引きしてくれます。
そして、実際にonmessageでメッセージを受け取ると、会話メッセージが追加されていきます。
ではアプリを起動して動作を確認してみましょう。
% cd /path/your/apppath % play ・ ・ ・ [gyro] % run
以下のようにURLを入力して、複数ブラウザーでアクセスしてメッセージを送ってみてください。
http://localhost:9000/room/{ニックネーム}
メッセージを送ると、全てのブラウザーでメッセージが表示されます。
Play2の公式ページにもWebSocketに関するドキュメントがあります。参考にしてみてください。
次回はPlay2のプラグイン
さて、今回はnginx連携やWebSocket/AkkaをPlay2アプリから使用する方法を紹介しました。最近はWebSocketといえばNode.jsのイメージが強いですが、Play2でもしっかり使用できるので、検討してみてください。
次回はPlay2のプラグインを紹介する予定です。
- Play2プラグインのインストールと使用と新規開発
- Play2+nginx/Akka/WebSocketで高速双方向通信
- Play2(+JavaScript)アプリを高速化、最適化する4つのテクニック
- Play2におけるJSONおよびCoffeeScriptの使い方
- Playのグローバルな設定&spec2でBDDなテスト
- Play 2.xからMySQLに接続してAnormでCRUD操作するには
- Play 2.xのScala Templatesでビュー&フォーム操作
- Play 2.1にアップグレードしてコントローラを使いこなす
- Playコントローラ/Action/HTTP Routerの基本的な使い方
- 便利なPlayコンソールとEclipseでのデバッグ方法
- Play 2.0のアーキテクチャとディレクトリ構成の基礎知識
- Play framework 2.0の概要/5つの特徴とScalaで作るための環境構築
著者プロフィール
中村修太(なかむら しゅうた)
クラスメソッド勤務の新しもの好きプログラマです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 連載:人気順に説明する初めてのHTML5開発:双方向通信を実現! WebSocketを使いこなそう
リアルタイムな表示更新を行うWebアプリの開発で使われてきたAjaxとComet。その問題点を克服したWebSocketとは。 - Node.jsを使ってみよう(1):Node.js、Socket.IO、MongoDBでリアルタイムWeb
Node.jsとSocket.IO、MongoDBを使用して、Webページの更新内容がリアルタイムに画面に反映されるサイトを作ってみた - WebSocketやREST APIのサポート強化:Java 8&Java EE 7に対応した「Spring Framework 4.0」正式版リリース
米Pivotalは2013年12月12日、オープンソースのJavaアプリケーションフレームワーク「Spring Framework 4.0」の正式版をリリースした。 - Tomcat、Jetty、Socket.IO/Node.js性能比較:WebSocketが一番速いアプリケーションサーバはどれだ?
双方向通信を実現するHTML5関連技術WebSocketを実装した3つのアプリケーションサーバの実装の違い・性能などを徹底検証する。 - WebSocket、組み込み、並列処理で進化するJava、そして最強のIDEはどれだ?〜Java Day Tokyo 2013まとめレポート
Java EEにおけるWebSocket、並列処理の過去・現在・未来、Raspberry Piとの連携、Eclipse、NetBeans、IntelliJ IDEAの中から最強のIDEを決める座談会、ミニ四駆の発表もあったLT大会などの模様をお届け。 - UXClip(27):WebSocketでスマートテレビをリアル接続するぷらら
ひかりTV独自のスマートテレビリモコンの接続方法はWebSocketを用いた常時接続だ。その仕組みと開発意図を聞いた。 - ASP.NETで利用可能:米マイクロソフト、Windows Azure WebサイトでWebSocketに対応
Windows Azureの「Webサイト」でWebSocketが使えるようになった。Windows Azure Webサイトの設定タブに、WebSocketへの対応を有効にできるオプションが新たに加わった。ASP.NETまたはnode.jsによるアプリケーションで利用できる。 - スケーラブルで関数型でオブジェクト指向なScala入門
Scalaの特徴を紹介し、基本構文や関数、クラスなど、Scalaの基本的な機能について解説する入門連載 - Javaの常識を変えるPlay framework入門
サーブレット/JSPを基にする重厚長大なJavaのWeb開発のイメージを変える軽量フレームワーク「Play」について解説し、Webアプリの作り方を紹介する入門連載