Play2+nginx/Akka/WebSocketで高速双方向通信Scala+Play 2.0でWebアプリ開発入門(11)(2/2 ページ)

» 2013年12月18日 18時00分 公開
[中村修太クラスメソッド株式会社]
前のページへ 1|2       

Play2アプリでWebSocketを使う

WebSocketとは

 WebSocketとは、ネットワーク用に定義された比較的新しい通信規格で、W3CIETFが策定に関わっている、クライアント/サーバー間の双方向通信用技術規格です。

 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ファイルを作成し、その中に次のような定義を実装します。

  1. package controllers
  2. import akka.actor._
  3. import akka.pattern.ask
  4. import akka.util.Timeout
  5. import play.api.libs.iteratee._
  6. import play.api.libs.concurrent._
  7. import play.api.mvc.WebSocket
  8. import play.api.Play.current
  9. import play.api.mvc.Controller
  10. import play.api.mvc.Action
  11. object ChatController extends Controller {
  12. implicit val timeout = Timeout(1)
  13. val room = Akka.system.actorOf(Props[ChatRoom])
  14. def showRoom(nickName: String) = Action { implicit request =>
  15. Ok(views.html.chat(nickName))
  16. }
  17. def chatSocket(nickName: String) = WebSocket.async { request =>
  18. val channelsFuture = room ? Join(nickName)
  19. channelsFuture.mapTo[(Iteratee[String, _], Enumerator[String])]
  20. }
  21. }

 このチャットサンプルでは、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を作成しています。

  1. def sampleCode = WebSocket.using[String] { request =>
  2. // in channel
  3. val in = Iteratee.foreach[String](println).mapDone { _ =>
  4. // クライアント切断時の処理
  5. println("Disconnected")
  6. }
  7. // out channel
  8. val out = Enumerator("Hello WebSocket")
  9. (in, out)
  10. }

 次に、Actorでメッセージ種類を判定するためのcase classを定義しておきます。

  1. case class Join(nickName: String)
  2. case class Leave(nickName: String)
  3. case class Broadcast(msg: String)

 そして、Controllerから利用するActorを定義します。下記アクターのreceiveはWebSocketからメッセージを受け取るたびに処理されます。

  1. class ChatRoom extends Actor {
  2. var users = Set[String]()
  3. val (enumerator, channel) = Concurrent.broadcast[String]
  4. def receive = {
  5. case Join(nickName) => {
  6. if (!users.contains(nick)) {
  7. val iteratee = Iteratee.foreach[String] { message =>
  8. self ! Broadcast("%s: %s" format (nickName, message))
  9. }.mapDone { _ =>
  10. self ! Leave(nickName)
  11. }
  12. users += nickName
  13. channel.push("User %s has joined the room, now %s users"
  14. format (nickName, users.size))
  15. sender ! (iteratee, enumerator)
  16. } else {
  17. val enumerator = Enumerator(
  18. "Nickname %s is already in use." format nickName)
  19. val iteratee = Iteratee.ignore
  20. sender ! (iteratee, enumerator)
  21. }
  22. }
  23. case Leave(nickName) => {
  24. users -= nickName
  25. channel.push("User %s has left the room, %s users left"
  26. format (nickName, users.size))
  27. }
  28. case Broadcast(msg: String) => channel.push(msg)
  29. }
  30. }

 Join/Leave/Broadcast、それぞれメッセージを受け取ると、クライアントにその処理に応じたString文字列を返します。最後に、「views/chat.scala.html」を作成しましょう。

  1. @(nickName: String)(implicit request: RequestHeader)
  2. @main("Chatroom for " + nickName) {
  3. <h1>Chatroom - You are @nickName</h1>
  4. <form id="chatform">
  5. <input id="text" placeholder="Say something..." />
  6. <button type="submit">Say</button>
  7. </form>
  8. <ul id="messages"></ul>
  9. <script type="text/javascript">
  10. $(function() {
  11. ws = new WebSocket("@routes.ChatController.chatSocket(nickName).webSocketURL()");
  12. //ws = new WebSocket("ws://localhost:9000/room/socket/{nickName}")になる
  13. ws.onmessage = function(msg) {
  14. $('<li />').text(msg.data).appendTo('#messages')
  15. }
  16. $('#chatform').submit(function(){
  17. ws.send($('#text').val())
  18. $('#text').val("").focus()
  19. return false;
  20. });
  21. });
  22. </script>
  23. }

 クライアント側では普通に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のプラグインを紹介する予定です。

著者プロフィール

中村修太

中村修太(なかむら しゅうた)

クラスメソッド勤務の新しもの好きプログラマです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。

前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

Java Agile 鬮ォ�ェ陋滂ソス�ス�コ闕オ譁溷クキ�ケ譎「�ス�ウ驛「�ァ�ス�ュ驛「譎「�ス�ウ驛「�ァ�ス�ー

髫エ蟷「�ス�ャ髫エ魃会スス�・髫エ蟶キ�」�ッ闖ォ�」

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。