1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.web.socket.sockjs.support;
18
19 import java.io.IOException;
20 import java.util.Arrays;
21 import javax.servlet.ServletOutputStream;
22 import javax.servlet.http.HttpServletResponse;
23
24 import static org.junit.Assert.assertEquals;
25 import org.junit.Before;
26 import org.junit.Test;
27
28 import org.springframework.http.HttpStatus;
29 import org.springframework.http.server.ServerHttpRequest;
30 import org.springframework.http.server.ServerHttpResponse;
31 import org.springframework.http.server.ServletServerHttpResponse;
32 import org.springframework.scheduling.TaskScheduler;
33 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
34 import org.springframework.web.socket.AbstractHttpRequestTests;
35 import org.springframework.web.socket.WebSocketHandler;
36 import org.springframework.web.socket.sockjs.SockJsException;
37
38 import static org.junit.Assert.*;
39 import static org.mockito.BDDMockito.*;
40
41
42
43
44
45
46
47 public class SockJsServiceTests extends AbstractHttpRequestTests {
48
49 private TestSockJsService service;
50
51 private WebSocketHandler handler;
52
53
54 @Override
55 @Before
56 public void setUp() {
57 super.setUp();
58 this.service = new TestSockJsService(new ThreadPoolTaskScheduler());
59 }
60
61
62 @Test
63 public void validateRequest() throws Exception {
64
65 this.service.setWebSocketEnabled(false);
66 resetResponseAndHandleRequest("GET", "/echo/server/session/websocket", HttpStatus.NOT_FOUND);
67
68 this.service.setWebSocketEnabled(true);
69 resetResponseAndHandleRequest("GET", "/echo/server/session/websocket", HttpStatus.OK);
70
71 resetResponseAndHandleRequest("GET", "/echo//", HttpStatus.NOT_FOUND);
72 resetResponseAndHandleRequest("GET", "/echo///", HttpStatus.NOT_FOUND);
73 resetResponseAndHandleRequest("GET", "/echo/other", HttpStatus.NOT_FOUND);
74 resetResponseAndHandleRequest("GET", "/echo//service/websocket", HttpStatus.NOT_FOUND);
75 resetResponseAndHandleRequest("GET", "/echo/server//websocket", HttpStatus.NOT_FOUND);
76 resetResponseAndHandleRequest("GET", "/echo/server/session/", HttpStatus.NOT_FOUND);
77 resetResponseAndHandleRequest("GET", "/echo/s.erver/session/websocket", HttpStatus.NOT_FOUND);
78 resetResponseAndHandleRequest("GET", "/echo/server/s.ession/websocket", HttpStatus.NOT_FOUND);
79 }
80
81 @Test
82 public void handleInfoGet() throws Exception {
83 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
84
85 assertEquals("application/json;charset=UTF-8", this.servletResponse.getContentType());
86 assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.servletResponse.getHeader("Cache-Control"));
87 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
88 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
89 assertNull(this.servletResponse.getHeader("Vary"));
90
91 String body = this.servletResponse.getContentAsString();
92 assertEquals("{\"entropy\"", body.substring(0, body.indexOf(':')));
93 assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":true,\"websocket\":true}",
94 body.substring(body.indexOf(',')));
95
96 this.service.setSessionCookieNeeded(false);
97 this.service.setWebSocketEnabled(false);
98 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
99
100 body = this.servletResponse.getContentAsString();
101 assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":false,\"websocket\":false}",
102 body.substring(body.indexOf(',')));
103
104 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
105 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
106 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
107 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
108 assertNull(this.servletResponse.getHeader("Vary"));
109 }
110
111 @Test
112 public void handleInfoGetWithOrigin() throws Exception {
113 this.servletRequest.setServerName("mydomain2.com");
114 setOrigin("http://mydomain2.com");
115 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
116
117 assertEquals("application/json;charset=UTF-8", this.servletResponse.getContentType());
118 assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.servletResponse.getHeader("Cache-Control"));
119 assertEquals("http://mydomain2.com", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
120 assertEquals("true", this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
121 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
122
123 String body = this.servletResponse.getContentAsString();
124 assertEquals("{\"entropy\"", body.substring(0, body.indexOf(':')));
125 assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":true,\"websocket\":true}",
126 body.substring(body.indexOf(',')));
127
128 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
129 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.FORBIDDEN);
130 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
131 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
132 assertNull(this.servletResponse.getHeader("Vary"));
133
134 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com"));
135 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
136 assertEquals("http://mydomain2.com", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
137 assertEquals("true", this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
138 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
139
140 this.service.setAllowedOrigins(Arrays.asList("*"));
141 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
142 assertEquals("http://mydomain2.com", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
143 assertEquals("true", this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
144 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
145 }
146
147 @Test
148 public void handleInfoGetCorsFilter() throws Exception {
149
150
151 this.servletResponse.setHeader("Access-Control-Allow-Origin", "foobar:123");
152
153 handleRequest("GET", "/echo/info", HttpStatus.OK);
154
155 assertEquals("foobar:123", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
156 }
157
158 @Test
159 public void handleInfoGetWildflyNPE() throws Exception {
160 HttpServletResponse mockResponse = mock(HttpServletResponse.class);
161 ServletOutputStream ous = mock(ServletOutputStream.class);
162 given(mockResponse.getHeaders("Access-Control-Allow-Origin")).willThrow(NullPointerException.class);
163 given(mockResponse.getOutputStream()).willReturn(ous);
164 this.response = new ServletServerHttpResponse(mockResponse);
165
166 handleRequest("GET", "/echo/info", HttpStatus.OK);
167
168 verify(mockResponse, times(1)).getOutputStream();
169 }
170
171 @Test
172 public void handleInfoOptions() throws Exception {
173 this.servletRequest.addHeader("Access-Control-Request-Headers", "Last-Modified");
174 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
175 this.response.flush();
176
177 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
178 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
179 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers"));
180 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods"));
181 assertNull(this.servletResponse.getHeader("Access-Control-Max-Age"));
182 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
183
184 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
185 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
186 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
187 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
188 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers"));
189 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods"));
190 assertNull(this.servletResponse.getHeader("Access-Control-Max-Age"));
191 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
192 }
193
194 @Test
195 public void handleInfoOptionsWithOrigin() throws Exception {
196 this.servletRequest.setServerName("mydomain2.com");
197 setOrigin("http://mydomain2.com");
198 this.request.getHeaders().add("Access-Control-Request-Headers", "Last-Modified");
199 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
200 this.response.flush();
201 assertEquals("http://mydomain2.com", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
202 assertEquals("true", this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
203 assertEquals("Last-Modified", this.servletResponse.getHeader("Access-Control-Allow-Headers"));
204 assertEquals("OPTIONS, GET", this.servletResponse.getHeader("Access-Control-Allow-Methods"));
205 assertEquals("31536000", this.servletResponse.getHeader("Access-Control-Max-Age"));
206 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
207
208 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
209 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.FORBIDDEN);
210 this.response.flush();
211 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
212 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
213 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers"));
214 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods"));
215 assertNull(this.servletResponse.getHeader("Access-Control-Max-Age"));
216 assertNull(this.servletResponse.getHeader("Vary"));
217
218 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com"));
219 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
220 this.response.flush();
221 assertEquals("http://mydomain2.com", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
222 assertEquals("true", this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
223 assertEquals("Last-Modified", this.servletResponse.getHeader("Access-Control-Allow-Headers"));
224 assertEquals("OPTIONS, GET", this.servletResponse.getHeader("Access-Control-Allow-Methods"));
225 assertEquals("31536000", this.servletResponse.getHeader("Access-Control-Max-Age"));
226 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
227
228 this.service.setAllowedOrigins(Arrays.asList("*"));
229 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
230 this.response.flush();
231 assertEquals("http://mydomain2.com", this.servletResponse.getHeader("Access-Control-Allow-Origin"));
232 assertEquals("true", this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
233 assertEquals("Last-Modified", this.servletResponse.getHeader("Access-Control-Allow-Headers"));
234 assertEquals("OPTIONS, GET", this.servletResponse.getHeader("Access-Control-Allow-Methods"));
235 assertEquals("31536000", this.servletResponse.getHeader("Access-Control-Max-Age"));
236 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
237 }
238
239 @Test
240 public void handleInfoOptionsWithOriginAndCorsHeadersDisabled() throws Exception {
241 setOrigin("http://mydomain2.com");
242 this.service.setAllowedOrigins(Arrays.asList("*"));
243 this.service.setSuppressCors(true);
244
245 this.servletRequest.addHeader("Access-Control-Request-Headers", "Last-Modified");
246 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
247 this.response.flush();
248 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
249 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
250 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers"));
251 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods"));
252 assertNull(this.servletResponse.getHeader("Access-Control-Max-Age"));
253 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
254
255 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
256 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.FORBIDDEN);
257 this.response.flush();
258 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
259 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
260 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers"));
261 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods"));
262 assertNull(this.servletResponse.getHeader("Access-Control-Max-Age"));
263 assertNull(this.servletResponse.getHeader("Vary"));
264
265 this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com"));
266 resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
267 this.response.flush();
268 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Origin"));
269 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Credentials"));
270 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Headers"));
271 assertNull(this.servletResponse.getHeader("Access-Control-Allow-Methods"));
272 assertNull(this.servletResponse.getHeader("Access-Control-Max-Age"));
273 assertEquals("Origin", this.servletResponse.getHeader("Vary"));
274 }
275
276 @Test
277 public void handleIframeRequest() throws Exception {
278 resetResponseAndHandleRequest("GET", "/echo/iframe.html", HttpStatus.OK);
279
280 assertEquals("text/html;charset=UTF-8", this.servletResponse.getContentType());
281 assertTrue(this.servletResponse.getContentAsString().startsWith("<!DOCTYPE html>\n"));
282 assertEquals(490, this.servletResponse.getContentLength());
283 assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.response.getHeaders().getCacheControl());
284 assertEquals("\"06b486b3208b085d9e3220f456a6caca4\"", this.response.getHeaders().getETag());
285 }
286
287 @Test
288 public void handleIframeRequestNotModified() throws Exception {
289 this.servletRequest.addHeader("If-None-Match", "\"06b486b3208b085d9e3220f456a6caca4\"");
290 resetResponseAndHandleRequest("GET", "/echo/iframe.html", HttpStatus.NOT_MODIFIED);
291 }
292
293 @Test
294 public void handleRawWebSocketRequest() throws Exception {
295 resetResponseAndHandleRequest("GET", "/echo", HttpStatus.OK);
296 assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString());
297
298 resetResponseAndHandleRequest("GET", "/echo/websocket", HttpStatus.OK);
299 assertNull("Raw WebSocket should not open a SockJS session", this.service.sessionId);
300 assertSame(this.handler, this.service.handler);
301 }
302
303 @Test
304 public void handleEmptyContentType() throws Exception {
305 this.servletRequest.setContentType("");
306 resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
307
308 assertEquals("Invalid/empty content should have been ignored", 200, this.servletResponse.getStatus());
309 }
310
311
312 private void resetResponseAndHandleRequest(String httpMethod, String uri, HttpStatus httpStatus) throws IOException {
313 resetResponse();
314 handleRequest(httpMethod, uri, httpStatus);
315 }
316
317 private void handleRequest(String httpMethod, String uri, HttpStatus httpStatus) throws IOException {
318 setRequest(httpMethod, uri);
319 String sockJsPath = uri.substring("/echo".length());
320 this.service.handleRequest(this.request, this.response, sockJsPath, this.handler);
321
322 assertEquals(httpStatus.value(), this.servletResponse.getStatus());
323 }
324
325
326 private static class TestSockJsService extends AbstractSockJsService {
327
328 private String sessionId;
329
330 @SuppressWarnings("unused")
331 private String transport;
332
333 private WebSocketHandler handler;
334
335 public TestSockJsService(TaskScheduler scheduler) {
336 super(scheduler);
337 }
338
339 @Override
340 protected void handleRawWebSocketRequest(ServerHttpRequest req, ServerHttpResponse res,
341 WebSocketHandler handler) throws IOException {
342 this.handler = handler;
343 }
344
345 @Override
346 protected void handleTransportRequest(ServerHttpRequest req, ServerHttpResponse res, WebSocketHandler handler,
347 String sessionId, String transport) throws SockJsException {
348 this.sessionId = sessionId;
349 this.transport = transport;
350 this.handler = handler;
351 }
352 }
353
354 }