001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.socket.config.annotation; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022 023import org.springframework.scheduling.TaskScheduler; 024import org.springframework.util.Assert; 025import org.springframework.util.ObjectUtils; 026import org.springframework.web.socket.server.HandshakeInterceptor; 027import org.springframework.web.socket.sockjs.SockJsService; 028import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; 029import org.springframework.web.socket.sockjs.transport.TransportHandler; 030import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService; 031import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; 032 033/** 034 * A helper class for configuring SockJS fallback options for use with an 035 * {@link org.springframework.web.socket.config.annotation.EnableWebSocket} and 036 * {@link WebSocketConfigurer} setup. 037 * 038 * @author Rossen Stoyanchev 039 * @since 4.0 040 */ 041public class SockJsServiceRegistration { 042 043 private TaskScheduler taskScheduler; 044 045 private String clientLibraryUrl; 046 047 private Integer streamBytesLimit; 048 049 private Boolean sessionCookieNeeded; 050 051 private Long heartbeatTime; 052 053 private Long disconnectDelay; 054 055 private Integer httpMessageCacheSize; 056 057 private Boolean webSocketEnabled; 058 059 private final List<TransportHandler> transportHandlers = new ArrayList<TransportHandler>(); 060 061 private final List<TransportHandler> transportHandlerOverrides = new ArrayList<TransportHandler>(); 062 063 private final List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>(); 064 065 private final List<String> allowedOrigins = new ArrayList<String>(); 066 067 private Boolean suppressCors; 068 069 private SockJsMessageCodec messageCodec; 070 071 072 public SockJsServiceRegistration(TaskScheduler defaultTaskScheduler) { 073 this.taskScheduler = defaultTaskScheduler; 074 } 075 076 077 public SockJsServiceRegistration setTaskScheduler(TaskScheduler taskScheduler) { 078 this.taskScheduler = taskScheduler; 079 return this; 080 } 081 082 /** 083 * Transports with no native cross-domain communication (e.g. "eventsource", 084 * "htmlfile") must get a simple page from the "foreign" domain in an invisible 085 * iframe so that code in the iframe can run from a domain local to the SockJS 086 * server. Since the iframe needs to load the SockJS javascript client library, 087 * this property allows specifying where to load it from. 088 * <p>By default this is set to point to 089 * "https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js". However it can 090 * also be set to point to a URL served by the application. 091 * <p>Note that it's possible to specify a relative URL in which case the URL 092 * must be relative to the iframe URL. For example assuming a SockJS endpoint 093 * mapped to "/sockjs", and resulting iframe URL "/sockjs/iframe.html", then the 094 * the relative URL must start with "../../" to traverse up to the location 095 * above the SockJS mapping. In case of a prefix-based Servlet mapping one more 096 * traversal may be needed. 097 */ 098 public SockJsServiceRegistration setClientLibraryUrl(String clientLibraryUrl) { 099 this.clientLibraryUrl = clientLibraryUrl; 100 return this; 101 } 102 103 /** 104 * Streaming transports save responses on the client side and don't free 105 * memory used by delivered messages. Such transports need to recycle the 106 * connection once in a while. This property sets a minimum number of bytes 107 * that can be send over a single HTTP streaming request before it will be 108 * closed. After that client will open a new request. Setting this value to 109 * one effectively disables streaming and will make streaming transports to 110 * behave like polling transports. 111 * <p>The default value is 128K (i.e. 128 * 1024). 112 */ 113 public SockJsServiceRegistration setStreamBytesLimit(int streamBytesLimit) { 114 this.streamBytesLimit = streamBytesLimit; 115 return this; 116 } 117 118 /** 119 * The SockJS protocol requires a server to respond to the initial "/info" request 120 * from clients with a "cookie_needed" boolean property that indicates whether the use 121 * of a JSESSIONID cookie is required for the application to function correctly, e.g. 122 * for load balancing or in Java Servlet containers for the use of an HTTP session. 123 * 124 * <p>This is especially important for IE 8,9 that support XDomainRequest -- a modified 125 * AJAX/XHR -- that can do requests across domains but does not send any cookies. In 126 * those cases, the SockJS client prefers the "iframe-htmlfile" transport over 127 * "xdr-streaming" in order to be able to send cookies. 128 * 129 * <p>The default value is "true" to maximize the chance for applications to work 130 * correctly in IE 8,9 with support for cookies (and the JSESSIONID cookie in 131 * particular). However, an application can choose to set this to "false" if the use 132 * of cookies (and HTTP session) is not required. 133 */ 134 public SockJsServiceRegistration setSessionCookieNeeded(boolean sessionCookieNeeded) { 135 this.sessionCookieNeeded = sessionCookieNeeded; 136 return this; 137 } 138 139 /** 140 * The amount of time in milliseconds when the server has not sent any 141 * messages and after which the server should send a heartbeat frame to the 142 * client in order to keep the connection from breaking. 143 * <p>The default value is 25,000 (25 seconds). 144 */ 145 public SockJsServiceRegistration setHeartbeatTime(long heartbeatTime) { 146 this.heartbeatTime = heartbeatTime; 147 return this; 148 } 149 150 /** 151 * The amount of time in milliseconds before a client is considered 152 * disconnected after not having a receiving connection, i.e. an active 153 * connection over which the server can send data to the client. 154 * <p>The default value is 5000. 155 */ 156 public SockJsServiceRegistration setDisconnectDelay(long disconnectDelay) { 157 this.disconnectDelay = disconnectDelay; 158 return this; 159 } 160 161 /** 162 * The number of server-to-client messages that a session can cache while waiting for 163 * the next HTTP polling request from the client. All HTTP transports use this 164 * property since even streaming transports recycle HTTP requests periodically. 165 * <p>The amount of time between HTTP requests should be relatively brief and will not 166 * exceed the allows disconnect delay (see 167 * {@link #setDisconnectDelay(long)}), 5 seconds by default. 168 * <p>The default size is 100. 169 */ 170 public SockJsServiceRegistration setHttpMessageCacheSize(int httpMessageCacheSize) { 171 this.httpMessageCacheSize = httpMessageCacheSize; 172 return this; 173 } 174 175 /** 176 * Some load balancers don't support WebSocket. This option can be used to 177 * disable the WebSocket transport on the server side. 178 * <p>The default value is "true". 179 */ 180 public SockJsServiceRegistration setWebSocketEnabled(boolean webSocketEnabled) { 181 this.webSocketEnabled = webSocketEnabled; 182 return this; 183 } 184 185 public SockJsServiceRegistration setTransportHandlers(TransportHandler... handlers) { 186 this.transportHandlers.clear(); 187 if (!ObjectUtils.isEmpty(handlers)) { 188 this.transportHandlers.addAll(Arrays.asList(handlers)); 189 } 190 return this; 191 } 192 193 public SockJsServiceRegistration setTransportHandlerOverrides(TransportHandler... handlers) { 194 this.transportHandlerOverrides.clear(); 195 if (!ObjectUtils.isEmpty(handlers)) { 196 this.transportHandlerOverrides.addAll(Arrays.asList(handlers)); 197 } 198 return this; 199 } 200 201 public SockJsServiceRegistration setInterceptors(HandshakeInterceptor... interceptors) { 202 this.interceptors.clear(); 203 if (!ObjectUtils.isEmpty(interceptors)) { 204 this.interceptors.addAll(Arrays.asList(interceptors)); 205 } 206 return this; 207 } 208 209 /** 210 * @since 4.1.2 211 */ 212 protected SockJsServiceRegistration setAllowedOrigins(String... allowedOrigins) { 213 this.allowedOrigins.clear(); 214 if (!ObjectUtils.isEmpty(allowedOrigins)) { 215 this.allowedOrigins.addAll(Arrays.asList(allowedOrigins)); 216 } 217 return this; 218 } 219 220 /** 221 * This option can be used to disable automatic addition of CORS headers for 222 * SockJS requests. 223 * <p>The default value is "false". 224 * @since 4.1.2 225 */ 226 public SockJsServiceRegistration setSupressCors(boolean suppressCors) { 227 this.suppressCors = suppressCors; 228 return this; 229 } 230 231 /** 232 * The codec to use for encoding and decoding SockJS messages. 233 * <p>By default {@code Jackson2SockJsMessageCodec} is used requiring the 234 * Jackson library to be present on the classpath. 235 * @param codec the codec to use. 236 * @since 4.1 237 */ 238 public SockJsServiceRegistration setMessageCodec(SockJsMessageCodec codec) { 239 this.messageCodec = codec; 240 return this; 241 } 242 243 protected SockJsService getSockJsService() { 244 TransportHandlingSockJsService service = createSockJsService(); 245 service.setHandshakeInterceptors(this.interceptors); 246 if (this.clientLibraryUrl != null) { 247 service.setSockJsClientLibraryUrl(this.clientLibraryUrl); 248 } 249 if (this.streamBytesLimit != null) { 250 service.setStreamBytesLimit(this.streamBytesLimit); 251 } 252 if (this.sessionCookieNeeded != null) { 253 service.setSessionCookieNeeded(this.sessionCookieNeeded); 254 } 255 if (this.heartbeatTime != null) { 256 service.setHeartbeatTime(this.heartbeatTime); 257 } 258 if (this.disconnectDelay != null) { 259 service.setDisconnectDelay(this.disconnectDelay); 260 } 261 if (this.httpMessageCacheSize != null) { 262 service.setHttpMessageCacheSize(this.httpMessageCacheSize); 263 } 264 if (this.webSocketEnabled != null) { 265 service.setWebSocketEnabled(this.webSocketEnabled); 266 } 267 if (this.suppressCors != null) { 268 service.setSuppressCors(this.suppressCors); 269 } 270 service.setAllowedOrigins(this.allowedOrigins); 271 272 if (this.messageCodec != null) { 273 service.setMessageCodec(this.messageCodec); 274 } 275 return service; 276 } 277 278 private TransportHandlingSockJsService createSockJsService() { 279 if (!this.transportHandlers.isEmpty()) { 280 Assert.state(this.transportHandlerOverrides.isEmpty(), 281 "Specify either TransportHandlers or TransportHandler overrides, not both"); 282 return new TransportHandlingSockJsService(this.taskScheduler, this.transportHandlers); 283 } 284 else { 285 return new DefaultSockJsService(this.taskScheduler, this.transportHandlerOverrides); 286 } 287 } 288 289}