感謝のプログラミング 10000時間

たどり着いた結果(さき)は、感謝でした。

Jettyで超簡単にWebSocketサーバを作るサンプル

<スポンサーリンク>

Jettyというサーブレットコンテナを使って、java -jar XXXX.jarで実行できるような軽いサーブレットを立ち上げ、
そこでWebSocket接続を待ち受けるサービスを立ち上げたいと思います。

今回のサンプルのディレクトリ構成は以下の通りです。

f:id:sho322:20170622224746j:plain


eclipseでMavenプロジェクトを作り、以下のようにpom.xmlを編集してください。

<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>websocket</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>websocket</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <jetty.version>9.2.11.v20150529</jetty.version>
  </properties>
  <build>
      <plugins>
          <plugin>
              <groupId>org.eclipse.jetty</groupId>
              <artifactId>jetty-maven-plugin</artifactId>
              <version>${jetty.version}</version>
          </plugin>
      </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!--Jetty dependencies start here -->
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>${jetty.version}</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-servlet</artifactId>
        <version>${jetty.version}</version>
    </dependency>

	<!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-webapp -->
	<dependency>
	    <groupId>org.eclipse.jetty</groupId>
	    <artifactId>jetty-webapp</artifactId>
	    <version>${jetty.version}</version>
	</dependency>

    <!--Jetty Websocket API server side dependency -->
    <dependency>
        <groupId>org.eclipse.jetty.websocket</groupId>
        <artifactId>websocket-server</artifactId>
        <version>${jetty.version}</version>
    </dependency>

  </dependencies>
</project>

Jettyを立ち上げるためのスタートポイントを作ります。
ここにはmainメソッドがあります。

JettyLauncherはGitBucketのソースを参考にしました。
https://github.com/gitbucket/gitbucket

package sample.websocket;

import java.net.InetSocketAddress;
import java.net.URL;
import java.security.ProtectionDomain;

import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.webapp.WebAppContext;

public class JettyLauncher {
    public static void main(String[] args) throws Exception {
        System.out.println("Hello Jetty!");
        System.out.println("Jetty is running -> localhost:8888");

        String host = null;
        int port = 8888;
        String contextPath = "/";
        boolean forceHttps = false;
        InetSocketAddress address = null;

        if (host != null) {
        	address = new InetSocketAddress(host, port);
        } else {
        	address = new InetSocketAddress(port);
        }

        Server server = new Server(address);

        for (Connector connector : server.getConnectors()) {
            for (ConnectionFactory factory : connector.getConnectionFactories()) {
                if (factory instanceof HttpConnectionFactory) {
                    ((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
                }
            }
        }
        WebAppContext context = new WebAppContext();
        ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
        URL location = domain.getCodeSource().getLocation();

        context.setContextPath(contextPath);
        context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
        context.setServer(server);
        context.setWar(location.toExternalForm());
        if (forceHttps) {
            context.setInitParameter("org.scalatra.ForceHttps", "true");
        }

        Handler handler = addStatisticsHandler(context);

        server.setHandler(handler);
        server.setStopAtShutdown(true);
        server.setStopTimeout(7000);
        server.start();
        server.join();
    }

    private static Handler addStatisticsHandler(Handler handler) {

        final StatisticsHandler statisticsHandler = new StatisticsHandler();
        statisticsHandler.setHandler(handler);
        return statisticsHandler;
    }
}

上記でコンテクストパスを設定しているため、/WEB-INF/web.xmlを以下のように記載します。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0">

  <servlet>
    <servlet-name>WebSocketServlet</servlet-name>
    <servlet-class>sample.websocket.servlet.ToUpperWebSocketServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>WebSocketServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

次に、WebSocketを通信を行うサービスを作ります。

package sample.websocket.service;

import java.io.IOException;

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 ToUpperWebSocketService {

	@OnWebSocketMessage
	public void onText(Session session, String message) throws IOException {
		System.out.println("Message Received:" + message);
		if (session.isOpen()) {
			String response = message.toUpperCase();
			session.getRemote().sendString(response);
		}
	}

	@OnWebSocketConnect
	public void onConnect(Session session) throws IOException {
		System.out.println(session.getRemoteAddress().getHostString() + "connected!");
	}

	@OnWebSocketClose
	public void onClose(Session session, int status, String reason) {
		System.out.println(session.getRemoteAddress().getHostString() + "closed!");
	}
}

受け取った文字列を大文字にして返します。

そして、WebSocketのリクエストを受け取るサーブレットを作ります。
このサーブレットはweb.xmlに定義します。

package sample.websocket.servlet;

import javax.servlet.annotation.WebServlet;

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

import sample.websocket.service.ToUpperWebSocketService;

@WebServlet(urlPatterns="/toUpper")
public class ToUpperWebSocketServlet extends WebSocketServlet{
	@Override
	public void configure(WebSocketServletFactory factory) {
		System.out.println("register ToUpperWebSocket class to WebSocketFactory...");
		factory.register(ToUpperWebSocketService.class);

	}
}


これでサーバサイドは完成です。
次にクライアント側のソースコードを作りましょう。

以下のサイトを参考にしています。
https://examples.javacodegeeks.com/enterprise-java/jetty/jetty-websocket-example/

<html>
<body>
	<div>
		<input type="text" id="input" />
	</div>
	<div>
		<input type="button" id="connectBtn" value="CONNECT"
			onclick="connect()" /> <input type="button" id="sendBtn"
			value="SEND" onclick="send()" disabled="true" />
	</div>
	<div id="output">
		<p>Output</p>
	</div>
</body>

<script type="text/javascript">
	var webSocket;
	var output = document.getElementById("output");
	var connectBtn = document.getElementById("connectBtn");
	var sendBtn = document.getElementById("sendBtn");

	function connect() {
		// open the connection if one does not exist
		if (webSocket !== undefined
				&& webSocket.readyState !== WebSocket.CLOSED) {
			return;
		}
		// Create a websocket
		webSocket = new WebSocket("ws://localhost:8888/toUpper");

		webSocket.onopen = function(event) {
			updateOutput("Connected!");
			connectBtn.disabled = true;
			sendBtn.disabled = false;

		};

		webSocket.onmessage = function(event) {
			updateOutput(event.data);
		};

		webSocket.onclose = function(event) {
			updateOutput("Connection Closed");
			connectBtn.disabled = false;
			sendBtn.disabled = true;
		};
	}

	function send() {
		var text = document.getElementById("input").value;
		webSocket.send(text);
	}

	function closeSocket() {
		webSocket.close();
	}

	function updateOutput(text) {
		output.innerHTML += "<br/>" + text;
	}
</script>
</html>

eclipse上からjavaを実行し、サーバを起動します。
それから、sample.htmlのようなファイルに上記のhtmlを保存し、それをChromeで開くと、以下のような画面が表示されます。
f:id:sho322:20170622224758j:plain

CONNECTしてからSENDを押すと、大文字になった文字が返ってきます。

まずここまでで、超かんたんなWebSocketのサンプルができました。
これを使って、今度はサーバからのプッシュ通信を実現してみましょう。