프로젝트에서 웹 소켓을 사용할 일이 있는데, 웹 소켓이 제대로 적용되었는지 체크하기 위해 Spring의 WebSocket Client를 활용하기로 하였다.
프로젝트가 모바일 웹 앱을 기반으로 하는 만큼 SockJS를 지원하기로 했다. SockJS는 웹소켓이 지원되지 않는 환경일 경우, HTTP Polling과 같은 웹 소켓을 사용하지 않는 다른 방법으로 웹 소켓을 Emulation하는 기술이다. 즉, 웹 소켓을 흉내내는 것이다.
다음과 같은 경우에 SockJS를 사용한다.
Spring WebSocket에서 SockJS를 지원하기 위해 다음과 같이 설정하였다.
@Configuration
@EnableWebSocketMessageBroker
public class SimpleBrokerWebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hf").withSockJS();
}
...
}
위와 같이 설정하면 웹 소켓 클라이언트에서 ws schema를 사용한 채 /hf 경로로 웹 소켓 연결을 요청할 경우 SockJS를 사용해 웹 소켓이 연결된다. 현재 환경에서 웹 소켓이 지원될 경우 웹 소켓을 사용하고, 웹 소켓을 사용하기 어려운 환경일 경우 HTTP Polling과 같은 전통적인 HTTP 기반 양방향 통신 Emulation을 사용한다.
테스트는 다음과 같이 구성하였다.
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestWebSocketConnectivity {
@LocalServerPort
int serverPort;
@DisplayName("웹 소켓에 제대로 연결이 되는지 테스트 - SockJs 클라이언트 사용")
@Test
void testConnectivity() throws ExecutionException, InterruptedException {
WebSocketClient webSocketClient = new SockJsClient(List.of(
new WebSocketTransport(new StandardWebSocketClient()),
new RestTemplateXhrTransport(new RestTemplate())
));
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
String url = "ws://localhost:" + this.serverPort + "/hf";
StompSessionHandler stompSessionHandler = new MyStompSessionHandler();
assertThatNoException()
.isThrownBy(() -> stompClient.connectAsync(url, stompSessionHandler).get());
}
static class MyStompSessionHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
log.info("sessionId={}", session.getSessionId());
log.info("connectedHeaders={}", connectedHeaders);
}
}
}
WebSocketClient를 사용하여 웹 소켓 서버와 연결이 제대로 되는지만 체크한다.
2024-11-29T17:31:41.927+09:00 INFO 21692 --- [ient-AsyncIO-16] c.h.h.g.w.TestWebSocketConnectivity : sessionId=9609df04-a897-9f15-1c4c-05385223b5ee
2024-11-29T17:31:41.927+09:00 INFO 21692 --- [ient-AsyncIO-16] c.h.h.g.w.TestWebSocketConnectivity : connectedHeaders={version=[1.2], heart-beat=[0,0]}
테스트는 무사히 통과하였고, 연결이 정상적으로 성립하여 Session ID가 생성된 것을 확인할 수 있다.
SockJsClient 클래스의 생성자를 보면 List<Transport>를 받고 있는 것을 확인할 수 있다. Transport의 FQCN은 org.springframework.web.socket.sockjs.client.Transport이다. 패키지에서 알 수 있듯, Transport는 SockJS에서 사용되는 객체이다.
Transport란 SockJsClient에서 지원할 웹 소켓 에뮬레이션 수단을 말한다. SockJS 기술 자체가 웹 소켓을 지원하지 않을 경우에 대비해 웹 소켓을 통한 연결뿐만 아니라 XHR을 통한 HTTP 기반 양방향 통신 연결도 지원한다. 그러한 SockJS의 특성을 Spring의 SockJsClient에 반영하기 위해 Transport라는 타입으로 추상화한 것이다. Transport 타입의 객체들은 각각 XHR, WebSocket 등 나름의 방법으로 웹 소켓을 지원하고 있다.
Spring API 문서에서 Transport 인터페이스의 구현체들을 확인할 수 있다.

위에 나타난 테스트 코드를 조금 수정하여 XHR 기반의 SockJS 클라이언트를 테스트해 보았다.
@DisplayName("웹 소켓에 제대로 연결이 되는지 테스트 - SockJs 클라이언트 사용 - RestTemplate만 사용")
@Test
void testConnectivity_sockJS_restTemplate() throws ExecutionException, InterruptedException {
WebSocketClient webSocketClient = new SockJsClient(List.of(
new RestTemplateXhrTransport(new RestTemplate())
));
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
String url = "ws://localhost:" + this.serverPort + "/hf";
StompSessionHandler stompSessionHandler = new MyStompSessionHandler();
assertThatNoException()
.isThrownBy(() -> stompClient.connectAsync(url, stompSessionHandler).get());
}
SockJsClient의 생성자 argument 부분만 바뀌었다. 테스트를 돌려 보면

잘 돌아간다. 웹 소켓을 지원하지 않는 클라이언트에서도 SockJS를 활용해 웹 소켓 연결이 성립될 수 있음을 확인할 수 있다.