JettyでWebSocket
Why?
先日インストール方法を投稿したのでやってみました。
結果的にjarを起動するものになったので
jettyのサーバへのインストールは不要だったというオチなんだけど同じ罠にハマる人もいるかと。
主にこちらを参考にさせて頂きました。
ツール類
Mavenプロジェクトで作ります。バージョン依存とか超面倒なんで。
JavaのコーディングにはIntelliJを使ってます。I'm JetBrainser.
ソースのパスはmavenの基本的な構成に沿って配置していきます。
Introduction to the Standard Directory Layout
プロジェクトはWebSocketSampleに配置していますよ。
src
pom
もろもろ定義していきます。
jetty-maven-pluginを使います。ビルドも定義しちゃいます。
もろもろ情報を集めるとローカル上で動かすものが多く罠にハマったんだけど、
サーバ上にデプロイして実行するタイプの場合は、warでなくjarを生成して
アップロード、java -jarで実行するという流れになります。
で、ライブラリ周りのパスがローカルとサーバ上で上手く噛み合わず、
だったらmaven-assembly-pluginで依存ライブラリ(jetty内のもの含む)を詰めたjarを作ってしまえという事になった。
なんでサーバ上のjettyは起動しなくて良し。
それでも諦められないアナタのためにlib含めないjarの出力も設定しました。
- /pom.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として起動するんでエンドポイントが必要っす。
サーブレットを起動します。
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
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
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
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 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側に用意されているクラスをそのまま使ったシンプルなものですね。
ローカルで動作チェック
ローカルのプロジェクトルートにて
# mvn clean package
を叩いてみましょう。初回は存在しないライブラリ等がインストールされて、ビルドが完了します。
# java -jar target/WebSocketSample-jar-with-dependencies.jar
でWebsocketサーバを起動します。ライブラリ込なんでパスとかもう関係ない。
jetty上で起動したい場合は
# mvn jetty:run
で試せる。うまく起動できれば
[INFO] Started Jetty Server
[INFO] Starting scanner at interval of 10 seconds.
となって起動OKです。
ローカルでてっとり早くデバッグするならChromeのWebSocketClientの拡張をインストールして
ws://localhost:8080/
※jetty上の場合は ws://localhost:8080/WebSocketSample
でConnectしてStatusがOpenedになれば接続成功、
Requestを送信して同じ文字列が戻ってくればBroadCastが成功となりますよ。
デプロイ
サーバにアップして同様に
# java -jar WebSocketSample-jar-with-dependencies.jar
すれば起動完了です。javaは1.7なんだからね!
うまく接続できない場合はポートとか確認してみると良いです。
以上、次回はSSL対応とEC2のELB対応します。