개발 환경
- JDK : Oracle JDK 1.8
- WS : Apache 2.4
- WAS : Apache Tomcat 9
웹 소켓을 이용해 채팅 프로그램을 만들었는데, 채팅 UI에서 사용자 이름을 표현하거나 채팅 로그를 남길 때 HttpSession에 저장해둔 로그인 사용자 정보를 이용하고 싶어서 구현해보았습니다.
우선 알아두어야할 것은 아래와 같습니다.
웹 소켓 세션과 서블릿 세션은 다릅니다. 때문에, 웹 소켓 세션에서 우리가 흔히 이야기하는 세션(HttpSession)에 접근할 수가 없습니다.
00. 사전 준비
우선 Apache에 웹 소켓 통신을 위한 세팅을 해주어야합니다.
아파치 설치경로/conf/httpd.conf 파일을 열어 아래 모듈들을 로드해주세요. 아마 대부분 작성이 되어있고 주석을 해제하시면 될겁니다.
1
2
3
|
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
|
cs |
그리고 아파치 설치경로/conf/extra/httpd-vhosts.conf 파일을 열어 VirtualHost에 아래 내용을 추가해주세요.
만약 VirtualHost를 httpd.conf에 직접 작성하신경우 해당 위치에 추가해주시면 됩니다.
1
2
3
4
|
<VirtualHost *:80>
...
ProxyPass /websocket/echo ws://127.0.0.1:8180/websocket/echo
</VirtualHost>
|
cs |
마지막으로, 저는 채팅 UI 서버를 담당하는 톰캣에서 웹 소켓 요청도 같이 처리하도록 하겠습니다.
톰캣의 server.xml 을 열어 8180 포트를 추가했습니다.
1
2
|
<Connector connectionTimeout="20000" port="8180" protocol="HTTP/1.1" redirectPort="8443"/>
<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
|
cs |
여기까지 설정을 해주셨다면 아래 과정을 거쳐 요청이 처리됩니다.
- 클라이언트단에서 ws://도메인:80/websocket/echo 요청
- Apache에서 해당 요청을 톰캣으로 Proxy 처리(8180)
01. Configurator 추가
아래 설정 클래스를 작성해 웹 소켓 통신이 시작되면, HttpSession을 해당 웹 소켓 통신에 저장해두겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package logicalcode.websocket;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
/**
* 웹 소켓 HTTP 세션 세팅 Configurator
*/
public class WebSocketSessionConfigurator extends Configurator
{
/**
* 웹 소켓 세션의 UserProperties에 PRIVATE_HTTP_SESSION 으로 현재 HttpSession을 세팅해준다.
*/
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{
HttpSession session = (HttpSession) request.getHttpSession();
if (session != null)
{
sec.getUserProperties().put("PRIVATE_HTTP_SESSION", session);
}
}
}
|
cs |
그리고 위 클래스의 HttpSession에서 Null이 취득되는 현상이 있어 아래 리스너를 추가했습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package modular.core.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
/**
* Servlet Request Listener
*/
@WebListener
public class RequestListener implements ServletRequestListener
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
}
/**
* WebSocketSessionConfigurator에서 HttpSession이 null 취득되어 오버라이드함.
*/
@Override
public void requestInitialized(ServletRequestEvent sre)
{
((HttpServletRequest) sre.getServletRequest()).getSession();
}
}
|
cs |
02. 웹소켓 컨트롤러 작성
ws://도메인/websocket/main 요청을 처리하는 컨트롤러를 작성합니다. 이 컨트롤러에서는 위에서 작성한 설정을 적용해 Endpoint를 설정해줍니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
package logicalcode.websocket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ServerEndpoint(value="/websocket/main", configurator=WebSocketSessionConfigurator.class)
public class MainEndpoint
{
private static final Logger logger = LoggerFactory.getLogger(MainEndpoint.class);
/**
* 현재 연결중인 세션 관리용 변수
*/
private static final Map<Session, HttpSession> sessionMap = new HashMap<Session, HttpSession>();
/**
* 클라이언트가 정상적으로 웹 소켓 서버 접근 시 호출되는 메서드
* @param session
* @throws IOException
*/
@OnOpen
public void onOpen (Session session, EndpointConfig config)
{
// HttpSession 객체 취득
HttpSession httpSession = (HttpSession) config.getUserProperties().get("PRIVATE_HTTP_SESSION");
logger.debug("WebSocket Session created : " + session.getId());
logger.debug("Http Session is : " + httpSession.getId());
sessionMap.put(session, httpSession);
}
/**
* 클라이언트에서 메시지가 서버로 도착했을 때 호출되는 메서드
* @param message
* @param session
* @throws IOException
*/
@OnMessage
public void onMessage (String message, Session session)
{
HttpSession httpSession = sessionMap.get(session);
logger.debug("Sender : " + httpSession.getId() + ", Message : " + message);
}
@OnError
public void onError(Throwable e,Session session)
{
e.printStackTrace();
}
@OnClose
public void onClose(Session session)
{
HttpSession httpSession = sessionMap.get(session);
logger.debug("WebSocket Session destroyed : " + session.getId());
logger.debug("Http Session was : " + httpSession.getId());
sessionMap.remove(session);
}
}
|
cs |
03. JSP 작성
마지막으로 JSP 에서 웹 소켓 요청을 보내는 코드를 작성하면 끝입니다!
소스는 간단하게, body Element는 생략하고 body load 이벤트에서 웹 소켓 통신을 연결하도록 하겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<html>
<head>
<script type="text/javascript">
var mainWS;
function body_load ()
{
if (mainWS !== undefined && mainWS.readyState !== WebSocket.CLOSED)
{
console.log("WebSockey is already opened");
return;
}
mainWS = new WebSocket("ws://서버아이피주소:8180/websocket/main");
mainWS.onopen = function (e)
{
if (e.data === undefined)
{
return;
}
}
}
</script>
</head>
<body>
</body>
</html>
|
cs |
04. 테스트
작성한 JSP에 접속하게되면, 서버 로그에는 아래와 같이 로그가 남게됩니다.
웹 소켓 세션은 0부터 시작해서 순차적으로 증가하고 HttpSession의 아이디가 로그에 남는 것을 확인하실 수 있습니다.
1
2
3
4
5
6
7
8
9
10
|
2020-09-10 23:51:12,108 DEBUG [logicalcode.websocket.MainEndpoint] WebSocket Session created : 0
2020-09-10 23:51:12,108 DEBUG [logicalcode.websocket.MainEndpoint] WebSocket Session created : 0
2020-09-10 23:51:12,109 DEBUG [logicalcode.websocket.MainEndpoint] Http Session is : B1266BCCD78EA8F10A09AAEB7C61A9CB
2020-09-10 23:51:12,109 DEBUG [logicalcode.websocket.MainEndpoint] Http Session is : B1266BCCD78EA8F10A09AAEB7C61A9CB
2020-09-10 23:51:42,947 DEBUG [logicalcode.websocket.MainEndpoint] WebSocket Session created : 1
2020-09-10 23:51:42,947 DEBUG [logicalcode.websocket.MainEndpoint] WebSocket Session created : 1
2020-09-10 23:51:42,948 DEBUG [logicalcode.websocket.MainEndpoint] Http Session is : A74DFD0EC1A1ACFE8CECA11DF9FB4A45
2020-09-10 23:51:42,948 DEBUG [logicalcode.websocket.MainEndpoint] Http Session is : A74DFD0EC1A1ACFE8CECA11DF9FB4A45
|
cs |
'☕️ JAVA' 카테고리의 다른 글
BufferedReader 가 Scanner 보다 빠른 이유 (0) | 2020.11.08 |
---|---|
Enum을 사용해보자 (0) | 2020.10.18 |
Spring AOP와 AspectJ 비교하기 (14) | 2019.12.15 |
이미지를 BLOB 형태로 DB에 저장하기 (5) | 2018.09.09 |
웹 서버와 웹 어플리케이션 서버의 차이 (1) | 2017.12.22 |