FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

Play frameworkでWebSocketを使う

んにちは。フリュー モバイル事業部の九岡です。 今回はPlay frameworkのWebSocketサポートをご紹介します。

WebSocket

WebSocketとは、WebブラウザとWebサーバ間でリアルタイムに双方向通信を行うための通信方法です。

ブラウザ-サーバ間のリアルタイムな双方向通信は、これまでもFlashやAppletでソケット通信を使えばできましたが、

  • JavaScriptから直接アクセスできない
  • Webの範囲外であるが故にWebフレームワークが対応しておらず、サーバ側の実装も面倒くさい

という難点がありました。

そこでWebSocketです。

双方向通信の仕様がWebSocketとしてブラウザに取り込まれる事で、実装の敷居が下がりました。

  • 最近流行のHTML5+JavaScriptのクライアントアプリにちょっと手を入れるだけで双方向通信に対応できる
  • 巷のWebフレームワーク等のサポートが始まり、サーバ側の実装コストも低い

Play framework meets WebSocket

さて、本題のPlay frameworkですが、バージョン1.2からWebSocketをサポートしています。

Play frameworkはMVCアーキテクチャに基づいたWebフレームワークですが、

C層にWebSocketControllerというものが追加されました。

これを実装するだけで、既存のWebアプリケーションをさくさくっとWebSocketに対応させることができます。

これまで利用してきたHTTPベースのWebアプリケーションを同居させることができたり、同じModelをWebSocketControllerからも利用することができるなど、相互運用性が高いところもポイントです。

環境

Play framework

バージョン1.2以降

ブラウザ

最近の * Google Chrome * Safari * Mobile Safari Mobile Safariというところがポイントで、実はiPhone/iPad/iPod touchでもWebSocketが使えます。

実装例

双方向通信のイメージが湧くように、WebSocketを利用してWebベースのechoサーバを実装してみます。 仕様は以下のようにします。

  1. テストページから、サーバにメッセージを送信できる
  2. 送信されたメッセージが、サーバからそのまま返送される
  3. 返送されたメッセージがページ内に表示される

View

まず、Viewを作成します。

app/views/Echo/demo.html

#{extends 'main.html' /}
#{set title:'デモページ' /}



<div class="content">
  <div id="messages">
    
  </div>
   
    
  
  <div id="control">
    <input type="text" id="what" /> <input type="button" id="say" value="送信" />
      
  </div>
  
</div>

id=whatのinput要素にメッセージを入力して、id=sayのボタンをクリックするとメッセージをWebSocket経由でサーバに送信します。 id=messagesのdiv要素に、全メッセージが表示されます。

var ws = new WebSocket("@@{Echo.WebSocketEcho.listen()}"); 

このコードで@@{ /** */ }となっている部分は、WebSocketの接続先の指定です。

このViewがHTMLとして出力される時には、conf/routseのルート定義に基づいて、

var ws = new WebSocket("ws://localhost:9000/echo"); 

のように置換えられます。

また、

ws.send("hi"); 

は、sendの引数に渡した文字列をWebSocket経由で送信します。

ws.onmessage = function(event) { /** メッセージを受信したときの処理 */ } 

は、WebSocket経由でサーバからメッセージを受け取る度に実行されます。

event.data 

がサーバから送信されたメッセージです。

Integration Test

仕様1〜3のテキトウなテストを書きます。

今回はWebSocketというブラウザに依存する機能を通したテストなので、Integration TestをPlayのSeleniumテストケースを使います。

先ほど作成したViewを見ながら、

test/Application.test.htmlに以下の内容を追記してください。

#{selenium} 
  open('/echo') assertNotTitle('Application error')
  input(id=what,'Hello!')
  click(id=say)
  pause(1000)
  assertTextPresent('Hello!') 
#{/selenium} 

このテストケースでは、

  • /echoを開いた時に、エラーが発生していないこと
  • input要素にメッセージを入力して送信ボタンをクリックすると、しばらく後にメッセージがページ内に表示されること

を検証しています。

2つめのテストでは、メッセージがWebSocketを経由してサーバに送信され、サーバがそれを送り返してページに表示される、ということを期待しています。

作成したテストケースは、

http://localhost:9000/@tests 

から実行できます。

WebSocketController

テストページの表示と、WebSocketでのメッセージのやり取りのためのコントローラを実装します。

package controllers;
import models.LoggedMessage;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.WebSocketController;

import static play.mvc.Http.WebSocketEvent.TextFrame;

public class Echo extends Controller {
    public static void demo() {
      render();
    } 
    
    public static class WebSocketEcho extends WebSocketController {
    
        public static void listen() {
            // WebSocketが接続されている間、isbound.isOpen()はtrue 
            while(inbound.isOpen()) {
                // クライアントから送られるメッセージを、継続を使って非同期で待ちます。
                Http.WebSocketEvent event = await(inbound.nextEvent()); 
                
                // メッセージがテキストであればfor内が実行されます。 
                // パターンマッチにfor文を使うのは珍しいですね。
                for (String message : TextFrame.match(event)) {
                    // クライアントにメッセージを返送します。
                    outbound.send(message); 
                    
                    // 本題のechoサーバとは何の関係もありませんが、
                    // このように、HTTP用のControllerから利用していたModelをWebSocketControllerからも普通に利用することができます。Interoperability! 
                    new LoggedMessage(message).save();
                }
            }
        }
    }
} 

Echo.demoがテストページを表示するメソッドです。

render()により描画されるViewは、先ほど作成したapp/views/Echo/demo.htmlです。

Echo.WebSocketEcho.listenがWebSocket経由のメッセージのやり取りを行うメソッドです。

1コネクションあたり1回呼び出され、接続終了までループでメッセージを待ち続けます。

HTTP用のコントローラとは全く実装方法が違いますね。

routes

conf/routesにHTTP, WebSocket用コントローラへのパスを登録します。

GET /echo Echo.demo WS /echo Echo.WebSocketEcho.listen 

前者はhttp://localhost:9000/echoのようなURLへのHTTPリクエストをEcho.demoへ、

後者はws://localhost:9000/echoのようなURLへのWebSocket接続をEcho.WebSocketEcho.listenへルーティングします。

実行例

では早速実行してみましょう。

play test 

ブラウザで以下のURLにアクセスすると、デモページが表示されます。

http://localhost:9876/echo 

ポート番号はconf/application.confで設定したものです。

[テストページ]

テストページ

このように、送信したメッセージがWebSocket経由でechoされてページに表示されます。

また、

http://localhost:9876/@tests 

から、作成したSeleniumテストケースを実行することができます。

[Seleniumテストの実行例]

Seleniumテストの実行例

このように、ブラウザのWebSocket実装を通したテストもブラウザ上で実行できます。

まとめ

この記事では、Play frameworkのWebSocketサポートについて説明しました。

  • WebSocketはWebブラウザとWebサーバの双方向通信のための規格です。
  • Play frameworkでは、WebSocketControllerを実装するだけで既存のWebアプリに双方向通信機能を後づけできます。
  • Play frameworkでWebSocketを利用したechoサーバとテス トページを実装しました

Play frameworkなら、WebSocketを活用したWebアプリがさくっと作れそうな気がしますね!(ΦωΦ)

ソースコードはコチラ(GitHub)