Spring WebSocket Client 만들기

PGD·2024년 11월 29일

프로젝트에서 웹 소켓을 사용할 일이 있는데, 웹 소켓이 제대로 적용되었는지 체크하기 위해 Spring의 WebSocket Client를 활용하기로 하였다.

프로젝트가 모바일 웹 앱을 기반으로 하는 만큼 SockJS를 지원하기로 했다. SockJS는 웹소켓이 지원되지 않는 환경일 경우, HTTP Polling과 같은 웹 소켓을 사용하지 않는 다른 방법으로 웹 소켓을 Emulation하는 기술이다. 즉, 웹 소켓을 흉내내는 것이다.

다음과 같은 경우에 SockJS를 사용한다.

  • 브라우저가 웹 소켓을 지원하지 않을 경우 (모바일 크롬 혹은 IE)
  • Proxy 서버가 Upgrade Header를 이해하지 못할 경우
  • Proxy 서버에서 유휴 상태의 웹 소켓 Connection을 끊어 버릴 수 있을 경우

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가 생성된 것을 확인할 수 있다.

Transport

SockJsClient 클래스의 생성자를 보면 List<Transport>를 받고 있는 것을 확인할 수 있다. Transport의 FQCN은 org.springframework.web.socket.sockjs.client.Transport이다. 패키지에서 알 수 있듯, Transport는 SockJS에서 사용되는 객체이다.

TransportSockJsClient에서 지원할 웹 소켓 에뮬레이션 수단을 말한다. SockJS 기술 자체가 웹 소켓을 지원하지 않을 경우에 대비해 웹 소켓을 통한 연결뿐만 아니라 XHR을 통한 HTTP 기반 양방향 통신 연결도 지원한다. 그러한 SockJS의 특성을 Spring의 SockJsClient에 반영하기 위해 Transport라는 타입으로 추상화한 것이다. Transport 타입의 객체들은 각각 XHR, WebSocket 등 나름의 방법으로 웹 소켓을 지원하고 있다.

Spring API 문서에서 Transport 인터페이스의 구현체들을 확인할 수 있다.

XHR만 지원하는 SockJsClient 테스트해 보기

위에 나타난 테스트 코드를 조금 수정하여 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를 활용해 웹 소켓 연결이 성립될 수 있음을 확인할 수 있다.

profile
student

0개의 댓글