Spring Framework의 Web Socket 지원
Spring 공식 문서를 살펴보면, 다음과 같은 3가지 기능을 지원하고 있는 것을 확인할 수 있다.
WebSockets :: Spring Framework
The WebSocket protocol, RFC 6455, provides a standardized way to establish a full-duplex, two-way communication channel between client and server over a single TCP connection. It is a different TCP protocol from HTTP but is designed to work over HTTP, usin
docs.spring.io
- Web Socket API
- SockJS Fallback
- STOMP
1. Web Socket API
WebSocket 메시지를 다룰 수 있는 API 제공
WebSocket API의 핵심은 WebSocketHandler라고 할 수 있다.
WebSocketHandler
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
이진 데이터를 다루기 위한 `BinaryWebSocketHandler`, 혹은 텍스트를 다루기 위한 `TextWebSocketHandler`를 상속받아 구현하는 것만으로 간단한 Web Socket 서버를 만들 수 있다. `handleTextMessage` 메서드를 개발자가 원하는 기능으로 Override한다.
WebSocketConfigurer
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(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
`WebSocketConfigurer`를 구현하는 설정 파일을 만들고 @EnableWebSocket 어노테이션을 선언한 뒤, 위에서 작성한 Handler 클래스를 Bean으로 등록하면 Web Socket을 통해 메시지를 주고 받는 서버를 구현할 수 있다.
이에 더해, Web Socket의 HTTP Handshake를 커스터마이징할 수 있는 `HandshakeInterceptor` 또한 제공한다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/myHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
이를 통해, Handshake 과정에서 발생하는 인증, 서브 프로토콜 협상 등의 여러 가지 부가적인 작업을 수행할 수 있다.
서버 설정
또한, Spring은 Web Socket 서버 설정을 자바 코드를 통해 제어할 수 있는 기능을 제공하고 있다.
이러한 기능을 통해, Web Socket의 Message Buffer Size, 각종 Time Out과 같은 설정을 쉽게 제어할 수 있다.
예시
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
또한, Web 서버의 종류(Tomcat, Netty, Jetty 등)에 따라서 다른 설정을 적용할 수 있다.
예시
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(), "/echo").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
JettyRequestUpgradeStrategy strategy = new JettyRequestUpgradeStrategy();
strategy.addWebSocketConfigurer(configurable -> {
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
});
return new DefaultHandshakeHandler(strategy);
}
}
2. SockJS Fallback
WebSocket이 정상적으로 작동하지 않는 환경을 위한 SockJS 프로토콜 지원
SockJS := 브라우저 환경에서 실행되는 JS 라이브러리
아래 코드를 통해 SockJS 지원 기능을 활성화 할 수 있다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
3. STOMP
STOMP 브로커 기능을 제공
⇒ Web Socket을 직접 사용했을 때보다 풍부한 기능을 제공한다.
- Spring Security를 이용할 수 있다.
STOMP 사용 설정
우선, STOMP를 사용하기 위해서는 아래와 같은 설정 파일(Config Class)이 필요하다.
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
@EnableWebSocketMessageBroker 어노테이션을 선언하고, `WebSocketMessageBrokerConfigurer` 인터페이스를 구현하면 된다.
registerStompEndpoints 메서드
해당 메서드에서 `addEndpoint` 를 통해서
Web Socket 클라이언트가 Handshake를 위해 접속해야 하는 서버 주소를 설정할 수 있다.
configureMessageBroker 메서드
해당 메서드의 `setApplicationDestinationPrefixes`를 통해
클라이언트가 메시지를 발행하는 주소를 설정할 수 있다.
`enableSimpleBroker`를 통해
클라이언트가 메시지를 구독하는 주소를 설정할 수 있다.
Annotated Controller : @MessageMapping
위와 같은 설정을 통해 STOMP가 활성화된다면, Controller에서 @MessageMapping 어노테이션을 사용해,
클라이언트로부터 들어오는 STOMP 메시지를 핸들링할 메시지를 지정할 수 있다.
예시로, 클라이언트에서 “app/greeting”이라는 경로로 메시지를 보낸다면, 위에서 설명한 `setApplicationDestinationPrefixes`에 의해 “app”은 제외가 되고 “/greeting”에 해당하는 @MessageMapping 어노테이션이 선언되어 있는 Controller의 메서드로 메시지를 전달하게 된다.
이곳에서 로직이 수행된 후 반환되는 String 값은 STOMP 메시지의 Payload로 변환되어 `enableSimpleBroker`에서 설정한 “/topic”이 포함된, “/topic/greeting”을 구독하고 있는 클라이언트에게 메시지를 발행한다.
STOMP의 Controller에서는 아래 표에 나타나 있는 파라미터들을 사용할 수 있다.
Payload, Header, Security의 Principal 등을 인자로 받을 수 있는 것을 확인할 수 있다.
@DestinationVariable
@MessageMapping 어노테이션에 선언된 template 형태의 변수를 받아올 때 사용한다.
⇒ 일반적인 Controller에서 @PathVariable 어노테이션과 비슷한 역할을 한다. 따라서, Endpoint를 통해서도 변수를 받아 처리할 수 있다.
서버에서 클라이언트로 메시지를 전송 : SimpMessagingTemplate
`SimpMessagingTemplate`이라는 Bean을 이용해 서버에서 클라이언트로 메시지를 전송하는 기능도 제공한다.
@Controller
public class GreetingController {
private SimpMessagingTemplate template;
@Autowired
public GreetingController(SimpMessagingTemplate template) {
this.template = template;
}
@RequestMapping(path="/greetings", method=POST)
public void greet(String greeting) {
String text = "[" + getTimestamp() + "]:" + greeting;
this.template.convertAndSend("/topic/greetings", text);
}
}
클라이언트에서 보내는 메시지를 처리하는 것과는 다르게, 서버에서 원하는 메시지를 원하는 경로의 클라이언트로 전송할 수 있다.
Flow of Message
STOMP가 활성화되면 Spring 서버는 접속된 Web Socket 클라이언트들에 대한 STOMP Message Broker로 작동하게 된다.
메시지가 흐르는 순서대로 살펴보자.
- 상단의 ClientInboundChannel(request channel)은 Web Socket 클라이언트로부터 받은 메시지를 서버로 넘겨주는 역할을 한다.
- broker channel은 서버 내에서 발생한 메시지를 SimpleBroker에게 전달하는 역할을 한다.
- SimpleBroker는 클라이언트들로부터 받은 구독 요청들을 메모리에 관리하고 있다가 목적지가 일치하는 메시지가 들어올 때, 해당하는 클라이언트들에게 전송하는 역할을 한다.
- ClientOutboundChannel(response channel)은 서버의 메시지를 Web Socket 클라이언트로 전달하는 역할을 한다.