내용 목차
본 장에서는 WebSocket 컨테이너의 개념과 기능, 설정 방법에 대해 설명한다.
WebSocket 컨테이너는 웹 엔진에 내부적으로 포함되어 있는 형태이며 웹 컨텍스트마다 하나씩 존재한다. WebSocket 컨테이너를 사용하기 위해서는 반드시 jeus-web-dd.xml에 <websocket>을 설정해야 한다.
JEUS에서 제공하는 WebSocket 컨테이너는 JSR 356, Java API for WebSocket을 준수한다. 따라서 서버의 WebSocket 서비스는 해당 표준에서 정의한 API에 따라 작성한다. 단, 본 장에서는 WebSocket RFC6455 및 Java API for WebSocket 표준에 대한 설명은 포함하지 않는다.
1. HTML5 WebSocket API는 클라이언트 라이브러리이므로 WebtoB 및 JEUS와는 관계없다.
2. Java API for WebSocket은 Java EE 7에 포함되어 있다. Java EE 6 API 클래스들을 참조해서 개발할 경우 WebSocket API를 참조할 수 없다. 이때는 lib/system/javaee.jar를 classpath로 지정한다. 여기에 javax.websocket 패키지가 포함되어 있다.
WebSocket 컨테이너의 기본적인 역할은 웹 컨텍스트에 포함된 WebSocket Server Endpoint 개체들을 deploy해주고, 클라이언트로부터 WebSocket Handshake 요청이 왔을 때 이에 매핑되는 Server Endpoint 개체를 연결시켜주는 것이다. 클라이언트와 WebSocket 연결이 맺어지면 WebSocket Session이 생성되는데 이 Session 개체에 대한 라이프 사이클도 관리해준다.
서비스 개발자의 역할은 WebSocket Server Endpoint 클래스(javax.websocket.Endpoint) 및 Configuration 클래스(javax.websocket.server.ServerApplicationConfig)를 작성해서 웹 애플리케이션에 패키징하는 것이다.
JEUS 7에서 WebSocket 컨테이너를 사용할 때는 다음과 같은 제약 사항이 존재한다.
HTTP 리스너에서만 사용 가능하다. 그러므로 WebtoB 4.1.8 이상 버전의 Reverse Proxy와 결합해서 사용하는 것을 권장한다. WebtoB Reverse Proxy에서의 WebSocket 지원에 관한 내용은 WebtoB 매뉴얼을 참고한다.
jeus-web-dd.xml의 <websocket> 설정이 반드시 필요하다.
Java API for WebSocket 표준에서 제공하는 Client API는 제공하지 않는다. Client API는 반드시 Java SE 7을 필요로 한다. JEUS 7은 Java EE 6 기반 제품으로 Java SE 6로 컴파일해서 제공하므로 Client API를 제공할 수 없다. Client API에 해당하는 메소드를 호출하는 경우에는 java.lang.UnsupportedOperationException이 발생한다.
Client API는 javax.websocket.WebSocketContainer#connectToServer() 메소드들이다.
본 절에서는 WebSocket 컨테이너는 부가적인 기능에 대해 설명한다.
WebSocket 컨테이너는 WebSocket Session별로 메모리에 데이터를 저장할 수 있는 공간을 제공한다. javax.websocket.Session.getUserProperties() API를 호출해서 얻은 Map 객체(이하 UserProperties)가 그 공간을 나타낸다.
WebSocket 애플리케이션(WebSocket Server Endpoint가 포함된 웹 컨텍스트)을 서로 다른 두 서버에 deploy한 상황이라고 가정하면 한쪽 서버에 WebSocket을 연결해서 UserProperties에 데이터를 저장하면서 사용했는데 그 서버가 비정상 종료되면 데이터가 모두 사라지게 된다. 이를 만약 다른 서버에 백업해둔다면 한쪽 서버가 비정상 종료되더라도 다른 서버로 WebSocket을 연결해서 UserProperties를 복원할 수 있다. WebSocket 서비스 사용자 입장에서는 아무 문제없이 서비스를 사용할 수 있다. 이를 WebSocket Session Failover나 Distributed WebSocket Session이라고 한다.
이 기능을 사용하기 위해서는 다음과 같은 조건들을 만족해야 한다.
WebSocket Session Failover는 HTTP 세션 서비스를 기반으로 동작한다. WebSocket 애플리케이션이 반드시 HTTP 세션 클러스터에 deploy되어야 한다. HTTP 세션 클러스터에 대한 자세한 내용은 “JEUS 세션 관리 안내서”의 “2.8. 세션 클러스터링”을 참고한다.
WebSocket 애플리케이션의 jeus-web-dd.xml 설정 파일에 다음과 같이 기술되어야 한다.
<websocket> <distributed-session enabled="true"/> </websocket>
즉, 기본적으로는 WebSocket Session과 HTTP Session을 연동하지 않는다.
UserProperties에 put하는 데이터는 Serializable해야 한다.
WebSocket 연결은 HTTP 요청으로 시작하는데 이때 WebSocket Handshake 요청의 Cookie 헤더에 JSESSIONID가 있어야 한다. JSESSIONID가 가리키는 HTTP 세션은 Server Endpoint가 포함된 웹 컨텍스트에 생성되어 있어야 한다.
예를 들어 HTML5 WebSocket API를 호출하도록 작성된 JSP를 호출하면 HTTP 세션이 생성되어 응답 Cookie 헤더로 JESSIONID가 전달된다. 웹 브라우저에서 HTML5 WebSocket JavaScript를 실행하면서 JEUS로 WebSocket Handshake 요청을 보내는데, 그 요청 헤더에 Cookie 헤더가 들어가야 한다. 이를 지원하는 않는 WebSocket Client를 사용하면 WebSocket Session Failover 기능을 사용할 수 없다.
FireFox 30.0은 HTML5 WebSocket API를 사용할 때 WebSocket Handshake 요청에 Cookie 헤더를 붙여준다.
GET /service/chat HTTP/1.1 Connection: keep-alive, Upgrade Cookie: JSESSIONID=FheDy8e0bOTPO7KNqdeJ7Eps8j51CaQqcRWHvpYo9mdVw1BCSwlwrFiyrclsolkr.amV1czcvc2VydmVyMg== Sec-WebSocket-Key: Csv/FCQo1g1eZfqMtPd8+g== Sec-WebSocket-Version: 13 Upgrade: websocket User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0 FirePHP/0.7.4
WebtoB Reverse Proxy와 함께 이 기능을 사용할 경우 Reverse Proxy 절에서 Session Routing 설정을 해야 한다. 이에 대한 자세한 사항은 WebtoB 매뉴얼을 참고한다.
이 기능을 사용할 때는 다음과 같은 사항을 주의해야 한다.
HTTP Session의 timeout과 WebSocket Session의 timeout은 서로 독립적이다. 특히 WebSocket Session의 경우 HTTP Session에 의존적으로 동작하는 것이기 때문에 WebSocket 컨테이너 차원에서 HTTP Session의 속성을 마음대로 바꿀 수는 없다. 상황에 따라서는 UserProperties에 Serialzable한 데이터를 put하는 시점에 HTTP Session이 먼저 timeout 처리돼서 Failover가 되지 않을 수 있다. 이러한 상황을 고려해서 HTTP Session의 timeout을 적절하게 조정해야 한다.
UserProperties에 Serialzable한 데이터를 put하는 시점에 HTTP Session이 timeout된 경우에는 WARNING 레벨의 로그를 남긴다.
서로 다른 웹 컨텍스트 간에 WebSocket Session의 UserProperties는 공유되지 않는다.
Session.getUserProperties()의 리턴값인 Map<String, Object>에서 아래와 같은 정보들을 얻을 수 있다.
항목 | 설명 |
---|---|
jeus.websocket.remoteAddr(String) | HTTP 요청 헤더에 Forwarded 또는 X-Forwarded-For가 있는 경우에는 해당 헤더의 값을 사용한다. 없는 경우에는 HttpServletRequest.getRemoteAddr() 값을 사용한다. |
jeus.websocket.remoteHost(String) | HTTP 요청 헤더에 Forwarded 또는 X-Forwarded-For가 있는 경우에는 해당 헤더의 값을 사용한다. 없는 경우에는 HttpServletRequest.getRemoteHost()와 같다. |
jeus.websocket.remotePort(String) | HTTP 요청 헤더에 Forwarded 또는 X-Forwarded-For가 있는 경우에는 해당 헤더의 값에 포함된 포트를 사용한다. 만약 헤더는 있는데 포트 정보가 없다면 이 프로퍼티는 제공하지 않는다. 헤더가 없는 경우에는 HttpServletRequest.getRemotePort() 값을 사용하되, 0보다 큰 경우에만 유효하다. |
WebSocket 컨테이너의 정보는 각 웹 애플리케이션의 jeus-web-dd.xml에 설정한다.
[예 6.1] 웹 컨텍스트 설정 파일 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="7.0"> <websocket> <max-incoming-binary-message-buffer-size>8192</max-incoming-binary-message-buffer-size> <max-incoming-text-message-buffer-size>8192</max-incoming-text-message-buffer-size> <max-session-idle-timeout-in-millis>1800000</max-session-idle-timeout-in-millis> <monitoring-period-in-millis>300000</monitoring-period-in-millis> <blocking-send-timeout-in-millis>10000</blocking-send-timeout-in-millis> <async-send-timeout-in-millis>30000</async-send-timeout-in-millis> <websocket-executor> <min>10</min> <max>30</max> <keep-alive-time>60000</keep-alive-time> <queue-size>4096</queue-size> </websocket-executor> <distributed-session> <enabled>false</enabled> <use-write-through-policy>false</use-write-through-policy> </distributed-session> <init-param> <name>name</name> <value>value</value> </init-param> <batching-buffer-size>655536</batching-buffer-size> <websocket-timeout-min-threads>1</websocket-timeout-min-threads> </websocket> </jeus-web-dd>
다음은 설정 태그에 대한 설명이다.
본 절에서는 JEUS에서 Spring WebSocket을 사용하는 방법에 대해서 설명한다.
Spring WebSocket은 웹 엔진에서 제공하는 WebSocket 컨테이너를 사용하면서 추가적인 기능을 제공하는 프레임워크이다. JSR 356, Java API for WebSocket을 준수하는 WebSocket 컨테이너는 동일한 인터페이스를 제공하나 WebSocket 컨테이너를 얻는 방법은 각 벤더별로 차이가 있다.
Spring WebSocket에서는 현재 특정 벤더에 대해서만 WebSocket 컨테이너를 얻는 방법을 지원한다. 따라서 JEUS에서는 Spring WebSocket을 사용하기 위한 별도의 라이브러리를 제공하며 이 라이브러리를 사용하기 위한 추가 설정이 필요하다.
현재 JEUS와 함께 제공되는 라이브러리는 Spring 프레임워크 4.2.0 이상의 버전을 지원한다.
애플리케이션에 Spring WebSocket 지원 라이브러리를 추가하기 위한 설정을 한다.
[예 6.2] 웹 컨텍스트 설정 파일 : <<jeus-web-dd.xml>>
<jeus-web-dd xmlns="http://www.tmaxsoft.com/xml/ns/jeus" version="7.0"> <library-ref> <library-name>spring-support</library-name> <specification-version> <value>4.2.0.RELEASE</value> </specification-version> <failonerror>true</failonerror> </library-ref> <websocket/> </jeus-web-dd>
Spring WebSocket 지원 라이브러리를 사용하기 위하여 websocket handshake-handler로 jeus.spring.websocket.JeusHandshakeHandler를 지정해준다.
[예 6.3] 스프링 컨텍스트 설정 파일 : <<spring-context.xml>>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:websocket="http://www.springframework.org/schema/websocket" xmlns:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.2.xsd"> <websocket:handlers> <websocket:mapping path="/chat" handler="chatHandler"/> <websocket:handshake-handler ref="handshakeHandler"/> </websocket:handlers> <bean id="chatHandler" class="com.tmax.jeus.ChatHandler"/> <bean id="handshakeHandler" class="jeus.spring.websocket.JeusHandshakeHandler"/> </beans>