JettyでWebSocket(ver9.1)

# JettyでWebSocket

## Why？

[先日インストール方法](http://qiita.com/supertaihei02/items/a53ce41d82c4b5ab0efa)を投稿したのでやってみました。
結果的にjarを起動するものになったので
jettyのサーバへのインストールは不要だったというオチなんだけど同じ罠にハマる人もいるかと。

主に[こちら](http://dev.classmethod.jp/ria/html5/websocket/)を参考にさせて頂きました。

### ツール類

Mavenプロジェクトで作ります。バージョン依存とか超面倒なんで。
JavaのコーディングにはIntelliJを使ってます。I'm JetBrainser.

ソースのパスはmavenの基本的な構成に沿って配置していきます。
[Introduction to the Standard Directory Layout](http://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html)

プロジェクトはWebSocketSampleに配置していますよ。


## src

### pom

もろもろ定義していきます。
jetty-maven-pluginを使います。ビルドも定義しちゃいます。
もろもろ情報を集めるとローカル上で動かすものが多く罠にハマったんだけど、
サーバ上にデプロイして実行するタイプの場合は、warでなくjarを生成して
アップロード、java -jarで実行するという流れになります。
で、ライブラリ周りのパスがローカルとサーバ上で上手く噛み合わず、
だったらmaven-assembly-pluginで依存ライブラリ(jetty内のもの含む)を詰めたjarを作ってしまえという事になった。
なんでサーバ上のjettyは起動しなくて良し。
それでも諦められないアナタのためにlib含めないjarの出力も設定しました。

* /pom.xml

```xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>WebSocketSample</groupId>
    <artifactId>WebSocketSample</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>WebSocketSample</name>
    <packaging>jar</packaging>
    <properties>
        <package.package-name>sample</package.package-name>
        <package.main-class>Main</package.main-class>
        <package.base-name>${name}-${version}</package.base-name>
        <package.jar-name>${name}.jar</package.jar-name>
    </properties>

    <dependencies>
        <dependency>
            <groupId>servletapi</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.4-20040521</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-server</artifactId>
            <version>9.1.1.v20140108</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-servlet</artifactId>
            <version>9.1.1.v20140108</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-common</artifactId>
            <version>9.1.1.v20140108</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-api</artifactId>
            <version>9.1.1.v20140108</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-client</artifactId>
            <version>9.1.1.v20140108</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <encoding>UTF-8</encoding>
                    <debug>true</debug>
                    <optimize>false</optimize>
                    <fork>true</fork>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.1.1.v20140108</version>
                <configuration>
                    <contextPath>/</contextPath>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <stopKey>TheKeyToStopJetty</stopKey>
                    <stopPort>9999</stopPort>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-server</artifactId>
                        <version>9.1.1.v20140108</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-servlet</artifactId>
                        <version>9.1.1.v20140108</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-common</artifactId>
                        <version>9.1.1.v20140108</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-api</artifactId>
                        <version>9.1.1.v20140108</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-client</artifactId>
                        <version>9.1.1.v20140108</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>${package.main-class}</mainClass>
                            <packageName>${package.package-name}</packageName>
                            <addClasspath>true</addClasspath>
                            <addExtensions>false</addExtensions>
                            <classpathPrefix></classpathPrefix>
                        </manifest>
                        <manifestEntries>
                            <Built-By></Built-By>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>${package.main-class}</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Class-Path></Class-Path>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <finalName>${name}</finalName>
    </build>
</project>
```

### Mainクラス

jettyでなくjarとして起動するんでエンドポイントが必要っす。
サーブレットを起動します。

```java
import sample.servlet.WebSocketServletImpl;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class Main {
    public static void main(String[] args) {
        System.out.println("WebSocket Server Init");
        Server server = new Server(8080);
        ResourceHandler resourceHandler = new ResourceHandler();
        // Set resource directory.
        resourceHandler.setResourceBase("./webapp");

        // websocket handler
        WebSocketServletImpl wsservlet = new WebSocketServletImpl();
        ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
        contextHandler.addServlet(new ServletHolder(wsservlet), "/");

        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] { resourceHandler, contextHandler });
        server.setHandler(handlers);

        try {
            server.start();
            System.out.println("Server Start");
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

}
```


### Servletクラス

まずはサーブレットから実装します。
といってもIFを実装してリスナーを定義するだけの簡単なお仕事。

* /src/main/java/sample/servlet.java

```java
package sample.servlet;

import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

public class WebSocketServletImpl extends WebSocketServlet{
    private static final long serialVersionUID = 1L;

    @Override
    public void configure(WebSocketServletFactory factory) {
        // Listener
        factory.register(sample.WebSocketSample.class);
    }
}
```

### リスナー

こちらもアノテーションだけ。
細かい処理は次で実装するWebSocketBroadcasterに全部お任せする感じでシンプルに。
servletの上のパッケージに置きます。

* /src/main/java/sample/WebSocketSample

```java
package sample;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

@WebSocket
public class WebSocketSample {
    private Session session;

    @OnWebSocketConnect
    public void onConnect(Session session) {
        this.session = session;
        WebSocketBroadcaster.getInstance().join(this);
    }

    @OnWebSocketMessage
    public void onText(String message) {
        WebSocketBroadcaster.getInstance().sendToAll(message);
    }

    @OnWebSocketClose
    public void onClose(int statusCode, String reason) {
        WebSocketBroadcaster.getInstance().bye(this);
    }

    public Session getSession(){
        return this.session;
    }
}
```

WebSocketのイベントに対するアクションを定義します。
今回はシンプルにクライアントを管理して受け取ったメッセージを全員に投げつける乱暴な子にします。

* /src/main/java/sample/servlet/WebSocketBroadcaster.java

```java
package sample;

import java.util.ArrayList;
import java.util.List;

public class WebSocketBroadcaster {
    private static WebSocketBroadcaster INSTANCE = new WebSocketBroadcaster();
    private List<WebSocketSample> clients = new ArrayList<WebSocketSample>();

    private WebSocketBroadcaster(){}

    protected static WebSocketBroadcaster getInstance(){
        return INSTANCE;
    }

    /**
     * Add Client
     * */
    protected void join(WebSocketSample socket){
        clients.add(socket);
    }
    /**
     * Delete Client
     * */
    protected void bye(WebSocketSample socket){
        clients.remove(socket);
    }

    /**
     * BroadCast to joined member
     * */
    protected void sendToAll(String message){
        for(WebSocketSample member: clients){
            member.getSession().getRemote().sendStringByFuture(message);
        }
    }
}
```

WEBアプリケーション設定を定義、
これはjetty側で起動した場合にservletをハンドルするためだったので不要かな。
一応残しておきます。
サーブレットとURIを指定するだけ。

* /src/main/webapp/WEB-INF/web.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="TaskList" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <servlet>
        <display-name>WebSocketSample</display-name>
        <servlet-name>WebSocketSample</servlet-name>
        <servlet-class>sample.servlet.WebSocketServletImpl</servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebSocketSample</servlet-name>
        <url-pattern>/WebSocketSample/*</url-pattern>
    </servlet-mapping>
</web-app>
```

以上です。ほぼほぼjetty側に用意されているクラスをそのまま使ったシンプルなものですね。

## ローカルで動作チェック

ローカルのプロジェクトルートにて

```bash
# mvn clean package
```

を叩いてみましょう。初回は存在しないライブラリ等がインストールされて、ビルドが完了します。

```bash
# java -jar target/WebSocketSample-jar-with-dependencies.jar
```

でWebsocketサーバを起動します。ライブラリ込なんでパスとかもう関係ない。

jetty上で起動したい場合は

```bash
# mvn jetty:run
```

で試せる。うまく起動できれば

> [INFO] Started Jetty Server
> [INFO] Starting scanner at interval of 10 seconds.

となって起動OKです。
ローカルでてっとり早くデバッグするならChromeの[WebSocketClientの拡張](https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo)をインストールして

>ws://localhost:8080/
>※jetty上の場合は ws://localhost:8080/WebSocketSample

でConnectしてStatusがOpenedになれば接続成功、
Requestを送信して同じ文字列が戻ってくればBroadCastが成功となりますよ。

## デプロイ

サーバにアップして同様に

```bash
# java -jar WebSocketSample-jar-with-dependencies.jar
```

すれば起動完了です。javaは1.7なんだからね！
うまく接続できない場合はポートとか確認してみると良いです。


以上、次回はSSL対応とEC2のELB対応します。