/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.socket.sockjs.support;
import java.io.IOException;
import java.util.Arrays;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Test fixture for {@link AbstractSockJsService}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class SockJsServiceTests extends AbstractHttpRequestTests {
private TestSockJsService service;
private WebSocketHandler handler;
@Override
@Before
public void setUp() {
super.setUp();
this.service = new TestSockJsService(new ThreadPoolTaskScheduler());
}
@Test
public void validateRequest() throws Exception {
this.service.setWebSocketEnabled(false);
resetResponseAndHandleRequest("GET", "/echo/server/session/websocket", HttpStatus.NOT_FOUND);
this.service.setWebSocketEnabled(true);
resetResponseAndHandleRequest("GET", "/echo/server/session/websocket", HttpStatus.OK);
resetResponseAndHandleRequest("GET", "/echo//", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo///", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo/other", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo//service/websocket", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo/server//websocket", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo/server/session/", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo/s.erver/session/websocket", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo/server/s.ession/websocket", HttpStatus.NOT_FOUND);
resetResponseAndHandleRequest("GET", "/echo/server/session/jsonp;Setup.pl", HttpStatus.NOT_FOUND);
}
@Test
public void handleInfoGet() throws Exception {
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
assertEquals("application/json;charset=UTF-8", this.servletResponse.getContentType());
assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.servletResponse.getHeader(HttpHeaders.CACHE_CONTROL));
assertNull(this.servletResponse.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
assertNull(this.servletResponse.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));
assertNull(this.servletResponse.getHeader(HttpHeaders.VARY));
String body = this.servletResponse.getContentAsString();
assertEquals("{\"entropy\"", body.substring(0, body.indexOf(':')));
assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":true,\"websocket\":true}",
body.substring(body.indexOf(',')));
this.service.setSessionCookieNeeded(false);
this.service.setWebSocketEnabled(false);
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
body = this.servletResponse.getContentAsString();
assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":false,\"websocket\":false}",
body.substring(body.indexOf(',')));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
assertNull(this.servletResponse.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
assertNull(this.servletResponse.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));
assertNull(this.servletResponse.getHeader(HttpHeaders.VARY));
}
@Test // SPR-12226 and SPR-12660
public void handleInfoGetWithOrigin() throws Exception {
this.servletRequest.setServerName("mydomain2.com");
this.servletRequest.addHeader(HttpHeaders.ORIGIN, "http://mydomain2.com");
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
assertEquals("application/json;charset=UTF-8", this.servletResponse.getContentType());
assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.servletResponse.getHeader(HttpHeaders.CACHE_CONTROL));
String body = this.servletResponse.getContentAsString();
assertEquals("{\"entropy\"", body.substring(0, body.indexOf(':')));
assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":true,\"websocket\":true}", body.substring(body.indexOf(',')));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com"));
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
this.service.setAllowedOrigins(Arrays.asList("*"));
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
this.servletRequest.setServerName("mydomain3.com");
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.FORBIDDEN);
}
@Test // SPR-11443
public void handleInfoGetCorsFilter() throws Exception {
// Simulate scenario where Filter would have already set CORS headers
this.servletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "foobar:123");
handleRequest("GET", "/echo/info", HttpStatus.OK);
assertEquals("foobar:123", this.servletResponse.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test // SPR-11919
public void handleInfoGetWildflyNPE() throws Exception {
HttpServletResponse mockResponse = mock(HttpServletResponse.class);
ServletOutputStream ous = mock(ServletOutputStream.class);
given(mockResponse.getHeaders(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).willThrow(NullPointerException.class);
given(mockResponse.getOutputStream()).willReturn(ous);
this.response = new ServletServerHttpResponse(mockResponse);
handleRequest("GET", "/echo/info", HttpStatus.OK);
verify(mockResponse, times(1)).getOutputStream();
}
@Test // SPR-12660
public void handleInfoOptions() throws Exception {
this.servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Last-Modified");
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNull(this.service.getCorsConfiguration(this.servletRequest));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNull(this.service.getCorsConfiguration(this.servletRequest));
}
@Test // SPR-12226 and SPR-12660
public void handleInfoOptionsWithOrigin() throws Exception {
this.servletRequest.setServerName("mydomain2.com");
this.servletRequest.addHeader(HttpHeaders.ORIGIN, "http://mydomain2.com");
this.servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Last-Modified");
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNotNull(this.service.getCorsConfiguration(this.servletRequest));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNotNull(this.service.getCorsConfiguration(this.servletRequest));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNotNull(this.service.getCorsConfiguration(this.servletRequest));
this.service.setAllowedOrigins(Arrays.asList("*"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNotNull(this.service.getCorsConfiguration(this.servletRequest));
this.servletRequest.setServerName("mydomain3.com");
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.FORBIDDEN);
}
@Test // SPR-12283
public void handleInfoOptionsWithOriginAndCorsHeadersDisabled() throws Exception {
this.servletRequest.addHeader(HttpHeaders.ORIGIN, "http://mydomain2.com");
this.service.setAllowedOrigins(Arrays.asList("*"));
this.service.setSuppressCors(true);
this.servletRequest.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Last-Modified");
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNull(this.service.getCorsConfiguration(this.servletRequest));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.FORBIDDEN);
assertNull(this.service.getCorsConfiguration(this.servletRequest));
this.service.setAllowedOrigins(Arrays.asList("http://mydomain1.com", "http://mydomain2.com", "http://mydomain3.com"));
resetResponseAndHandleRequest("OPTIONS", "/echo/info", HttpStatus.NO_CONTENT);
assertNull(this.service.getCorsConfiguration(this.servletRequest));
}
@Test
public void handleIframeRequest() throws Exception {
resetResponseAndHandleRequest("GET", "/echo/iframe.html", HttpStatus.OK);
assertEquals("text/html;charset=UTF-8", this.servletResponse.getContentType());
assertTrue(this.servletResponse.getContentAsString().startsWith("<!DOCTYPE html>\n"));
assertEquals(490, this.servletResponse.getContentLength());
assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.response.getHeaders().getCacheControl());
assertEquals("\"0096cbd37f2a5218c33bb0826a7c74cbf\"", this.response.getHeaders().getETag());
}
@Test
public void handleIframeRequestNotModified() throws Exception {
this.servletRequest.addHeader("If-None-Match", "\"0096cbd37f2a5218c33bb0826a7c74cbf\"");
resetResponseAndHandleRequest("GET", "/echo/iframe.html", HttpStatus.NOT_MODIFIED);
}
@Test
public void handleRawWebSocketRequest() throws Exception {
resetResponseAndHandleRequest("GET", "/echo", HttpStatus.OK);
assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString());
resetResponseAndHandleRequest("GET", "/echo/websocket", HttpStatus.OK);
assertNull("Raw WebSocket should not open a SockJS session", this.service.sessionId);
assertSame(this.handler, this.service.handler);
}
@Test
public void handleEmptyContentType() throws Exception {
this.servletRequest.setContentType("");
resetResponseAndHandleRequest("GET", "/echo/info", HttpStatus.OK);
assertEquals("Invalid/empty content should have been ignored", 200, this.servletResponse.getStatus());
}
private void resetResponseAndHandleRequest(String httpMethod, String uri, HttpStatus httpStatus) throws IOException {
resetResponse();
handleRequest(httpMethod, uri, httpStatus);
}
private void handleRequest(String httpMethod, String uri, HttpStatus httpStatus) throws IOException {
setRequest(httpMethod, uri);
String sockJsPath = uri.substring("/echo".length());
this.service.handleRequest(this.request, this.response, sockJsPath, this.handler);
assertEquals(httpStatus.value(), this.servletResponse.getStatus());
}
private static class TestSockJsService extends AbstractSockJsService {
private String sessionId;
@SuppressWarnings("unused")
private String transport;
private WebSocketHandler handler;
public TestSockJsService(TaskScheduler scheduler) {
super(scheduler);
}
@Override
protected void handleRawWebSocketRequest(ServerHttpRequest req, ServerHttpResponse res,
WebSocketHandler handler) throws IOException {
this.handler = handler;
}
@Override
protected void handleTransportRequest(ServerHttpRequest req, ServerHttpResponse res, WebSocketHandler handler,
String sessionId, String transport) throws SockJsException {
this.sessionId = sessionId;
this.transport = transport;
this.handler = handler;
}
}
}