Playframework付属のサンプル'websocket-chat'を見てみる(1/4)

WebSocketを使ったこんなチャットのサンプルアプリがついてたので動かしてみた。2つのブラウザ間でほぼ同時にサクサクとチャットを表示してくれる。ローカルホストとはいえスゴイ。WebSocketという技術が個人的に気になりまくりんぐなので楽しみである。
![2014-08-06_1846.png](https://qiita-image-store.s3.amazonaws.com/0/48758/e1567803-e15d-3bcd-7212-102735072a6b.png)


appフォルダは以下のようになっている。
![2014-08-06_1608_001.png](https://qiita-image-store.s3.amazonaws.com/0/48758/0b34902f-d471-8b6d-e95b-ad5c870574be.png)

##Application.java
```java:Application.java
package controllers;

import play.mvc.*;

import com.fasterxml.jackson.databind.JsonNode; 
import views.html.*;

import models.*;

public class Application extends Controller {
  
    /**
     * Display the home page.
     */
    public static Result index() {
        return ok(index.render());
    }
  
    /**
     * Display the chat room.
     */
    public static Result chatRoom(String username) {
        if(username == null || username.trim().equals("")) {
            flash("error", "Please choose a valid username.");
            return redirect(routes.Application.index());
        }
        return ok(chatRoom.render(username));
    }

    public static Result chatRoomJs(String username) {
        return ok(views.js.chatRoom.render(username));
    }
    
    /**
     * Handle the chat websocket.
     */
    public static WebSocket<JsonNode> chat(final String username) {
        return new WebSocket<JsonNode>() {
            
            // Called when the Websocket Handshake is done.
            public void onReady(WebSocket.In<JsonNode> in, WebSocket.Out<JsonNode> out){
                
                // Join the chat room.
                try { 
                    ChatRoom.join(username, in, out);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        };
    }
  
}
```

* index
* chatRoom
* chatRoomJs
* chat

の4つのアクションが定義してある。chatRoomとchatRoomJsがチャットルームの表示、chatがWebSocketのオブジェクトを返してくれているっぽいのがわかる。routesでルーティングを確認してみよう。

##routes
```text:conf/routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                                controllers.Application.index()
GET     /room                            controllers.Application.chatRoom(username: String ?= null)
GET     /room/chat                       controllers.Application.chat(username)
GET     /assets/javascripts/chatroom.js  controllers.Application.chatRoomJs(username)

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file                    controllers.Assets.at(path="/public", file)
```

まず/にアクセスすると、indexが呼び出される。indexではindex.scala.htmlの内容をResultとして返す。

##index.scala.html
```scala:index.scala.html

@main(null) {
    
    @if(flash.containsKey("error")) {
        
        <div class="alert-message error">
            <p>
                <strong>Oops!</strong> @flash.get("error")
            </p>
        </div>
        
    }
        
    <div class="alert-message block-message info">
        <p>
            <strong>This is the Play Websocket sample application!</strong> 
            To start, choose a username and sign in using the top right form.
        </p>
    </div>
}
```
index.scala.htmlは、まずmain.scala.htmlを呼び出し、その中身として自分自身を表示しようとする。ベースとなるmain.scala.htmlにはめ込むイメージである。main.scala.htmlは2つの引数を受け取る(@(connected: String)(content: Html))が、一つ目の`(connected: String)`にあたるものが`@main(null)`の'null'、`(content: Html)`にあたるものが`@main(null) {...}`で囲まれた部分となる。

ここにはサーバへのアクセス処理は含まれていない模様。特筆するとすれば、`flash`というオブジェクトを使って一度きりのメッセージを表示している点。これはController側で

```java
flash("error", "Please choose a valid username.");
```
と定義しておくことでView側で使用できるようになる。
![2014-08-06_1712.png](https://qiita-image-store.s3.amazonaws.com/0/48758/9617d29f-3e55-1e82-4217-729eb80afce3.png)



##main.scala.html
```scala:main.scala.html
@(connected: String)(content: Html)

<!DOCTYPE html>

<html>
    <head>
        <title>Websocket Chat-Room</title>
        <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/bootstrap.css")">
        <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
        <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
        <script src="@routes.Assets.at("javascripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
    </head>
    <body>
        
        <div class="topbar">
            <div class="fill">
                <div class="container">
                    <a class="brand" href="@routes.Application.index()">Websocket Chat-Room</a>
                    
                    @if(connected != null) {
                        <p class="pull-right">
                            Logged in as @connected —
                            <a href="@routes.Application.index()">Disconnect</a>
                        </p>
                    } else {
                        <form action="@routes.Application.chatRoom()" class="pull-right">
                            <input id="username" name="username" class="input-small" type="text" placeholder="Username">
                            <button class="btn" type="submit">Sign in</button>
                        </form>
                    }
                    
                </div>
            </div>
        </div>

        <div class="container">

            <div class="content">
                @content
            </div>

            <footer>
                <p>
                    <a href="http://www.playframework.com">www.playframework.com</a>
                </p>
            </footer>

        </div>
        
    </body>
</html>
```
main.scala.htmlのハイライトは

```scala:main.scala.html
@if(connected != null) {
    <p class="pull-right">
        Logged in as @connected —
        <a href="@routes.Application.index()">Disconnect</a>
    </p>
} else {
    <form action="@routes.Application.chatRoom()" class="pull-right">
        <input id="username" name="username" class="input-small" 
            type="text" placeholder="Username">
        <button class="btn" type="submit">Sign in</button>
    </form>
}
```
の部分である。

ログインしているかどうか(connectedに文字列が入っているかどうか)で条件分岐し、ログインしていない場合はログイン用のフォーム(methodはGET)を作成、飛び先が`@routes.Application.chatRoom()`となっている。routesファイルを見ると、

```
GET    /room    controllers.Application.chatRoom(username: String ?= null)
```
となっており、今回QueryStringで送信するusername(String型)をchatRoomアクションで受け取るよう定義してあることがわかる。`?= null`の部分は送信された値がなかった場合のデフォルト値を指定している模様([ScalaRouting]
(http://www.playframework-ja.org/documentation/2.0.8/ScalaRouting))。

```java:Application.java
public static Result chatRoom(String username) {
    if(username == null || username.trim().equals("")) {
        flash("error", "Please choose a valid username.");
        return redirect(routes.Application.index());
    }
    return ok(chatRoom.render(username));
}
```
chatRoomアクションの飛び先では、ユーザ名が入力されているかをチェックし、OKならchatRoom.scala.htmlの内容をResultとして返している。これでチャットルームへの入室が完了となる。
![2014-08-06_1742.png](https://qiita-image-store.s3.amazonaws.com/0/48758/e3d05dde-163c-e5f0-1bcd-d00e3c8f8d15.png)

今日はここまで。→[続き](http://qiita.com/yyyske/items/0d00e5e352b7078a501e)

---
[Playframework付属のサンプル'websocket-chat'を見てみる(2/4)](http://qiita.com/yyyske/items/0d00e5e352b7078a501e)
[Playframework付属のサンプル'websocket-chat'を見てみる(3/4)](http://qiita.com/yyyske/items/87c49e925237a208373e)
[Playframework付属のサンプル'websocket-chat'を見てみる(4/4)](http://qiita.com/yyyske/items/a6f5090f555038d6ff01)