SockJS Fallback Options

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-fallback

WebSocket 이 아직까지 모든 브라우저에서 지원되지 않거나 네트워크 프락시 제약등으로 사용할 수 없는 경우가 있다. 이에 Spring 은 fallback 옵션을 제공하는데 이는 SockJS protocol 에 기반으로 WebSocket API 를 emulate 한다.

1. SockJS 개요

SockJS 는 application 으로 하여금 WebSocket API 를 사용하는데 있다. 만약 WebSocket 사용이 불가한 경우에도 이를 fallback option 으로 제공하여 어떠한 코드 변화없이 WebSocket API 를 사용토록 한다.

SockJS 구성

SockJS 는 여러가지 테그닉을 이용하여 다양한 브라우저 및 브라우저 버전을 지원한다. 전송 타입은 다음의 3가지로 분류된다. WebSocket, HTTP Streaming, HTTP Long Polling. 이들 각각을 살펴보려면 여기 를 참조한다.

SockJS client 는 서버의 기본 정보를 얻기 위해서 “GET /info” 를 호출한다. 그 이후에 SockJS 는 어떤 전송 타입을 사용할지를 결정한다. WebSocket 은 최우선책이며, 이후로 HTTP Streaming > HTTP (long) polling 이 사용된다.

모든 전송 요청은 다음의 URL 구조를 갖는다.

http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

  • {server-id} - 클러스터에서 요청을 routing 하는데 사용하나 이외에는 의미가 없다.
  • {session-id} - SockJS session 에 소속하는 HTTP 요청과 연관성이 있다.
  • {transport} - 전송타입 ex> websocket, xhr-streaming, 기타 등등

WebSocket 전송은 WebSocket handshaking 을 위한 오직 하나의 HTTP 요청을 필요로 한다. 모든 메시지들은 그 이후에 사용했던 socket 을 통해 교환된다.

HTTP 전송은 좀 더 많은 요청을 필요로 한다. 예를 들어 Ajax/XHR streaming 은 server 에서 client 로의 메시지를 위해 하나의 long-running 요청이 있고 추가적인 HTTP POST 요청은 client 에서 server 로의 메시지를 위해 사용된다. Long polling 은 server 가 client 로의 응답 후에 현재의 요청을 끝내는 것을 제외하고는 XHR streaming 과 유사하다.

SockJS 는 최소한의 message framing 을 추가한다. 예를 들어 server 는 “o” (open frame) 를 초기에 전송하고, 메시지는 [“message1”,”message2”] 와 같은 JSON-encoded 배열로서 전달되며, 문자 “h” (hearbeat frame) 는 기본적으로 25초간 메시지 흐름이 없는 경우에 전송하고 “c” (close frame) 는 해당 세션을 종료한다.

이에 대한 자세한 이해는 브라우저에서 확인할 수 있다. SockJS 는 debug flag 를 제공하고, 전송타입을 고정하여 각각에 대해서 살펴볼 수 있다. 서버는 “org.springframework.web.socket” 에 TRACE 로깅을 활성화 하여 로그를 볼 수 있다.

2. SockJS 활성화

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();
    }
 
}
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:websocket="http://www.springframework.org/schema/websocket" 
    xsi:schemaLocation=" 
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
 
    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:sockjs/>
    </websocket:handlers>
 
    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>
 
</beans>

위의 설정은 Spring MVC 에서 사용하기 위한 것으로 DispatcherServlet 설정에 포함되어져야 한다. (application context 를 계층적으로 가져갈 경우) 그러나 Spring WebSocket 이 SpringMVC 이외에도 사용할 수 있도록 제공되듯, SockJS 도 그러하다. 이는 SockJsHttpRequestHandler 를 통해서 제공된다.

browser (클라이언트) 측에서는 W3C WebSocket API 를 emulate 하는 sockjs-client 를 사용할 수 있다. 이를 통해 서버와 통신하여 최적의 전송타입을 선택한다. 이 외에도 포함할 전송타입을 지정하는 몇가지 설정을 제공한다.

3. IE 8, 9 에서의 HTTP Streaming (Ajax/XHR vs IFrame)

IE 8/9 는 대중적으로 사용되고 있다. 이것은 SockJS 가 필요한 핵심적인 이유이다.

