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}