Polish Cookie abstraction in http packge of spring-web

A getCookies method is now available on ServerHttpRequest with one
ServletServerCookie implementation that wraps a Servlet cookie.

The SockJS service makes use of this to check for an existing session
cookie in the request.
This commit is contained in:
Rossen Stoyanchev
2013-08-02 12:23:48 -04:00
parent c26272cef6
commit 0d5901ffb6
26 changed files with 258 additions and 353 deletions

View File

@@ -78,7 +78,7 @@ public abstract class AbstractSockJsService implements SockJsService {
private int streamBytesLimit = 128 * 1024;
private boolean jsessionIdCookieRequired = true;
private boolean sessionCookieEnabled = false;
private long heartbeatTime = 25 * 1000;
@@ -187,23 +187,22 @@ public abstract class AbstractSockJsService implements SockJsService {
}
/**
* Some load balancers do sticky sessions, but only if there is a JSESSIONID
* Some load balancers do sticky sessions, but only if there is a "JSESSIONID"
* cookie. Even if it is set to a dummy value, it doesn't matter since
* session information is added by the load balancer.
*
* <p>Set this option to indicate if a JSESSIONID cookie should be created. The
* default value is "true".
* <p>The default value is "false" since Java servers set the session cookie.
*/
public void setJsessionIdCookieRequired(boolean jsessionIdCookieRequired) {
this.jsessionIdCookieRequired = jsessionIdCookieRequired;
public void setDummySessionCookieEnabled(boolean sessionCookieEnabled) {
this.sessionCookieEnabled = sessionCookieEnabled;
}
/**
* Whether setting JSESSIONID cookie is necessary.
* @see #setJsessionIdCookieRequired(boolean)
* @see #setDummySessionCookieEnabled(boolean)
*/
public boolean isJsessionIdCookieRequired() {
return this.jsessionIdCookieRequired;
public boolean isDummySessionCookieEnabled() {
return this.sessionCookieEnabled;
}
/**
@@ -476,7 +475,7 @@ public abstract class AbstractSockJsService implements SockJsService {
addCorsHeaders(request, response);
addNoCacheHeaders(response);
String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieRequired(), isWebSocketEnabled());
String content = String.format(INFO_CONTENT, random.nextInt(), isDummySessionCookieEnabled(), isWebSocketEnabled());
response.getBody().write(content.getBytes());
}
else if (HttpMethod.OPTIONS.equals(request.getMethod())) {

View File

@@ -91,7 +91,7 @@ public enum TransportType {
return this.headerHints.contains("cors");
}
public boolean setsJsessionId() {
public boolean sendsSessionCookie() {
return this.headerHints.contains("jsessionid");
}

View File

@@ -118,7 +118,7 @@ public class DefaultSockJsService extends AbstractSockJsService {
* @param transportHandlerOverrides zero or more overrides to the default transport
* handler types.
*/
public DefaultSockJsService(TaskScheduler taskScheduler, Set<TransportHandler> transportHandlers,
public DefaultSockJsService(TaskScheduler taskScheduler, Collection<TransportHandler> transportHandlers,
TransportHandler... transportHandlerOverrides) {
super(taskScheduler);
@@ -254,11 +254,10 @@ public class DefaultSockJsService extends AbstractSockJsService {
addNoCacheHeaders(response);
}
if (transportType.setsJsessionId() && isJsessionIdCookieRequired()) {
Cookie cookie = request.getCookies().getCookie("JSESSIONID");
String jsid = (cookie != null) ? cookie.getValue() : "dummy";
// TODO: bypass use of Cookie object (causes Jetty to set Expires header)
response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/");
if (transportType.sendsSessionCookie() && isDummySessionCookieEnabled()) {
Cookie cookie = request.getCookies().get("JSESSIONID");
String value = (cookie != null) ? cookie.getValue() : "dummy";
response.getHeaders().set("Set-Cookie", "JSESSIONID=" + value + ";path=/");
}
if (transportType.supportsCors()) {

View File

@@ -149,7 +149,7 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
assertEquals(",\"origins\":[\"*:*\"],\"cookie_needed\":true,\"websocket\":true}",
body.substring(body.indexOf(',')));
this.service.setJsessionIdCookieRequired(false);
this.service.setDummySessionCookieEnabled(false);
this.service.setWebSocketsEnabled(false);
handleRequest("GET", "/a/info", HttpStatus.OK);
@@ -214,6 +214,7 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
assertEquals(httpStatus.value(), this.servletResponse.getStatus());
}
private static class TestSockJsService extends AbstractSockJsService {
private String sessionId;

View File

@@ -16,26 +16,18 @@
package org.springframework.web.socket.sockjs.transport.handler;
import java.util.Collections;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.sockjs.SockJsProcessingException;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
import org.springframework.web.socket.sockjs.transport.handler.SockJsSessionFactory;
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.session.TestSockJsSession;
@@ -50,10 +42,42 @@ import static org.mockito.Mockito.*;
*/
public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Override
private static final String sockJsPrefix = "mysockjs";
private static final String sessionId = "session1";
private static final String sessionUrlPrefix = "/mysockjs/server1/" + sessionId + "/";
@Mock private SessionCreatingTransportHandler xhrHandler;
@Mock private TransportHandler xhrSendHandler;
@Mock private WebSocketHandler wsHandler;
@Mock private TaskScheduler taskScheduler;
private TestSockJsSession session;
private DefaultSockJsService service;
@Before
public void setUp() {
public void setup() {
super.setUp();
MockitoAnnotations.initMocks(this);
this.session = new TestSockJsSession(sessionId, new StubSockJsServiceConfig(), this.wsHandler);
when(this.xhrHandler.getTransportType()).thenReturn(TransportType.XHR);
when(this.xhrHandler.createSession(sessionId, this.wsHandler)).thenReturn(this.session);
when(this.xhrSendHandler.getTransportType()).thenReturn(TransportType.XHR_SEND);
this.service = new DefaultSockJsService(this.taskScheduler,
Arrays.<TransportHandler>asList(this.xhrHandler, this.xhrSendHandler));
this.service.setValidSockJsPrefixes(sockJsPrefix);
}
@Test
@@ -76,24 +100,14 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test
public void handleTransportRequestXhr() throws Exception {
setRequest("POST", "/a/server/session/xhr");
TaskScheduler taskScheduler = mock(TaskScheduler.class);
StubXhrTransportHandler xhrHandler = new StubXhrTransportHandler();
Set<TransportHandler> transportHandlers = Collections.<TransportHandler>singleton(xhrHandler);
WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
DefaultSockJsService service = new DefaultSockJsService(taskScheduler, transportHandlers);
service.handleTransportRequest(this.request, this.response, webSocketHandler, "123", TransportType.XHR.value());
setRequest("POST", sessionUrlPrefix + "xhr");
this.service.handleRequest(this.request, this.response, this.wsHandler);
assertEquals(200, this.servletResponse.getStatus());
assertNotNull(xhrHandler.session);
assertSame(webSocketHandler, xhrHandler.webSocketHandler);
verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
verify(taskScheduler).scheduleAtFixedRate(any(Runnable.class), eq(service.getDisconnectDelay()));
assertEquals("no-store, no-cache, must-revalidate, max-age=0", this.response.getHeaders().getCacheControl());
assertEquals("JSESSIONID=dummy;path=/", this.response.getHeaders().getFirst("Set-Cookie"));
assertEquals("*", this.response.getHeaders().getFirst("Access-Control-Allow-Origin"));
assertEquals("true", this.response.getHeaders().getFirst("Access-Control-Allow-Credentials"));
}
@@ -101,14 +115,8 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test
public void handleTransportRequestXhrOptions() throws Exception {
setRequest("OPTIONS", "/a/server/session/xhr");
TaskScheduler taskScheduler = mock(TaskScheduler.class);
StubXhrTransportHandler xhrHandler = new StubXhrTransportHandler();
Set<TransportHandler> transportHandlers = Collections.<TransportHandler>singleton(xhrHandler);
DefaultSockJsService service = new DefaultSockJsService(taskScheduler, transportHandlers);
service.handleTransportRequest(this.request, this.response, null, "123", TransportType.XHR.value());
setRequest("OPTIONS", sessionUrlPrefix + "xhr");
this.service.handleRequest(this.request, this.response, this.wsHandler);
assertEquals(204, this.servletResponse.getStatus());
assertEquals("*", this.response.getHeaders().getFirst("Access-Control-Allow-Origin"));
@@ -116,14 +124,44 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
assertEquals("OPTIONS, POST", this.response.getHeaders().getFirst("Access-Control-Allow-Methods"));
}
@Test
public void dummySessionCookieEnabled() throws Exception {
setRequest("POST", sessionUrlPrefix + "xhr");
this.service.setDummySessionCookieEnabled(true);
this.service.handleRequest(this.request, this.response, this.wsHandler);
assertEquals(200, this.servletResponse.getStatus());
assertEquals("JSESSIONID=dummy;path=/", this.servletResponse.getHeader("Set-Cookie"));
}
@Test
public void dummySessionCookieDisabled() throws Exception {
setRequest("POST", sessionUrlPrefix + "xhr");
this.service.setDummySessionCookieEnabled(false);
this.service.handleTransportRequest(this.request, this.response, this.wsHandler, sessionId, "xhr");
assertEquals(200, this.servletResponse.getStatus());
assertNull(this.servletResponse.getHeader("Set-Cookie"));
}
@Test
public void dummySessionCookieReuseRequestCookieValue() throws Exception {
setRequest("POST", sessionUrlPrefix + "xhr");
this.servletRequest.addHeader("Cookie", "JSESSIONID=123456789");
this.service.handleTransportRequest(this.request, this.response, this.wsHandler, sessionId, "xhr");
assertEquals(200, this.servletResponse.getStatus());
assertNull(this.servletResponse.getHeader("Set-Cookie"));
}
@Test
public void handleTransportRequestNoSuitableHandler() throws Exception {
setRequest("POST", "/a/server/session/xhr");
Set<TransportHandler> transportHandlers = new HashSet<>();
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), transportHandlers);
service.handleTransportRequest(this.request, this.response, null, "123", TransportType.XHR.value());
setRequest("POST", sessionUrlPrefix + "eventsource");
this.service.handleRequest(this.request, this.response, this.wsHandler);
assertEquals(404, this.servletResponse.getStatus());
}
@@ -131,71 +169,28 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test
public void handleTransportRequestXhrSend() throws Exception {
this.servletRequest.setMethod("POST");
setRequest("POST", sessionUrlPrefix + "xhr_send");
this.service.handleRequest(this.request, this.response, this.wsHandler);
Set<TransportHandler> transportHandlers = new HashSet<>();
transportHandlers.add(new StubXhrTransportHandler());
transportHandlers.add(new StubXhrSendTransportHandler());
WebSocketHandler wsHandler = mock(WebSocketHandler.class);
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), transportHandlers);
service.handleTransportRequest(this.request, this.response, wsHandler, "123", TransportType.XHR_SEND.value());
assertEquals(404, this.servletResponse.getStatus()); // dropped (no session)
assertEquals(404, this.servletResponse.getStatus()); // no session yet
resetResponse();
service.handleTransportRequest(this.request, this.response, wsHandler, "123", TransportType.XHR.value());
setRequest("POST", sessionUrlPrefix + "xhr");
this.service.handleRequest(this.request, this.response, this.wsHandler);
assertEquals(200, this.servletResponse.getStatus());
assertEquals(200, this.servletResponse.getStatus()); // session created
verify(this.xhrHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
resetResponse();
service.handleTransportRequest(this.request, this.response, wsHandler, "123", TransportType.XHR_SEND.value());
setRequest("POST", sessionUrlPrefix + "xhr_send");
this.service.handleRequest(this.request, this.response, this.wsHandler);
assertEquals(200, this.servletResponse.getStatus());
assertEquals(200, this.servletResponse.getStatus()); // session exists
verify(this.xhrSendHandler).handleRequest(this.request, this.response, this.wsHandler, this.session);
}
private static class StubXhrTransportHandler implements TransportHandler, SockJsSessionFactory {
WebSocketHandler webSocketHandler;
WebSocketSession session;
@Override
public TransportType getTransportType() {
return TransportType.XHR;
}
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, WebSocketSession session) throws SockJsProcessingException {
this.webSocketHandler = handler;
this.session = session;
}
@Override
public AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) {
return new TestSockJsSession(sessionId, new StubSockJsServiceConfig(), webSocketHandler);
}
}
private static class StubXhrSendTransportHandler implements TransportHandler {
@Override
public TransportType getTransportType() {
return TransportType.XHR_SEND;
}
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, WebSocketSession session) throws SockJsProcessingException {
if (session == null) {
response.setStatusCode(HttpStatus.NOT_FOUND);
}
}
interface SessionCreatingTransportHandler extends TransportHandler, SockJsSessionFactory {
}
}