본문 바로가기

Spring 4/WebSocket with Interceptor

Spring 4 WebSocket Param

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.javaHttpSessionHandshakeInterceptor 인터셉터는 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를 사용하면 접속이 안되거나 세션이 중간에 새로 생성되는 경우도 있기 때문에 세션에 저장된 데이터가 웹소켓핸들러에 전달되지 않을 수도 있다