먼저 Chatting을 구현하기 위해서는 WebSocket이라는 것과 STOMP라는 것에 대한 이해가 필요해 보였다.
그래서 나는 WebSocket과 STOMP에 대한 자료들을 찾아봤다.
구글링을 해본결과 아래 유튜브가 제일 정리가 잘 되어있었다!
💡 [10분 테코톡] 아론의 웹소켓&스프링
https://www.youtube.com/watch?v=rvss-_t6gzg
위의 영상을 정리해보자
HTTP에서도 실시간성을 보장하는 기법(Polling, Long Polling, Streaming)이 존재한다.
HTTP는 비 연결성으로 매번 연결을 맺고 끊는 과정의 비용이 든다.
허나 웹소켓은 연결 지향으로 한번 연결을 맺으면 데이터를 양방향으로 계속 보내줄 수 있다.
또한 위와 같이 HTTP는 매 요청을 보낼 때마다 많은 정보들을 보내야 되지만
웹소켓은 HTTP로 연결이 수립되고 나면 간단한 메시지로 통신이 가능하다.
💡 웹소켓을 지원하지 않는 환경에서도 SockJS, Socket.io를 통해 해결할 수 있다.
(해당 브라우저가 웹소켓을 지원하는지 확인을 해보고 그렇지 않은 경우 대안책으로 그 기법들을 대신 사용한다.)
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/user")
.setAllowedOrigins("*")
.withSockJS();
}
}
웹소켓에 대한 Configuration 클래스를 만들고 WebSocketConfigurer 인터페이스 구현하고, @EnableWebSocket 어노테이션을 달아준다.
스프링에서 웹소켓을 사용하기 위해서 클라이언트가 보내는 통신을 처리할 핸들러가 필요하다
-> 직접 구현한 웹소켓 핸들러 (SocketTextHandler)를 웹소켓이 연결될 때, Handshake할 주소 (/user)와 함께 addHandler 메소드의 인자로 넣어준다.
setAllowedOrigins("*") 으로 Cors 설정을 할 수 있다.
스프링에서 웹소켓을 사용할 때, same-origin만 허용하는 것이 기본정책이다.
withSockJS() 으로 SockJS 라이브러리를 사용하도록 설정할 수 있다.
import org.json.JSONObject;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class SocketTextHandler extends TextWebSocketHandler {
private final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
JSONObject jsonObject = new JSONObject(payload);
for (WebSocketSession s : sessions) {
s.sendMessage(new TextMessage("Hi " + jsonObject.getString("user") + "!"));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
}
간단한 웹소켓 핸들러이다.
웹소켓 프로토콜은 기본적으로 Text, Binary 타입을 지원한다.
-> 필요에 따라 TextWebSocketHandler, BinaryWebSocketHandler를 상속하여 구현해주면 된다.
WebSocketSession 파라미터는 웹소켓이 연결될 때 생기는 연결정보를 담고 있는 객체이다.
-> Handler에서는 웹소켓 통신에 대한 처리를 위해, 웹소켓 세션들을 컬랙션에 담아 관리하는 경우가 많다
웹소켓 커넥션이 맺어지는 경우 (afterConnectionEstablished) -> sessions.add(session);
커넥션이 끊어지면 (afterConnectionClosed) -> sessions.remove(session);
웹소켓 세션을 통해, 연결된 모든 클라이언트들에게 메시지를 보낼 수 있다.
💡 스프링부트에서 WebSocket 의존성을 받아오면 Spring Messaging이 같이 달려온다. 그럼 Spring Messaging은 무엇일까? 그전에 STOMP 프로토콜에 대해서 이해해야 한다.
발신자가 어떠한 범주(예를 들어 경로)로 메시지를 발행하면
이 범주를 "구독"하고 있는 수신자들이 해당 메시지를 받아볼 수 있는 방식이다.
메시지 브로커는 발신자가 보낸 메시지들을 받아서 수신자들에게 전달해주는 것이다.
STOMP 프로토콜은 웹소켓만을 위해 만들어진 프로토콜이 아니다.
중요한 점은 웹소켓과 같이 몇몇 양방향 통신 프로토콜에서 STOMP를 함께 사용할 수 있다는 것이다.
Spring은 웹소켓 위에 STOMP 프로토콜을 얹어 사용하는 방법을 지원해준다.
💡 STOMP를 사용하면 메시지 형식에 대한 고민과 파싱 로직을 위한 코드 구현이 필요없어진다.
메시지를 보내려는 발신자, 메시지를 받으려는 구독자가 있다.
구독자는 /topic 이라는 경로를 구독하고 있다.
발신자는 /topic을 destination 헤더로 넣어 메시지를 메시지 브로커를 통해 구독자들에게 곧바로 송신할 수 있다
또는 서버 내에서 어떤 가공처리가 필요하다면 /app 경로로 메시지를 송신할 수 있다.
-> 서버가 가공처리가 끝난 데이터를 /topic이라는 경로를 담아 메시지 브로커에게 전달하면
-> 메시지 브로커는 전달받은 메시지를 /topic을 구독하는 구독자들에게 최종적으로 전달한다.
package com.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue","/topic");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket")
.withSockJS();
}
}
@EnableWebSocketMessageBroker를 달고
WebSocketMessageBrokerConfigurer 인터페이스 구현해준다.
configureMessageBroker: 메시지 브로커를 설정하는 메서드
registerStompEndpoints: 웹소켓 코드에서 봤던 addHandler 메서드와 굉장히 비슷하다.
-> 인자로 들어가는 경로는 웹소켓의 /user처럼 처음 웹소켓 핸드쉐이크를 위한 주소이고
여기에서도 뒤에 cors 설정과 sockJS 설정을 그대로 해줄 수 있다.
package com.websocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greeting")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000);
return new Greeting("Hello" + HtmlUtils.htmlEscape(message.getName()));
}
STOMP를 사용하게 되면 따로 인터페이스를 상속을 받거나 할 필요없이 컨트롤러 어노테이션을 사용할 수 있다.
@MessageMapping: 기존에 알던 requestMapping과 비슷한 역할을 함
-> STOMP 웹소켓 통신을 통해 메시지가 들어왔을 때도 메시지의 destination 헤더와 MessageMapping에 설정된 경로가 일치하는 핸들러를 찾고 해당 핸들러가 처리를 하게된다.
여기서는 아까 Configuration에서 설정해둔 /app이라는 prefix와 합쳐져서
/app/hello destination 헤더를 가진 메시지들이 이 핸들러를 거치게 된다.
@SendTo: 핸들러에서 처리를 마친 후 반환 값을 /topic/greeting의 경로로 메시지를 보내준다.
-> 여기서는 처리를 마치고 반환된 Greeting 객체를 /topic/greeting 경로로 다시 보내는 것이니 바로 Simple 브로커로 전달될 것이다.
- 하위 프로토콜, 컨벤션을 따로 정의할 필요 없다.
-> STOMP가 프레임 단위로 정의해준다.- 연결 주소마다 새로운 Handler를 구현하고 설정해줄 필요 없다.
-> @Controller 어노테이션을 사용하면 된다.- 외부 Messaging Queue (Kafka, RabbitMQ, ...)를 사용할 수 있다.
-> Spring이 기본적으로 제공하는 내장 메시지 브로커가 아닌 외부 메시지 큐를 연동해서 사용할 수 있다.- Spring Security를 사용할 수 있다.
-> 오고 가는 메시지에 대한 보안설정을 할 수 있다.
지금까지 WebSocket, STOMP에 대한 개념과 Spring에서 WebSocket과 STOMP를 사용하는 방법을 알아봤다.
다음 장에서는 Spring STOMP를 사용하여 간단한 채팅프로그램을 만들고 현재 진행중인 프로젝트에도 채팅을 적용시켜보도록 하겠다.