Jetty 9.0.3でWebSocket

Jetty 9 – Updated WebSocket API | Jetty & Cometd Blogs

を基にWebSocketサンプルを作成したものの、微妙にAPIのパッケージ名が変わっていたりして混乱したので、ちょっとメモしてみる。

とりあえずpom.xmlmaven v2.0.9で動作確認)

<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>sample</groupId>
    <artifactId>WebSocketSample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>WebSocketSample</name>

    <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.0.3.v20130506</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-servlet</artifactId>
            <version>9.0.3.v20130506</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-common</artifactId>
            <version>9.0.3.v20130506</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-api</artifactId>
            <version>9.0.3.v20130506</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-client</artifactId>
            <version>9.0.3.v20130506</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</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.0.3.v20130506</version>
                <configuration>
                    <contextPath>/</contextPath>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <connectors>
                    </connectors>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-server</artifactId>
                        <version>9.0.3.v20130506</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-servlet</artifactId>
                        <version>9.0.3.v20130506</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-common</artifactId>
                        <version>9.0.3.v20130506</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-api</artifactId>
                        <version>9.0.3.v20130506</version>
                    </dependency>
                    <dependency>
                        <groupId>org.eclipse.jetty.websocket</groupId>
                        <artifactId>websocket-client</artifactId>
                        <version>9.0.3.v20130506</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
        <finalName>WebSocketSample</finalName>
    </build>

</project>

jetty-maven-pluginを使ってWebSocketを使用するため、プラグインでも依存性を指定してます(webで検索するとjetty-maven-pluginではWebSocketを使用できないっていう情報が引っかかりますが、誤り)。

web.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>

sample.servlet.WebSocketServletImplクラス

package sample.servlet;

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

public class WebSocketServletImpl extends WebSocketServlet{

    /**
     * generated by Eclipse.
     */
    private static final long serialVersionUID = 8202951174317989818L;

    @Override
    public void configure(WebSocketServletFactory factory) {
        //Listenerクラスとして、sample.WebSocketSampleを指定
        factory.register(sample.WebSocketSample.class);
        
    }
}


Listenerとして動くsample.WebSocketSampleクラス。

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;
    }
}

POJOにアノテーションを付けるタイプです。ここのサンプルとはアノテーションのパッケージ名が違うので注意。

最後に、Listenerを呼び出すサンプルクラスsample.WebSocketBroadcasterクラス。

package sample;

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

/**
 * 接続中の全てのClientに対してメッセージを送信するサンプルクラスです。
 * */
public class WebSocketBroadcaster {
    private static WebSocketBroadcaster INSTANCE = new WebSocketBroadcaster();
    private List<WebSocketSample> clients = new ArrayList<WebSocketSample>();


    private WebSocketBroadcaster(){
    }
    
    protected static WebSocketBroadcaster getInstance(){
        return INSTANCE;
    }

    /**
     * クライアントを追加します
     * */
    protected void join(WebSocketSample socket){
        clients.add(socket);
    }
    /**
     * クライアントを削除します
     * */
    protected void bye(WebSocketSample socket){
        clients.remove(socket);
    }

    /**
     * すべてのユーザへメッセージを送信します。
     * */
    protected void sendToAll(String message){
        for(WebSocketSample member: clients){
            member.getSession().getRemote().sendStringByFuture(message);
        }
    }
}

それにしても、jetty9.0と9.0.3でパッケージ名が結構違う・・・


あとはプロジェクトホームからコマンドラインで

mvn clean package jetty:run

と実行すればjettyプラグインが起動するはず。
jettyが起動したら、Chrome拡張のSimple WebSocket Clientを使って、

ws://localhost:8080/WebSocketSample

への接続を行った後にメッセージを送信すると、そのメッセージが戻ってくることが確認できるはずです。数分放って置くとコネクションタイムアウトになってクライアント側からもサーバ側からも接続が切れていることすらわからなくなるので注意が必要ですが。
タイムアウトを防ぐには、ポーリングっぽく定期的にクライアント-サーバ間でメッセージをやり取りするしか無いのかな・・・Ajaxのポーリングと違って、既存のコネクションを使いまわすのでそんなに負荷はかからないはずですが、何となく本末転倒感。