[Spring] WebSocket & Spring
2022. 8. 17. 21:33ㆍSpring
웹소켓
웹소켓은 전이중(동시에 양방향 전송이 가능한) 통신을 제공하기 때문에 실시간성을 보장해 줄 수 있다.
- 실시간성을 보장하는 서비스에서 웹소켓을 사용할 수 있다.
웹소켓 vs HTTP
웹소켓이 아닌 HTTP를 이용하여 실시간성을 보장하는 것처럼 흉내낼 수 있다.
- HTTP의 실시간성 보장 기법에는 Polling, Long Polling, Streaming이 있다.
실시간성 측면에서 웹소켓과 HTTP의 가장 큰 차이는 수립된 connection을 활용하는 방식이다.
- HTTP
- 클라이언트와 연결을 맺고 끊는다. (비연결성)
- 3way, 4way handshake로 연결을 맺고 끊어야한다.
- 웹소켓
- 한 번 연결을 맺고 나면, 그 연결을 계속 유지한다.
- 연결을 맺는 과정에서 발생하는 비용을 줄일 수 있다.
또 다른 차이점은 통신을 하는 방식에 있다.
- HTTP
- 요청과 응답이 하나의 쌍을 이루는 구조로 통신한다.
- 원하는 리소스에 대해 서버쪽에 요청을 해야함
- ex) 탁구
- 웹소켓
- 연결이 계속 유지되기 때문에 요청 없이 상대가 보낸 메시지를 계속 듣고있기만 하면 된다.
- ex) 전화 연결
보내야하는 데이터의 양에서도 차이가 있다.
- HTTP
- 매 요청마다 많은 정보를 만들어 서버에 보낸다.
- 응답시에도 마찬가지이다.
- 실시간성을 요하는 서비스(요청과 응답이 많은)에 부담이 되는 구조이다.
- 웹소켓
- 최초의 handshake 과정에서는 HTTP 프로토콜을 이용하기 때문에 위와 유사한 양의 데이터를 주고받는다.
- 하지만 한 번 연결이 수립되면, 간단한 데이터만 오고 간다. (HTTP보다 통신 비용이 저럼)
웹소켓을 지원하는 브라우저는 다양하다.
만일 지원하지 않는 환경이라도 SockJS, socket.io 라이브러리를 사용하면 된다. (Spring은 SockJS를 지원한다.)
- SockJS, socket.io 라이브러리를 사용하면, 웹소켓을 지원하지 않는 브라우저에서 웹소켓을 사용하는 것과 같은 비슷한 기능을 제공해준다.
- 스프링은 SockJS를 제공한다.
- 웹소켓을 지원하는 브라우저는 웹소켓 기술을 사용한다.
- 지원하지 않는다면 HTTP의 Streaming을 사용하고, 그것도 지원하지 않는다면 Polling을 사용한다.
Spring에서의 웹소켓
스프링에서의 웹소켓 적용
package com.websocket;
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 라이브러리를 사용하도록 설정할 수 있다.
- 웹소켓을 지원하지 않는 브라우저 환경에서도 비슷한 경험을 할 수 있도록 한다.
package com.websocket;
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.removed(session);
- 웹소켓 세션을 통해, 연결된 모든 클라이언트들에게 메시지를 보낼 수 있다.
STOMP & Spring-Messaging
스프링부트에서 WebSocket 의존성을 받아오면 Spring Messaging이 같이 달려온다.
그럼 Spring Messaging이란 무엇일까?
이에 대해 알기 전 STOMP 프로토콜에 대해 이해해야 한다.
- STOMP는 간단한 텍스트 기반 메시징 프로토콜이다.
-> 메시지 브로커라는 것을 활용하여 pub/sub 방식으로 클라이언트와 서버가 쉽게 메시지를 주고 받을 수 있도록하는 프로토콜이다. - pub/sub 은 일종의 메시지 패러다임이다.
- 발신자가 어떠한 범주(예를 들어 경로)로 메시지를 발행하면
이 범주를 구독하고 있는 수신자들이 해당 메시지를 받아볼 수 있는 방식이다.
- 발신자가 어떠한 범주(예를 들어 경로)로 메시지를 발행하면
- 메시지 브로커는 발신자가 보낸 메시지들을 받아서 수신자들에게 전달해주는 것이다.
STOMP 프로토콜은 웹소켓만을 위해 만들어진 프로토콜이 아니다.
- 중요한 점은 웹소켓과 같이 몇몇 양방향 통신 프로토콜에서 STOMP를 함께 사용할 수 있다는 것이다.
- Spring은 웹소켓 위에 STOMP 프로토콜을 얹어 사용하는 방법을 지원해준다.
그럼 굳이 웹소켓위에 STOMP를 얹어서 사용하는 이유가 무엇일까?
- 웹소켓은 Text, Binary 타입의 메시지를 양방향으로 주고받을 수 있는 프로토콜이다. 하지만 메시지를 주고 받는 형식이 따로 정해진 것이 없다.
-> 웹소켓만 사용하는 프로젝트가 커지면, 주고 받는 메시지에 대한 형식이 중요하게 된다.
-> 정의된 메시지 형식대로 파싱하는 로직 또한 따로 구현해야한다.
-> STOMP를 사용하면 메시지 형식에 대한 고민과 파싱 로직을 위한 코드 구현이 필요 없어진다.
- STOMP는 커맨드, 헤더, 바디로 이루어진 프레임 단위를 정의해두었기 때문이다
- 웹소켓만 사용할 때(왼쪽) 오고 가는 데이터는 오직 날것의 메시지 뿐이다.
- STOMP를 사용할 때(오른쪽)는 커맨드, 헤더, 바디의 형태로 데이터가 오고간다.
스프링이 STOMP 프로토콜을 사용하고 있을 떄의 동작 흐름에 대해 살펴보자
- 메시지를 보내려는 발신자, 메시지를 받으려는 구독자가 있다.
구독자는 /topic 이라는 경로를 구독하고 있다. - 발신자는 /topic을 destination 헤더로 넣어 메시지를 메시지 브로커를 통해 구독자들에게 곧바로 송신할 수 있다
- 또는 서버 내에서 어떤 가공처리가 필요하다면 /app 경로로 메시지를 송신할 수 있다.
-> 서버가 가공처리가 끝난 데이터를 /topic이라는 경로를 담아 메시지 브로커에게 전달하면
-> 메시지 브로커는 전달받은 메시지를 /topic을 구독하는 구독자들에게 최종적으로 전달한다.
Spring에서 웹소켓과 함께 STOMP 프로토콜을 사용해보자
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 메소드는 STOMP에서 사용하는 메시지 브로커를 설정하는 메소드이다.
- enableSimpleBroker : 내장 메시지 브로커를 사용하기 위한 메소드이다.
파라미터로 지정한 prefix(/queue 또는 /topic)가 붙은 메시지를 발행할 경우, 메시지 브로커가 이를 처리하게 된다.- /queue prefix는 메시지가 1대1로 송신될 때,
/topic prefix는 메시지가 1대다로 브로드캐스팅될 때 사용하는게 컨밴션이다.
- /queue prefix는 메시지가 1대1로 송신될 때,
- setApplicationDestinationPrefixes : 메시지 핸들러로 라우팅되는 prefix(/app)를 파라미터로 지정할 수 있다.
→ 메시지 가공 처리가 필요한 경우, 가공 핸들러로 메시지를 라우팅 되도록하는 설정이다.
- enableSimpleBroker : 내장 메시지 브로커를 사용하기 위한 메소드이다.
- registerStompEndpoints 메소드는 웹소켓 configuration의 addHandler 메소드와 유사하다.
→ addEndpoint 메소드의 파라미터로 들어가는 /gs-guide-websocket는 처음 웹소켓 Handshake를 위한 경로이다.
→ CORS, SockJS 설정 또한 할 수 있다.- 다른점은 addEndpoint에 핸들러를 하나하나 추가해줄 필요가 없다.
→ STOMP를 사용하면, 웹소켓만 사용할 때와 다르게 하나의 연결주소마다 핸들러 클래스를 따로 구현할 필요없이 Controller 방식으로 간편하게 사용할 수 있다. (STOMP의 장점)
- 다른점은 addEndpoint에 핸들러를 하나하나 추가해줄 필요가 없다.
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를 사용하면 따로 인터페이스를 상속받을 필요없이 @Controller 어노테이션을 사용할 수 있다.
- @MessageMapping 어노테이션은 @RequestMapping과 비슷한 역할을 한다
-> HTTP 요청의 경로에 맞는 핸들러에게 처리를 위임하듯,
STOMP 웹소켓 통신을 통해 메시지가 들어오면 메시지의 destination 헤더와 @MessageMapping에 설정된 경로가 일치한 핸들러를 찾아 해당 핸들러가 이를 처리한다.
-> WebSocketBrokerConfig에서 설정한 /app prefix와 합쳐진 /app/hello 라는 destination 헤더를 가진 메시지들이 @MessageMapping("/hello") 가 붙은 핸들러를 거치게 된다. - @SendTo 어노테이션은 핸들러에서 처리를 마친 후 결과 메시지를 설정한 경로(/topic/greeting)로 보내준다.
-> 앞에 /topic이 붙었으니 SimpleBroker로 전달된다.
Spring 속 STOMP 프로토콜의 장점
- 하위 프로토콜, 컨벤션을 따로 정의할 필요 없다.
-> STOMP가 프레임 단위로 정의해준다. - 연결 주소마다 새로운 Handler를 구현하고 설정해줄 필요 없다.
-> @Controller 어노테이션을 사용하면 된다. - 외부 Messaging Queue (Kafka, RabbitMQ, ...)를 사용할 수 있다.
-> Spring이 기본적으로 제공하는 내장 메시지 브로커가 아닌 외부 메시지 큐를 연동해서 사용할 수 있다. - Spring Security를 사용할 수 있다.
-> 오고 가는 메시지에 대한 보안 설정을 할 수 있다.
[출처]
WebSocket & Spring
https://www.youtube.com/watch?v=rvss-\_t6gzg웹소켓은 전이중 통신을 제공하기 때문에 실시간성을 보장해줄 수 있다.실시간성을 보장하는 서비스 (게임, 채팅, 실시간 주식거래)에서 웹소켓을 사용할 수 있
velog.io
'Spring' 카테고리의 다른 글
[Spring] 빈 스코프의 종류 (0) | 2022.10.13 |
---|---|
[Spring] @Configuration 안에 @Bean을 사용해야 하는 이유 (0) | 2022.10.13 |
[Spring] 빈 등록을 위한 어노테이션 @Bean, @Configuration, @Component (0) | 2022.10.12 |
[Spring] @RequestParam, @RequestBody, @ModelAttribute (0) | 2022.10.12 |
[Spring] 디스패처 서블릿(Dispatcher Servlet) (0) | 2022.08.15 |