Loading...
Spring Framework Reference Documentation 7.0.2의 SockJS Fallback의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
공용 Internet 환경에서는, 제어할 수 없는 restrictive proxy 때문에 WebSocket
상호 작용이 불가능할 수 있습니다. 예를 들어, proxy가 Upgrade 헤더를 전달하도록
구성되어 있지 않거나, idle 상태로 보이는 long-lived 연결을 닫는 경우가 있습니다.
이 문제에 대한 해결책은 WebSocket 에뮬레이션입니다. 즉, 먼저 WebSocket을 시도하고, 그다음 WebSocket 상호 작용을 에뮬레이션하고 동일한 애플리케이션 레벨 API를 제공하는 HTTP 기반 기술로 폴백하는 것입니다.
Servlet 스택에서 Spring Framework는 SockJS 프로토콜에 대한 서버 (및 클라이언트) 지원을 제공합니다.
SockJS의 목표는 애플리케이션이 WebSocket API를 사용하도록 하되, 런타임 시 필요할 경우 애플리케이션 코드를 변경할 필요 없이 non-WebSocket 대안으로 폴백할 수 있도록 하는 것입니다.
SockJS는 다음으로 구성됩니다:
spring-websocket 모듈에 포함된 것을 비롯한 SockJS 서버 구현체들.spring-websocket 모듈에 포함된 SockJS Java 클라이언트 (version 4.1부터).SockJS는 브라우저에서 사용하도록 설계되었습니다. 다양한 브라우저 버전을 지원하기 위해 여러 가지 기술을 사용합니다.
SockJS transport 타입과 브라우저의 전체 목록은 SockJS client 페이지를 참고하십시오. Transport는 크게 세 가지 카테고리로 나뉩니다: WebSocket, HTTP Streaming, HTTP Long Polling.
이러한 카테고리에 대한 개요는 this blog post를 참고하십시오.
SockJS 클라이언트는 먼저 GET /info를 전송하여
서버로부터 기본 정보를 가져옵니다. 그 후 어떤 transport를 사용할지
결정해야 합니다. 가능하다면 WebSocket이 사용됩니다.
그렇지 않은 경우, 대부분의 브라우저에서는 최소한 하나의 HTTP 스트리밍 옵션이 있습니다. 그것도 안 되면 HTTP (long) 폴링이 사용됩니다.
모든 transport 요청은 다음과 같은 URL 구조를 가집니다:
1https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
여기서:
{server-id}는 클러스터에서 요청을 라우팅하는 데 유용하지만 그 외에는 사용되지 않습니다.{session-id}는 SockJS 세션에 속한 HTTP 요청을 상호 연관시킵니다.{transport}는 transport 타입을 나타냅니다 (예: websocket, xhr-streaming 등).WebSocket transport는 WebSocket 핸드셰이크를 위해 단 하나의 HTTP 요청만 필요로 합니다. 그 이후의 모든 메시지는 해당 소켓에서 교환됩니다.
HTTP transport는 더 많은 요청이 필요합니다. 예를 들어 Ajax/XHR 스트리밍은 server-to-client 메시지를 위한 하나의 long-running 요청과 client-to-server 메시지를 위한 추가 HTTP POST 요청에 의존합니다. Long 폴링도 유사하지만, 각 server-to-client 전송 후에 현재 요청을 종료합니다.
SockJS는 최소한의 메시지 프레이밍을 추가합니다. 예를 들어 서버는 처음에
문자 o (“open” 프레임)를 보내고, 메시지는 a["message1","message2"]
(JSON 인코딩 배열)로 전송되며, 25초 동안 (기본값) 메시지가 흐르지 않으면
문자 h (“heartbeat” 프레임)를 보내고, 세션을 종료하기 위해 문자 c
(“close” 프레임)를 보냅니다.
자세히 알아보려면 브라우저에서 예제를 실행하고 HTTP 요청을 관찰해 보십시오. SockJS 클라이언트는 transport 목록을 고정할 수 있으므로, 각 transport를 하나씩 확인할 수 있습니다. SockJS 클라이언트는 또한 브라우저 콘솔에 유용한 메시지를 출력하는 디버그 플래그를 제공합니다.
서버 사이드에서는
org.springframework.web.socket에 대해 TRACE 로깅을 활성화할 수 있습니다.
더 자세한 내용은 SockJS 프로토콜의
narrated test를 참고하십시오.
다음 예제에서 볼 수 있듯이 설정을 통해 SockJS를 활성화할 수 있습니다:
1@Configuration 2@EnableWebSocket 3public class WebSocketConfiguration implements WebSocketConfigurer { 4 5 @Override 6 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 7 registry.addHandler(myHandler(), "/myHandler").withSockJS(); 8 } 9 10 @Bean 11 public WebSocketHandler myHandler() { 12 return new MyHandler(); 13 } 14 15}
1@Configuration 2@EnableWebSocket 3class WebSocketConfiguration : WebSocketConfigurer { 4 override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { 5 registry.addHandler(myHandler(), "/myHandler").withSockJS() 6 } 7 8 @Bean 9 fun myHandler(): WebSocketHandler { 10 return MyHandler() 11 } 12}
1<beans xmlns="http://www.springframework.org/schema/beans" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:websocket="http://www.springframework.org/schema/websocket" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans 6 https://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/websocket 8 https://www.springframework.org/schema/websocket/spring-websocket.xsd"> 9 10 <websocket:handlers> 11 <websocket:mapping path="/myHandler" handler="myHandler"/> 12 <websocket:sockjs/> 13 </websocket:handlers> 14 15 <bean id="myHandler" class="org.springframework.docs.web.websocket.websocketserverhandler.MyHandler"/> 16 17</beans>
앞의 예제는 Spring MVC 애플리케이션에서 사용하기 위한 것이며
DispatcherServlet의
설정에 포함되어야 합니다. 그러나 Spring의 WebSocket
및 SockJS 지원은 Spring MVC에 의존하지 않습니다.
비교적 간단하게
SockJsHttpRequestHandler의 도움으로
다른 HTTP 서빙 환경에 통합할 수 있습니다.
브라우저 사이드에서는 애플리케이션이
sockjs-client (version 1.0.x)을 사용할 수 있습니다. 이는
W3C WebSocket API를 에뮬레이션하고 서버와 통신하여 실행 중인 브라우저에 따라
최적의 transport 옵션을 선택합니다.
자세한 내용은 sockjs-client 페이지와 브라우저별로 지원되는 transport 타입 목록을 참고하십시오. 클라이언트는 또한 어떤 transport를 포함할지 지정하는 등의 여러 설정 옵션을 제공합니다.
Internet Explorer 8과 9는 여전히 사용되고 있습니다. 이들은 SockJS가 필요한 주요 이유입니다. 이 섹션에서는 해당 브라우저에서 실행할 때 중요한 고려 사항을 다룹니다.
SockJS 클라이언트는 Microsoft의
XDomainRequest를 사용하여
IE 8과 9에서 Ajax/XHR 스트리밍을 지원합니다.
이는 도메인 간 작동하지만 쿠키 전송을 지원하지 않습니다.
쿠키는 Java 애플리케이션에서 종종 필수적입니다. 그러나 SockJS 클라이언트는 (Java뿐 아니라) 여러 서버 타입과 함께 사용할 수 있으므로, 쿠키가 중요한지 여부를 알아야 합니다.
중요한 경우 SockJS 클라이언트는 스트리밍을 위해 Ajax/XHR을 선호합니다. 그렇지 않으면 iframe 기반 기법에 의존합니다.
SockJS 클라이언트의 첫 번째 /info 요청은
클라이언트의 transport 선택에 영향을 줄 수 있는 정보를 요청하는 것입니다.
그 세부 정보 중 하나는 서버 애플리케이션이 쿠키에 의존하는지 여부입니다
(예: 인증 목적 또는 sticky 세션을 사용하는 클러스터링).
Spring의 SockJS 지원에는 sessionCookieNeeded라는 프로퍼티가 있습니다.
대부분의 Java 애플리케이션이 JSESSIONID
쿠키에 의존하므로 기본적으로 활성화되어 있습니다. 애플리케이션이 이를 필요로 하지 않는다면
이 옵션을 끌 수 있고, 그러면 SockJS 클라이언트는 IE 8과 9에서 xdr-streaming을 선택해야 합니다.
iframe 기반 transport를 사용하는 경우,
HTTP 응답 헤더 X-Frame-Options를 DENY,
SAMEORIGIN, 또는 ALLOW-FROM <origin>으로 설정하여
특정 페이지에서 IFrame 사용을 차단하도록 브라우저에 지시할 수 있다는 점을
염두에 두십시오. 이는
clickjacking을 방지하는 데 사용됩니다.
Spring Security 3.2+는 모든 응답에 대해
X-Frame-Options를 설정하는 기능을 제공합니다. 기본적으로 Spring Security Java 설정은 이를DENY로 설정합니다. 3.2에서 Spring Security XML namespace는 기본적으로 해당 헤더를 설정하지 않지만, 설정하도록 구성할 수 있습니다. 향후에는 기본적으로 설정될 수도 있습니다.X-Frame-Options헤더 설정 방법에 대한 자세한 내용은 Spring Security 문서의 Default Security Headers를 참고하십시오. 추가적인 배경은 gh-2718을 참조할 수 있습니다.
애플리케이션이 X-Frame-Options 응답 헤더를 추가하고 (그렇게 해야 합니다!)
iframe 기반 transport에 의존하는 경우, 헤더 값을
SAMEORIGIN 또는 ALLOW-FROM <origin>으로 설정해야 합니다. Spring SockJS
지원은 또한 SockJS 클라이언트의 위치를 알아야 합니다.
이는 iframe에서 로드되기 때문입니다. 기본적으로 iframe은 CDN 위치에서 SockJS 클라이언트를 다운로드하도록 설정되어 있습니다. 이 옵션을 애플리케이션과 동일한 origin의 URL을 사용하도록 설정하는 것이 좋습니다.
다음 예제는 Java 설정에서 이를 수행하는 방법을 보여줍니다:
1@Configuration 2@EnableWebSocketMessageBroker 3public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 4 5 @Override 6 public void registerStompEndpoints(StompEndpointRegistry registry) { 7 registry.addEndpoint("/portfolio").withSockJS() 8 .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js"); 9 } 10 11 // ... 12 13}
XML namespace는 <websocket:sockjs> element를 통해 유사한 옵션을 제공합니다.
초기 개발 중에는 브라우저가 (iframe과 같이) SockJS 요청을 캐시하지 못하도록 방지하는 SockJS 클라이언트
devel모드를 활성화해야 합니다. 그렇지 않으면 캐시될 수 있습니다. 이를 활성화하는 방법에 대한 자세한 내용은 SockJS client 페이지를 참고하십시오.
SockJS 프로토콜은 proxy가 연결이 hung 상태라고 결론짓지 않도록
서버가 heartbeat 메시지를 보내도록 요구합니다. Spring SockJS 설정에는
주기를 커스터마이즈하는 데 사용할 수 있는 heartbeatTime이라는 프로퍼티가 있습니다.
기본적으로, 해당 연결에서 다른 메시지가 전송되지 않았다고 가정하면 25초 후에 heartbeat가 전송됩니다. 이 25초 값은 공용 Internet 애플리케이션에 대한 다음 IETF recommendation에 부합합니다.
WebSocket 및 SockJS 위에서 STOMP를 사용할 때, STOMP 클라이언트와 서버가 교환할 heartbeat를 협상하면 SockJS heartbeat는 비활성화됩니다.
Spring SockJS 지원은 또한 heartbeat 태스크를 스케줄링하기 위해
TaskScheduler를 설정할 수 있게 해 줍니다. 태스크 스케줄러는 스레드 풀을
기반으로 하며, 기본 설정은 사용 가능한 프로세서 수에 따라 결정됩니다.
특정 요구 사항에 따라 설정을 커스터마이즈하는 것을 고려해야 합니다.
HTTP 스트리밍 및 HTTP 롱 폴링 SockJS transport는 일반적인 경우보다 더 오래 연결이 열린 상태를 유지해야 합니다. 이러한 기법에 대한 개요는 this blog post를 참고하십시오.
Servlet 컨테이너에서는 Servlet 3 비동기 지원을 통해 Servlet 컨테이너 스레드를 빠져나와 요청을 처리하고, 다른 스레드에서 응답에 계속 write할 수 있습니다.
특정 이슈는 Servlet API가 클라이언트가 연결이 끊어진 상태에 대한 notification을 제공하지 않는다는 점입니다. eclipse-ee4j/servlet-api#44를 참고하십시오.
그러나 Servlet 컨테이너는 이후 응답에 write하려는 시도에서 예외를 발생시킵니다. Spring의 SockJS Service는 서버-발신 heartbeat를 지원하므로 (기본적으로 25초마다), 클라이언트 disconnect는 일반적으로 해당 기간 내에 (또는 메시지가 더 자주 전송되는 경우 더 일찍) 감지됩니다.
그 결과, 클라이언트가 disconnect되었기 때문에 network I/O failure가 발생할 수 있으며, 이는 불필요한 스택 트레이스로 로그를 가득 채울 수 있습니다. Spring은 각 서버에 특화된, 클라이언트 disconnect를 나타내는 이러한 네트워크 실패를 식별하고,
AbstractSockJsSession에 정의된 전용 로그 카테고리인DISCONNECTED_CLIENT_LOG_CATEGORY를 사용하여 최소한의 메시지만 로그하려고 최대한 노력합니다. 스택 트레이스를 확인해야 하는 경우 해당 로그 카테고리를 TRACE로 설정할 수 있습니다.
Cross-origin 요청을 허용하는 경우 (참고: Allowed Origins), SockJS 프로토콜은 XHR 스트리밍 및 폴링 transport에서 cross-domain 지원을 위해 CORS를 사용합니다.
따라서 응답에 CORS 헤더가 존재하는 것이 감지되지 않는 한 CORS 헤더는 자동으로
추가됩니다. 따라서 애플리케이션이 이미 CORS 지원을 제공하도록 설정되어 있는 경우
(예: Servlet 필터를 통해), Spring의 SockJsService는 이 부분을 건너뜁니다.
Spring의 SockJsService에서 suppressCors 프로퍼티를 설정하여
이러한 CORS 헤더 추가를 비활성화할 수도 있습니다.
SockJS는 다음 헤더와 값을 기대합니다:
Access-Control-Allow-Origin: Origin 요청 헤더의 값에서 초기화됩니다.Access-Control-Allow-Credentials: 항상 true로 설정됩니다.Access-Control-Request-Headers: 동등한 요청 헤더의 값에서 초기화됩니다.Access-Control-Allow-Methods: transport가 지원하는 HTTP 메서드 (TransportType enum 참조).Access-Control-Max-Age: 31536000 (1년)으로 설정됩니다.정확한 구현은 AbstractSockJsService의 addCorsHeaders와
소스 코드의 TransportType enum을 참고하십시오.
또는, CORS 설정이 허용하는 경우,
SockJS endpoint prefix가 있는 URL을 제외하여
Spring의 SockJsService가 이를 처리하도록 하는 것을 고려하십시오.
SockJsClientSpring은 브라우저를 사용하지 않고도 remote SockJS endpoint에 연결하는 SockJS Java 클라이언트를 제공합니다. 이는 public 네트워크를 통해 두 서버 간에 양방향 통신이 필요할 때 특히 유용합니다 (즉, 네트워크 proxy 때문에 WebSocket 프로토콜 사용이 불가능한 경우). SockJS Java 클라이언트는 또한 (예: 많은 수의 동시 사용자를 시뮬레이션하기 위해) 테스트 목적에도 매우 유용합니다.
SockJS Java 클라이언트는 websocket, xhr-streaming, xhr-polling
transport를 지원합니다. 나머지는 브라우저에서 사용되는 경우에만 의미가 있습니다.
WebSocketTransport는 다음과 함께 설정할 수 있습니다:
StandardWebSocketClient.JettyWebSocketClient.WebSocketClient 구현체.정의상 XhrTransport는 xhr-streaming과 xhr-polling 둘 다를 지원합니다.
클라이언트 관점에서는 서버에 연결하는 데 사용되는 URL을 제외하고
차이가 없습니다. 현재 두 가지 구현체가 있습니다:
RestTemplateXhrTransport는 HTTP 요청에 Spring의 RestTemplate을 사용합니다.JettyXhrTransport는 HTTP 요청에 Jetty의 HttpClient를 사용합니다.다음 예제는 SockJS 클라이언트를 생성하고 SockJS endpoint에 연결하는 방법을 보여줍니다:
1List<Transport> transports = new ArrayList<>(2); 2transports.add(new WebSocketTransport(new StandardWebSocketClient())); 3transports.add(new RestTemplateXhrTransport()); 4 5SockJsClient sockJsClient = new SockJsClient(transports); 6sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
SockJS는 메시지에 JSON formatted 배열을 사용합니다. 기본적으로 Jackson 2가 사용되며 classpath에 있어야 합니다. 또는
SockJsMessageCodec의 커스텀 구현체를 구성하고SockJsClient에 설정할 수 있습니다.
많은 수의 동시 사용자를 시뮬레이션하기 위해 SockJsClient를 사용하려면,
(XHR transport용) underlying HTTP 클라이언트를 충분한 수의 연결과 스레드를
허용하도록 설정해야 합니다. 다음 예제는 Jetty에서 이를 수행하는 방법을 보여줍니다:
1HttpClient jettyHttpClient = new HttpClient(); 2jettyHttpClient.setMaxConnectionsPerDestination(1000); 3jettyHttpClient.setExecutor(new QueuedThreadPool(1000));
다음 예제는 (자세한 내용은 javadoc 참조) 커스터마이즈하는 것을 고려해야 할 서버 사이드 SockJS 관련 프로퍼티를 보여줍니다:
1@Configuration 2public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport { 3 4 @Override 5 public void registerStompEndpoints(StompEndpointRegistry registry) { 6 registry.addEndpoint("/sockjs").withSockJS() 7 .setStreamBytesLimit(512 * 1024) // (1) 8 .setHttpMessageCacheSize(1000) // (2) 9 .setDisconnectDelay(30 * 1000); // (3) 10 } 11 12 // ... 13}
| 1 | streamBytesLimit 프로퍼티를 512KB로 설정합니다 (기본값은 128KB — 128 * 1024). |
| 2 | httpMessageCacheSize 프로퍼티를 1,000으로 설정합니다 (기본값은 100). |
| 3 | disconnectDelay 프로퍼티를 30초로 설정합니다 (기본값은 5초 — 5 * 1000). |
WebSocket API
STOMP