1. 소개
- 웹소켓이란?
- Jetty에서 WebSocket을 사용하기 위한 간단한 예제
- Jetty 9 기준
- 참고 사이트: https://github.com/jetty-project/embedded-jetty-websocket-examples/tree/master/native-jetty-websocket-example/src/main/java/org/eclipse/jetty/demo
이 예제는 내장형 Jetty 서버를 띄우고, 서버와 클라이언트가 같은 WebSocket 구현체를 사용해 메시지를 주고받는 흐름을 확인하는 데 목적이 있다. 서버는 /wstest/* 경로로 WebSocket 요청을 받고, 클라이언트는 ws://localhost:8080/wstest/로 접속한다.
2. 프로젝트
메이븐 프로젝트 기반이다. 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>io.sarc</groupId>
<artifactId>jettywebsocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jettywebsocket</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>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<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>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-server-impl</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-client-impl</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>io.sarc.jettywebsocket.WSServer</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
pom.xml 파일의 build 부분은 실행 가능한 jar를 만들기 위한 설정이므로, IDE에서 직접 실행한다면 무시해도 된다. 핵심은 Jetty 서버, Servlet, WebSocket 서버/클라이언트 관련 의존성이 포함되어 있다는 점이다.
3. 코드
총 4개의 코드로 구성되어 있다.
3-1. WSServer
WSServer는 8080 포트로 Jetty 서버를 실행하고, /wstest/* 경로에 WebSocket Servlet을 등록한다.
package io.sarc.jettywebsocket;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
public class WSServer {
public static void main(String[] args) {
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
ServletHolder holderEvents = new ServletHolder("ws-tests", WSServlet.class);
context.addServlet(holderEvents, "/wstest/*");
try {
server.start();
server.dump(System.err);
server.join();
} catch ( Throwable t ) {
t.printStackTrace(System.err);
}
}
}
3-2. WSSocket
WSSocket은 연결, 메시지 수신, 종료, 오류 이벤트를 처리한다. 이 예제에서는 수신한 메시지를 콘솔에 출력하는 정도만 구현했다.
package io.sarc.jettywebsocket;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
public class WSSocket extends WebSocketAdapter {
@Override
public void onWebSocketConnect(Session sess) {
super.onWebSocketConnect(sess);
System.out.println("- Socket Connected: " + sess);
}
@Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
System.out.println("- Received Text message: " + message);
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
super.onWebSocketClose(statusCode, reason);
System.out.println("- Socket Closed: [" + statusCode + "] " + reason);
}
@Override
public void onWebSocketError(Throwable cause) {
super.onWebSocketError(cause);
cause.printStackTrace(System.err);
}
}
3-3. WSServlet
WSServlet은 실제 WebSocket 클래스를 Jetty의 WebSocketServletFactory에 등록한다.
package io.sarc.jettywebsocket;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@SuppressWarnings("serial")
public class WSServlet extends WebSocketServlet {
@Override
public void configure(WebSocketServletFactory factory) {
factory.register(WSSocket.class);
}
}
3-4. WSClient
WSClient는 서버에 접속한 뒤 문자열 메시지를 전송하고 세션을 닫는다. 서버가 먼저 실행 중이어야 정상적으로 연결된다.
package io.sarc.jettywebsocket;
import java.net.URI;
import java.util.concurrent.Future;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
public class WSClient {
public static void main(String[] args) {
URI uri = URI.create("ws://localhost:8080/wstest/");
WebSocketClient client = new WebSocketClient();
try {
try {
client.start();
WSSocket socket = new WSSocket();
Future future = client.connect(socket, uri);
Session session = future.get();
session.getRemote().sendString("Hello, sarc.io!");
session.close();
} finally {
client.stop();
}
} catch ( Throwable t ) {
t.printStackTrace(System.err);
}
}
}
4. 테스트
테스트는 서버를 먼저 실행한 뒤 클라이언트를 실행하는 순서로 진행한다. 클라이언트가 메시지를 보내면 서버 콘솔에서 Hello, sarc.io! 문자열이 출력되는지 확인하면 된다.
4-1. WSServer 실행
WSServer를 실행하면 8080 포트에서 요청을 대기한다. 실행 중인 다른 프로세스가 이미 8080 포트를 사용하고 있다면 포트를 변경해야 한다.
4-2. WSClient 실행
2018-05-01 23:49:55.631:INFO::main: Logging initialized @131ms
- Socket Connected: WebSocketSession[websocket=JettyListenerEventDriver[io.sarc.jettywebsocket.WSSocket],behavior=CLIENT,connection=WebSocketClientConnection@49ef82ec{IDLE}{f=Flusher[queueSize=0,aggregateSize=0,failure=null],g=Generator[CLIENT,validating],p=Parser@580d8488[ExtensionStack,s=START,c=0,len=0,f=null,p=WebSocketPolicy@43b5e282[behavior=CLIENT,maxTextMessageSize=65536,maxTextMessageBufferSize=32768,maxBinaryMessageSize=65536,maxBinaryMessageBufferSize=32768,asyncWriteTimeout=60000,idleTimeout=300000,inputBufferSize=4096]]},remote=WebSocketRemoteEndpoint@5faa27[batching=true],incoming=JettyListenerEventDriver[io.sarc.jettywebsocket.WSSocket],outgoing=ExtensionStack[queueSize=0,extensions=[],incoming=org.eclipse.jetty.websocket.common.WebSocketSession,outgoing=org.eclipse.jetty.websocket.client.io.WebSocketClientConnection]]
- Socket Closed: [1001] Shutdown
4-3. WSServer 확인
- Socket Connected: WebSocketSession[websocket=JettyListenerEventDriver[io.sarc.jettywebsocket.WSSocket],behavior=SERVER,connection=WebSocketServerConnection@16ec75ac{IDLE}{f=Flusher[queueSize=0,aggregateSize=0,failure=null],g=Generator[SERVER,validating],p=Parser@3be2f06b[ExtensionStack,s=START,c=0,len=0,f=null,p=WebSocketPolicy@4148ea7d[behavior=SERVER,maxTextMessageSize=65536,maxTextMessageBufferSize=32768,maxBinaryMessageSize=65536,maxBinaryMessageBufferSize=32768,asyncWriteTimeout=60000,idleTimeout=300000,inputBufferSize=4096]]},remote=WebSocketRemoteEndpoint@7eac0734[batching=true],incoming=JettyListenerEventDriver[io.sarc.jettywebsocket.WSSocket],outgoing=ExtensionStack[queueSize=0,extensions=[],incoming=org.eclipse.jetty.websocket.common.WebSocketSession,outgoing=org.eclipse.jetty.websocket.server.WebSocketServerConnection]]
- Received Text message: Hello, sarc.io!
- Socket Closed: [1000] null
클라이언트 콘솔에서는 연결과 종료 로그를 확인하고, 서버 콘솔에서는 메시지 수신 로그를 확인한다. 종료 코드 1000은 정상 종료를 의미한다.
5. HTML Client로 테스트
서버는 그대로 두고 HTML 기반의 Client를 통해 테스트한다. 다음은 HTML Client 예제이다.
<!DOCTYPE HTML>
<html>
<head>
<script type = "text/javascript">
function WebSocketTest() {
if ("WebSocket" in window) {
alert("WebSocket is supported by your Browser!");
var ws = new WebSocket("ws://localhost:8080/wstest");
ws.onopen = function() {
ws.send("My message");
alert("Message is sent...");
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
alert("Message is received...");
};
ws.onclose = function() {
alert("Connection is closed...");
};
} else {
alert("WebSocket NOT supported by your Browser!");
}
}
</script>
</head>
<body>
<div id = "sse">
<a href = "javascript:WebSocketTest()">Run WebSocket</a>
</div>
</body>
</html>
위 HTML을 브라우저에서 열고 Run WebSocket을 클릭하면 서버로 My message가 전송된다. 서버 콘솔에 해당 문자열이 출력되면 HTML 클라이언트에서도 정상적으로 WebSocket 연결이 된 것이다.
단, 브라우저에서 테스트할 때도 Jetty 서버는 계속 실행 중이어야 하며, 접속 주소와 서버 포트가 예제의 ws://localhost:8080/wstest와 일치해야 한다.