webSocket : 2011년에 나온 비교적 최신 기술이며, http 처럼 일종의 통신 규약이다. 한번 연결된 이후에 계속해서 연결 상태가 유지되는 통신이라고 생각하면 된다.
흐름:
1. 스프링 내부에서 webSocket 요청을 받을 endPoint 설정
2. 자바 스크립트 - 스프링간의 webSocket 통신 연결
3. 요청(메시지)이 들어올시 처리될 코드 작성
실제 코드:
0. websocket 을 사용하기 위한 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-websocket'
1. WebSocketConfig 클래스
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketChatHandler webSocketChatHandler;
private final HttpSessionHandshakeInterceptor handshakeInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 이를 통해서 ws://localhost:8080/ws/chat 으로 요청이 들어오면 websocket 통신을 진행한다.
// setAllowedOrigins("*")는 모든 ip에서 접속 가능하도록 해줌
// addInterceptors 를 통해, 최초 websocket 연결을 위한 통신이 이뤄질때 interceptor 를 적용
registry.addHandler(webSocketChatHandler, "/ws/chat").setAllowedOrigins("*")
.addInterceptors(handshakeInterceptor);
}
}
설정 클래스로, @EnableWebSocket 를 통해 웹소켓을 사용 가능하게 설정한다.
2. 자바스크립트 코드
// 웹소켓을 연결한다.
var websocket = new WebSocket("ws://localhost:8080/ws/chat");
websocket.onmessage = onMessage;
websocket.onopen = onOpen;
websocket.onclose = onClose;
// 웹소켓이 연결되면 실행됨
function onOpen() {
...........
}
// 웹소켓 연결이 끊기면 실행됨
function onClose() {
...........
}
// 웹소켓으로부터 메시지를 수신하면 실행됨
function onMessage(msg) {
..........
}
// 웹소켓으로 메시지를 보냄
function sendMessage() {
let item = {
clubNo: $("#clubNo").val(),
message: $("#messageText").val(),
messageType: "chat",
};
websocket.send(JSON.stringify(item));
$("#messageText").val("");
}
websocket을 통해 json 형태로 메시지를 보낸다.
websocket.send 함수로 보낼수 있다.
3. WebSocketChatHandler 클래스
@Component
@RequiredArgsConstructor
public class WebSocketChatHandler extends TextWebSocketHandler {
// json -> ChatMessageDto 변경시키는 객체
private final ObjectMapper mapper;
// websocket 과 연결된 사용자들 세션 저장
private final Set<WebSocketSession> sessions = new HashSet<>();
// 키 값이 채팅방 번호, 밸류값이 채팅 참여 사용자 세션 저장
private final Map<Long, Set<WebSocketSession>> chatRoomSessionMap = new ConcurrentHashMap<>();
// 소켓 연결시 실행되는 함수
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// TODO Auto-generated method stub
log.info("{} 연결됨", session.getId());
}
// 소켓 통신 시, 메시지가 들어오면 실행되는 함수
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
sessions.add(session);
String payload = message.getPayload();
ChatMessageDto chatMessageDto = mapper.readValue(payload, ChatMessageDto.class);
Long chatRoomId = chatMessageDto.getClubNo();
// 해당 번호의 채팅방이 없으면 새롭게 생성
if (!chatRoomSessionMap.containsKey(chatRoomId)) {
chatRoomSessionMap.put(chatRoomId, new HashSet<>());
}
// 해당 채팅방의 참여자들의 session
Set<WebSocketSession> chatRoomSession = chatRoomSessionMap.get(chatRoomId);
if(chatMessageDto.getMessageType().equals("open")){
chatRoomSession.add(session);
sendMessageToChatRoom(chatMessageDto, chatRoomSession);
}
if (chatMessageDto.getMessageType().equals("chat")) {
sendMessageToChatRoom(chatMessageDto, chatRoomSession);
}
if(chatMessageDto.getMessageType().equals("close")){
sendMessageToChatRoom(chatMessageDto, chatRoomSession);
chatRoomSession.remove(session);
}
}
// 소켓 종료 확인
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// TODO Auto-generated method stub
log.info("{} 연결 끊김", session.getId());
sessions.remove(session);
}
// ====== 채팅 관련 메소드 ======
private void removeClosedSession(Set<WebSocketSession> chatRoomSession) {
chatRoomSession.removeIf(sess -> !sessions.contains(sess));
}
private void sendMessageToChatRoom(ChatMessageDto chatMessageDto, Set<WebSocketSession> chatRoomSession) {
chatRoomSession.parallelStream().forEach(sess -> sendMessage(sess, chatMessageDto));//2
}
public <T> void sendMessage(WebSocketSession session, T message) {
try {
session.sendMessage(new TextMessage(mapper.writeValueAsString(message)));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
TextWebSocketHandler 는 websocket 을 이용해 문자열을 받기 위해 구현된 클래스다.
연결시, 메시지 수신시, 연결 끊김시 실행될 함수가 각각 이미 구현되어 있기에, 알맞은 코드만 작성해주면 된다.
4. (외전) HttpSession 을 websocket 내부에서 사용하기
@Component
public class HttpSessionHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false); // Get HttpSession from the request
attributes.put("reqSession", session);
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
}
HandshakeInterceptor 는 처음 websocket 연결을 위한 http handshake가 이뤄질때 호출되는 함수다.
기존에 WebSocketChatHandler 내부에서 사용되는 websocketSession 은 웹소켓 내부에서 사용되는 세션으로, HttpServletRequest 에서 사용되는 session 과는 별개의 객체다. 따라서 HttpSession 에 저장된 데이터를 websocketSession 으로 사용하거나, websocket 내부에서 불러올 방법은 없다.
따라서 처음 handshake 를 위한 http 통신이 이뤄질때 httpSession 을 불러와 webSocketSession 에 저장해야 httpSession 의 값을 사용 가능하다.
Map<String, Object> attributes 이 WebSocketSession 으로 변하게 된다. 따라서 attributes 에 저장하면 webSocketSession 에서도 불러올수 있다.
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
HttpSession reqSession = (HttpSession) session.getAttributes().get("reqSession");
}
'공부 > springboot' 카테고리의 다른 글
SpringMVC(3) : 서블릿 요청에 대한 응답 (0) | 2023.12.11 |
---|---|
SpringMVC(2) : 서블릿 요청 처리 (2) | 2023.12.08 |
SpringMVC(1) : 웹 어플리케이션 서버(WAS) 의 이해 (2) | 2023.12.06 |
스프링 레거시와 스프링 부트의 차이 (0) | 2023.10.19 |
(6)빈 자동 등록과 자동 주입 (0) | 2023.05.15 |