SockJS 는 Ajax/XHR Streaming 을 Microsoft 의 XDomainRequest 를 통해 지원하고 있다. 이것은 서로 다른 도메인 간에도 동작하지만 cookie 전송을 지원하지 않는다. Cookie 는 Java Application 에서 매우 필수적이다. 그러나 SockJS 클라이언트는 Java 만을 위한 것이 아닌 많은 서버 타입을 위해 사용되도록 고안되었기 때문에 Cookie 를 중요하게 다룰지 여부를 알려주어야 한다. SockJS 클라이언트는 Ajax/XHR Streaming 을 선택하거나 그렇지 않다면 iframe 기반의 technique 을 사용한다.

SockJS 클라이언트로 부터의 최초 요청인 '/info' 는 클라이언트의 전송 타입 선택을 위한 정보를 위한 요청이다. 상세한 내용 중 하나는 서버 application 이 cookie 에 의존 (authentication 목적이나 session clustering with stick mode 등) 하는지 여부이다. Spring 의 SockJS 지원은 sessionCookieNeeded 라는 속성을 포함한다. 이것은 Java application 이 JSESSIONID cookie 에 의존하기 때문에 기본적으로 활성화 된다. 만약 이 기능을 OFF 한다면 비로서 SockJS 클라이언트는 xdr-streaming 을 IE 8/9 에서 사용토록 선택되어 진다.

iframe 기반의 전송을 사용한다면 browser 가 HTTP 응답헤더인 X-Frame-Option 이 DENY, SAMEORIGIN, ALLOW-FROM <origin> 로 설정됨에 따라 iframe 사용을 블락시킬수 있음을 염두에 두어야 한다. 이러한 헤더는 clickjacking 이라 알려진 공격을 방어하기 위한 목적으로 주로 사용된다.

Spring Security 3.2+ 에서 X-Frame-Options 헤더를 모든 응답에 설정하도록 지원하고 있다. 즉 Spring Security Java Config 에서 이 값을 DENY 로 기본설정한다. Spring Security XML 설정에서는 이 헤더를 설정하지 않지만 차후에 기본값으로 설정될 여지가 있다.

Spring Security 의 Section 7.1. Default Security Headers 에서 X-Frame-Options 헤더 설정에 대한 상세 문서를 살펴볼 수 있다. 이에 대한 추가적인 배경을 보고자 한다면 SEC-2501 을 살펴본다.

X-Frame-Option 응답헤더를 추가한다면 (Spring Security 를 사용한다면 그렇게 된다.) 헤더 값을 SAMEORIGIN 이나 ALLOW-FROM <origin> 으로 설정할 필요가 있다. 이와 더불어 Spring SockJS 지원은 SockJS 클라이언트의 location 을 알아야 할 필요가 있다. 왜냐하면 client 가 iframe 상에서 로드될 것이기 때문이다. 기본적으로 iframe 은 SockJS 클라이언트 (sockJS.js) 를 CDN location 으로부터 다운되도록 설정되어 있다. 이를 same origin 으로 설정하는 것이 필요하다.

Java Config 에서 아래와 같이 설정할 수 있다. XML 에서는 <websocket:sockjs> 에서 설정한다.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
 
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS()
                .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
    }
 
    // ...
 
}

초기 개발중에는 SockJS client devel mode 를 활성화 하라. 이는 SockJS request (iframe 같은) 를 브라우저가 캐시하는 것을 방지한다. 이에 대한 방법은 SockJS client page 에서 확인할 수 있다.

4. Heartbeat Messages

SockJS 프로토콜은 server 가 hearbeat message 를 전송하도록 하여 proxies 가 connection 을 hung 으로 인식하지 못하도록 할 필요가 있다. Spring SockJS 설정은 heartbeatTime 속성을 가지는데 빈도를 조정하는데 사용된다. 기본값은 해당 커넥션에 어떤 메시지도 없는 25초를 사용한다. 이 25초는 IETF 권고안 을 따르고 있다.

STOMP over WebSocket/SockJS 를 사용할 때 만약 STOMP 클라이언트와 서버가 heartbeat 교환에 합의한다면 SockJS heartbeat 은 비활성화 된다.

