1
1
/*
2
- * Copyright 2002-2013 the original author or authors.
2
+ * Copyright 2002-2014 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
19
19
import java .net .URI ;
20
20
import java .security .Principal ;
21
21
import java .util .ArrayList ;
22
+ import java .util .HashSet ;
22
23
import java .util .List ;
23
24
import java .util .Map ;
25
+ import java .util .Set ;
24
26
import java .util .concurrent .ConcurrentHashMap ;
25
27
26
28
import org .apache .commons .logging .Log ;
31
33
import org .springframework .scheduling .TaskScheduler ;
32
34
import org .springframework .util .Assert ;
33
35
import org .springframework .util .ClassUtils ;
36
+ import org .springframework .util .CollectionUtils ;
34
37
import org .springframework .util .concurrent .ListenableFuture ;
35
38
import org .springframework .util .concurrent .SettableListenableFuture ;
36
39
import org .springframework .web .socket .WebSocketHandler ;
53
56
*
54
57
* @author Rossen Stoyanchev
55
58
* @since 4.1
56
- *
57
59
* @see <a href="http://sockjs.org">http://sockjs.org</a>
58
60
* @see org.springframework.web.socket.sockjs.client.Transport
59
61
*/
@@ -64,34 +66,41 @@ public class SockJsClient implements WebSocketClient, Lifecycle {
64
66
65
67
private static final Log logger = LogFactory .getLog (SockJsClient .class );
66
68
69
+ private static final Set <String > supportedProtocols = new HashSet <String >(4 );
70
+
71
+ static {
72
+ supportedProtocols .add ("ws" );
73
+ supportedProtocols .add ("wss" );
74
+ supportedProtocols .add ("http" );
75
+ supportedProtocols .add ("https" );
76
+ }
67
77
68
- private InfoReceiver infoReceiver ;
69
78
70
79
private final List <Transport > transports ;
71
80
81
+ private InfoReceiver infoReceiver ;
82
+
72
83
private SockJsMessageCodec messageCodec ;
73
84
74
85
private TaskScheduler connectTimeoutScheduler ;
75
86
76
- private final Map <URI , ServerInfo > serverInfoCache = new ConcurrentHashMap <URI , ServerInfo >();
77
-
78
87
private volatile boolean running = false ;
79
88
89
+ private final Map <URI , ServerInfo > serverInfoCache = new ConcurrentHashMap <URI , ServerInfo >();
90
+
80
91
81
92
/**
82
93
* Create a {@code SockJsClient} with the given transports.
83
- *
84
94
* <p>If the list includes an {@link XhrTransport} (or more specifically an
85
95
* implementation of {@link InfoReceiver}) the instance is used to initialize
86
96
* the {@link #setInfoReceiver(InfoReceiver) infoReceiver} property, or
87
97
* otherwise is defaulted to {@link RestTemplateXhrTransport}.
88
- *
89
98
* @param transports the (non-empty) list of transports to use
90
99
*/
91
100
public SockJsClient (List <Transport > transports ) {
92
101
Assert .notEmpty (transports , "No transports provided" );
93
- this .infoReceiver = initInfoReceiver (transports );
94
102
this .transports = new ArrayList <Transport >(transports );
103
+ this .infoReceiver = initInfoReceiver (transports );
95
104
if (jackson2Present ) {
96
105
this .messageCodec = new Jackson2SockJsMessageCodec ();
97
106
}
@@ -110,39 +119,37 @@ private static InfoReceiver initInfoReceiver(List<Transport> transports) {
110
119
/**
111
120
* Configure the {@code InfoReceiver} to use to perform the SockJS "Info"
112
121
* request before the SockJS session starts.
113
- *
114
122
* <p>If the list of transports provided to the constructor contained an
115
123
* {@link XhrTransport} or an implementation of {@link InfoReceiver} that
116
124
* instance would have been used to initialize this property, or otherwise
117
125
* it defaults to {@link RestTemplateXhrTransport}.
118
- *
119
126
* @param infoReceiver the transport to use for the SockJS "Info" request
120
127
*/
121
128
public void setInfoReceiver (InfoReceiver infoReceiver ) {
122
- Assert .notNull (infoReceiver , "'infoReceiver' is required" );
129
+ Assert .notNull (infoReceiver , "InfoReceiver is required" );
123
130
this .infoReceiver = infoReceiver ;
124
131
}
125
132
126
133
/**
127
- * Return the configured {@code InfoReceiver}, never {@code null}.
134
+ * Return the configured {@code InfoReceiver} ( never {@code null}) .
128
135
*/
129
136
public InfoReceiver getInfoReceiver () {
130
137
return this .infoReceiver ;
131
138
}
132
139
133
140
/**
134
141
* Set the SockJsMessageCodec to use.
135
- *
136
142
* <p>By default {@link org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec
137
143
* Jackson2SockJsMessageCodec} is used if Jackson is on the classpath.
138
- *
139
- * @param messageCodec the message messageCodec to use
140
144
*/
141
145
public void setMessageCodec (SockJsMessageCodec messageCodec ) {
142
146
Assert .notNull (messageCodec , "'messageCodec' is required" );
143
147
this .messageCodec = messageCodec ;
144
148
}
145
149
150
+ /**
151
+ * Return the SockJsMessageCodec to use.
152
+ */
146
153
public SockJsMessageCodec getMessageCodec () {
147
154
return this .messageCodec ;
148
155
}
@@ -159,6 +166,7 @@ public void setConnectTimeoutScheduler(TaskScheduler connectTimeoutScheduler) {
159
166
this .connectTimeoutScheduler = connectTimeoutScheduler ;
160
167
}
161
168
169
+
162
170
@ Override
163
171
public void start () {
164
172
if (!isRunning ()) {
@@ -192,35 +200,27 @@ public boolean isRunning() {
192
200
return this .running ;
193
201
}
194
202
195
- /**
196
- * By default the result of a SockJS "Info" request, including whether the
197
- * server has WebSocket disabled and how long the request took (used for
198
- * calculating transport timeout time) is cached. This method can be used to
199
- * clear that cache hence causing it to re-populate.
200
- */
201
- public void clearServerInfoCache () {
202
- this .serverInfoCache .clear ();
203
- }
204
203
205
204
@ Override
206
- public ListenableFuture <WebSocketSession > doHandshake (WebSocketHandler handler ,
207
- String uriTemplate , Object ... uriVars ) {
205
+ public ListenableFuture <WebSocketSession > doHandshake (
206
+ WebSocketHandler handler , String uriTemplate , Object ... uriVars ) {
208
207
209
208
Assert .notNull (uriTemplate , "uriTemplate must not be null" );
210
209
URI uri = UriComponentsBuilder .fromUriString (uriTemplate ).buildAndExpand (uriVars ).encode ().toUri ();
211
210
return doHandshake (handler , null , uri );
212
211
}
213
212
214
213
@ Override
215
- public final ListenableFuture <WebSocketSession > doHandshake (WebSocketHandler handler ,
216
- WebSocketHttpHeaders headers , URI url ) {
214
+ public final ListenableFuture <WebSocketSession > doHandshake (
215
+ WebSocketHandler handler , WebSocketHttpHeaders headers , URI url ) {
217
216
218
- Assert .notNull (handler , "'webSocketHandler' is required" );
219
- Assert .notNull (url , "'url' is required" );
217
+ Assert .notNull (handler , "WebSocketHandler is required" );
218
+ Assert .notNull (url , "URL is required" );
220
219
221
220
String scheme = url .getScheme ();
222
- Assert .isTrue (scheme != null && ("ws" .equals (scheme ) || "wss" .equals (scheme ) ||
223
- "http" .equals (scheme ) || "https" .equals (scheme )), "Invalid scheme: " + scheme );
221
+ if (!supportedProtocols .contains (scheme )) {
222
+ throw new IllegalArgumentException ("Invalid scheme: '" + scheme + "'" );
223
+ }
224
224
225
225
SettableListenableFuture <WebSocketSession > connectFuture = new SettableListenableFuture <WebSocketSession >();
226
226
try {
@@ -259,7 +259,10 @@ private DefaultTransportRequest createRequest(SockJsUrlInfo urlInfo, HttpHeaders
259
259
}
260
260
}
261
261
}
262
- Assert .notEmpty (requests , "No transports, " + urlInfo + ", wsEnabled=" + serverInfo .isWebSocketEnabled ());
262
+ if (CollectionUtils .isEmpty (requests )) {
263
+ throw new IllegalStateException (
264
+ "No transports: " + urlInfo + ", webSocketEnabled=" + serverInfo .isWebSocketEnabled ());
265
+ }
263
266
for (int i = 0 ; i < requests .size () - 1 ; i ++) {
264
267
DefaultTransportRequest request = requests .get (i );
265
268
request .setUser (getUser ());
@@ -274,15 +277,24 @@ private DefaultTransportRequest createRequest(SockJsUrlInfo urlInfo, HttpHeaders
274
277
275
278
/**
276
279
* Return the user to associate with the SockJS session and make available via
277
- * {@link org.springframework.web.socket.WebSocketSession#getPrincipal()
278
- * WebSocketSession#getPrincipal()}.
280
+ * {@link org.springframework.web.socket.WebSocketSession#getPrincipal()}.
279
281
* <p>By default this method returns {@code null}.
280
- * @return the user to associate with the session, possibly {@code null}
282
+ * @return the user to associate with the session ( possibly {@code null})
281
283
*/
282
284
protected Principal getUser () {
283
285
return null ;
284
286
}
285
287
288
+ /**
289
+ * By default the result of a SockJS "Info" request, including whether the
290
+ * server has WebSocket disabled and how long the request took (used for
291
+ * calculating transport timeout time) is cached. This method can be used to
292
+ * clear that cache hence causing it to re-populate.
293
+ */
294
+ public void clearServerInfoCache () {
295
+ this .serverInfoCache .clear ();
296
+ }
297
+
286
298
287
299
/**
288
300
* A simple value object holding the result from a SockJS "Info" request.
@@ -293,8 +305,7 @@ private static class ServerInfo {
293
305
294
306
private final long responseTime ;
295
307
296
-
297
- private ServerInfo (String response , long responseTime ) {
308
+ public ServerInfo (String response , long responseTime ) {
298
309
this .responseTime = responseTime ;
299
310
this .webSocketEnabled = !response .matches (".*[\" ']websocket[\" ']\\ s*:\\ s*false.*" );
300
311
}
@@ -308,4 +319,4 @@ public long getRetransmissionTimeout() {
308
319
}
309
320
}
310
321
311
- }
322
+ }
0 commit comments