Spring 4 WebSocket 에서 이용자의 아이디를 참조하는 예
Spring 4부터 지원하는 WebSocketHandler 에서는 접속자에 대한 정보가 WebSocketSession에 저장되는데 이는 HttpSession 클래스와 다르기 때문에 이용자의 ID 등 개발자가 필요한 이용자의 정보를 저장할 때는 HttpSession에 저장해 둔 이용자의 ID를 WebSocketHandler의 WebSocketSession에 저장해두고 이용자를 관리할 수 있다. WebSocketHandler 를 구현한 클래스 안에서는 HttpServletRequest 등의 서블릿 관련 클래스를 사용할 수 없으므로 WebSocketHandler 보다 앞서 실행되는 HttpSessionHandshakeInterceptor 인터셉터를 이용하여 이용자의 HttpSession에 접속하고 세션에 저장된 내용을 WebSocketHandler로 전달해주면 WebSocketHandler 안에서도 이용자의 ID 등 필요한 이용자 정보를 사용하고 관리할 수 있게 된다
테스트 환경
Windows 7
JDK 1.8, Tomcat 8
Spring 4.0.9, STS, Maven
Eclipse
개요
Spring 4에서 지원하는 WebSocket 안에서 HttpSession을 참조할 필요가 있었는데, Spring 4가 지원하는 방법으로는 더 복잡해서
웹소켓 관련 라이브러리 (pom.xml 에 추가한다)
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.9.RELEASE</version>
</dependency>
WEB-INF/spring/servlet-context.xml (서블릿 설정파일)
웹소켓 핸들러 클래스(SimpleWebSocketHandler)가 호출되기 전에 HttpSession에 접속하여 이용자 아이디를 추출하는 기능을 인터셉터가 수행하도록 설정한다
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:websocket="http://www.springframework.org/schema/websocket" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd">
........
...........
<beans:bean id="simpleChatHandler" class="org.kdea.spring.demo.SimpleWebSocketHandler"/>
<websocket:handlers>
<websocket:mapping path="/chat" handler="simpleChatHandler"/>
<websocket:handshake-interceptors>
<beans:bean class="org.kdea.interceptor.HandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
.........
................
SimpleChatController.java (이용자가 /chat 으로 접속하면 웹소켓 클라이언트 페이지(wsclient.jsp)로 연결해주는 기능의 콘트롤러 클래스)
package org.kdea.spring.demo; import javax.servlet.http.HttpSession; import org.springframework.stereotype.*; import org.springframework.web.bind.annotation.*; @Controller public class SimpleChatController { @RequestMapping(value="/chat", method=RequestMethod.GET) public String chatForm(){ return "/wsclient"; } }
WEB-INF/views/wsclient.jsp ( 이 페이지가 웹브라우저에서 실행되면 웹소켓 서버에 접속을 시도하고 성공하면 메시지를 전달할 수 있다 ) 웹소켓 접속 URL에는 아래처럼 ?id=1234 처럼 파라미터를 추가할 수 있고 서버측으로 전달된 파라미터는 웹소켓 핸들러가 실행되기 전에 인터셉터에 의해서 추출될 수 있다
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Websocket Client</title> <script type="text/javascript" src="<c:url value="/resources/jquery-2.1.4.min.js"/>"></script> <script type="text/javascript"> $(function () { //var ws = new WebSocket("ws://192.168.8.32:8888/MavenWeb/chat"); // 인터셉터에 파라미터를 전달하려는 경우에는 아래처럼 할 수도 있다 var ws = new WebSocket("ws://192.168.8.32:8888/MavenWeb/chat?id=1234"); ws.onopen = function () { $('#chatStatus').text('Info: connection opened.'); $('input[name=chatInput]').on('keydown', function(evt){ if(evt.keyCode==13){ var msg = $('input[name=chatInput]').val(); ws.send(msg); $('input[name=chatInput]').val(''); } }); }; ws.onmessage = function (event) { $('textarea').eq(0).prepend(event.data+'\n'); }; ws.onclose = function (event) { $('#chatStatus').text('Info: connection closed.'); }; }); </script> </head> <body> <p> <div id='chatStatus'></div> <textarea name="chatMsg" rows="5" cols="40"></textarea> <p> 메시지 입력 : <input type="text" name="chatInput"> </body> </html>
HandshakeInterceptor.java ( HttpSessionHandshakeInterceptor 인터셉터는 HttpServletRequest에 접근할 수 있기 때문에 요청 파라미터에 포함된 이용자 아이디를 추출하여 뒤이어 실행될 웹소켓 핸들러에게 전달할 수 있다 )
package org.kdea.interceptor; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{ @Override public boolean beforeHandshake(ServerHttpRequest request,ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { // 위의 파라미터 중, attributes 에 값을 저장하면 웹소켓 핸들러 클래스의 WebSocketSession에 전달된다 System.out.println("Before Handshake"); ServletServerHttpRequest ssreq = (ServletServerHttpRequest) request; System.out.println("URI:"+request.getURI()); HttpServletRequest req = ssreq.getServletRequest(); /*String userId = req.getParameter("userid"); System.out.println("param, id:"+userId); attributes.put("userId", userId);*/ // HttpSession 에 저장된 이용자의 아이디를 추출하는 경우 String id = (String)req.getSession().getAttribute("id"); attributes.put("userId", id); System.out.println("HttpSession에 저장된 id:"+id); return super.beforeHandshake(request, response, wsHandler, attributes); } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { System.out.println("After Handshake"); super.afterHandshake(request, response, wsHandler, ex); } }
SimpleWebSocketHandler.java ( 클라이언트 측에서 접속하거나 메시지를 전송할 때마다 콜백 메소드가 호출되는 서버측 핸들러 클래스) 인터셉터에 의해 추출되어 웹소켓 핸들러에게 전달된 데이터는 WebSocketSession 을 이용해 받을 수 있다
package org.kdea.spring.demo; import java.util.*; import org.springframework.web.socket.*; import org.springframework.web.socket.handler.TextWebSocketHandler; public class SimpleWebSocketHandler extends TextWebSocketHandler { // 웹소켓 서버측에 텍스트 메시지가 접수되면 호출되는 메소드 @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { String payloadMessage = (String) message.getPayload(); System.out.println("서버에 도착한 메시지:"+payloadMessage); session.sendMessage(new TextMessage("ECHO : " + payloadMessage)); Map<String, Object> map = session.getAttributes(); String userId = (String)map.get("userId"); System.out.println("전송자 아이디:"+userId); } // 웹소켓 서버에 클라이언트가 접속하면 호출되는 메소드 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { super.afterConnectionEstablished(session); System.out.println("클라이언트 접속됨"); Map<String, Object> map = session.getAttributes(); String id = (String)map.get("userId"); } // 클라이언트가 접속을 종료하면 호출되는 메소드 @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { super.afterConnectionClosed(session, status); System.out.println("클라이언트 접속해제"); } // 메시지 전송시나 접속해제시 오류가 발생할 때 호출되는 메소드 @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { super.handleTransportError(session, exception); System.out.println("전송오류 발생"); } }
실행 테스트
http://192.168.8.32:8080/컨텍스트명/chat 으로 접속하면 SimpleChatController 가 실행되고 wsclient.jsp가 화면에 출력된다. wsclient.jsp 에서는 /simpleChat?id=1234으로 웹소켓 접속을 시도하므로 /simpleChat 과 연결된 SimpleWebSocketHandler 가 호출되는데, 이에 앞서 SimpleWebSocketHandler에 등록된 인터셉터인 HandshakeInterceptor 가 먼저 실행되어 요청 파라미터에 포함된 이용자 아이디를 추출하여 뒤 이어 실행될 SimpleWebSocketHandler 에게 전달하는 절차가 수행된다
주의할 점은 테스트할 때 IP주소나 도메인 중 한가지를 사용해야 하고 localhost를 사용하면 접속이 안되거나 세션이 중간에 새로 생성되는 경우도 있기 때문에 세션에 저장된 데이터가 웹소켓핸들러에 전달되지 않을 수도 있다