Spring SockJS 지원은 heartbeat 작업을 스케줄링하기 위해 TaskScheduler 를 설정하도록 하고 있다. TaskScheduler 는 이용가능한 processor 의 수에 따른 기본값으로 설정된 thread pool 로부터 가져오는데 이는 특정한 요구에 적합한 값을 설정하는 것을 고려해야 한다.

5. Servlet 3 Async Requests

HTTP streaming 과 HTTP long polling SockJS 전송은 커넥션을 보통의 경우보다 오래 open 되도록 한다. 이것에 대한 개요는 다음 에서 확인한다.

Servlet Container 에서 이것은 Servlet 3 async 지원을 통해 수행되는데 이것은 요청을 처리중인 Servlet 스레드를 빠져나오고 다른 Servlet 스레드에서 응답을 기록하도록 한다.

Servlet API 가 가지는 특정 이슈가 있는데 그것은 클라이언트의 갑작스러운 사라짐에 어떠한 알림도 제공하지 않는 것이다. ( SERVLET_SPEC-44 참고) 그러나 Servlet container 는 응답을 write 하기 위한 이어지는 시도에 예외를 발생한다. Spring SockJS 지원은 기본 25초 간격의 heartbeat message 를 전송하므로 이를 통해 보통 client disconnect 는 주기 안에 발견되어 진다. (주기를 짧게 하면 더 빨리 알게됨)

결론적으로, Network IO failure 은 client disconnect 에 대해서 빈번하게 발생할 여지가 있다. 이것은 로그를 stack trace 로 채우게 될 수도 있다. Spring 은 이러한 client disconnect 를 식별하기위해 최선의 노력을 하고 이에 따른 최소한의 메시지를 기록하려고 노력한다. (이 로그는 AbstractSockJsSession 에 정의된 DISCONNECTED_CLIENT_LOG_CATEGORY 로그 카테고리를 사용한다. 만약 stack trace 를 보고자 한다면 해당 로그 카테고리를 TRACE 로 설정하라)

6. CORS Headers for SockJS

SockJS protocol 은 XHR streaming 과 polling 전송에 cross-domain 지원을 위한 CORS 헤더를 사용한다. 그래서 CORS 헤더가 응답에서 발견되지 않는다면 CORS 헤더들이 자동으로 추가된다. 만약 Servlet Filter 등을 통해서 CORS 헤더가 이미 설정된다면 Spring SockJsService 는 이를 스킵한다.

다음은 SockJS 에 의해 기대되는 header 와 값들이다.

“Access-Control-Allow-Origin” - intitialized from the value of the “origin” request header or “*”. “Access-Control-Allow-Credentials” - always set to true. “Access-Control-Request-Headers” - initialized from values from the equivalent request header. “Access-Control-Allow-Methods” - the HTTP methods a transport supports (see TransportType enum). “Access-Control-Max-Age” - set to 31536000 (1 year).

정확한 구현을 보고자 한다면 AbstractSockJsService.addCorsHeaders() 와 TransportType enum 을 소스에서 살펴보라.

대안으로서 CORS 설정은 SockJS endpoint prefix 를 제외 URL 목록으로 고려한다면, Spring SockJsService 가 그것을 처리토록 한다.

 
egovframework/rte3.5/ptl/sockjs.txt · 마지막 수정: 2023/12/21 05:21 (외부 편집기)
 
이 위키의 내용은 다음의 라이센스에 따릅니다 :CC Attribution-Noncommercial-Share Alike 3.0 Unported
전자정부 표준프레임워크 라이센스(바로가기)

전자정부 표준프레임워크 활용의 안정성 보장을 위해 위험성을 지속적으로 모니터링하고 있으나, 오픈소스의 특성상 문제가 발생할 수 있습니다.
전자정부 표준프레임워크는 Apache 2.0 라이선스를 따르고 있는 오픈소스 프로그램입니다. Apache 2.0 라이선스에 따라 표준프레임워크를 활용하여 발생된 업무중단, 컴퓨터 고장 또는 오동작으로 인한 손해 등에 대해서 책임이 없습니다.
Recent changes RSS feed CC Attribution-Noncommercial-Share Alike 3.0 Unported Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki