diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java new file mode 100644 index 00000000000..f78c2ccf4ba --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2013 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.sockjs; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsSessionFactory{ + + S createSession(String sessionId); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index bff4ccace3a..2a81ca6f8c5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -58,7 +58,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { private int streamBytesLimit = 128 * 1024; - private boolean jsessionIdCookieNeeded = true; + private boolean jsessionIdCookieRequired = true; private long heartbeatTime = 25 * 1000; @@ -138,17 +138,17 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { * Set this option to indicate if a JSESSIONID cookie should be created. The * default value is "true". */ - public AbstractSockJsService setJsessionIdCookieNeeded(boolean jsessionIdCookieNeeded) { - this.jsessionIdCookieNeeded = jsessionIdCookieNeeded; + public AbstractSockJsService setJsessionIdCookieRequired(boolean jsessionIdCookieRequired) { + this.jsessionIdCookieRequired = jsessionIdCookieRequired; return this; } /** * Whether setting JSESSIONID cookie is necessary. - * @see #setJsessionIdCookieNeeded(boolean) + * @see #setJsessionIdCookieRequired(boolean) */ - public boolean isJsessionIdCookieNeeded() { - return this.jsessionIdCookieNeeded; + public boolean isJsessionIdCookieRequired() { + return this.jsessionIdCookieRequired; } public AbstractSockJsService setHeartbeatTime(long heartbeatTime) { @@ -344,7 +344,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { addCorsHeaders(request, response); addNoCacheHeaders(response); - String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieNeeded(), isWebSocketsEnabled()); + String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieRequired(), isWebSocketsEnabled()); response.getBody().write(content.getBytes()); } else if (HttpMethod.OPTIONS.equals(request.getMethod())) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 93ada27d1ea..8f4087227c4 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -29,12 +29,6 @@ public interface TransportHandler { TransportType getTransportType(); - boolean canCreateSession(); - - SockJsSessionSupport createSession(String sessionId); - - boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response); - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java index 5373d3edc8e..04936f7d562 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java @@ -15,6 +15,9 @@ */ package org.springframework.sockjs.server; +import java.util.Arrays; +import java.util.List; + import org.springframework.http.HttpMethod; @@ -25,30 +28,34 @@ import org.springframework.http.HttpMethod; */ public enum TransportType { - WEBSOCKET("websocket", HttpMethod.GET, false), + WEBSOCKET("websocket", HttpMethod.GET), - XHR("xhr", HttpMethod.POST, true), - XHR_SEND("xhr_send", HttpMethod.POST, true), + XHR("xhr", HttpMethod.POST, "cors", "jsessionid", "no_cache"), - JSONP("jsonp", HttpMethod.GET, false), - JSONP_SEND("jsonp_send", HttpMethod.POST, false), + XHR_SEND("xhr_send", HttpMethod.POST, "cors", "jsessionid", "no_cache"), - XHR_STREAMING("xhr_streaming", HttpMethod.POST, true), - EVENT_SOURCE("eventsource", HttpMethod.GET, false), - HTML_FILE("htmlfile", HttpMethod.GET, false); + JSONP("jsonp", HttpMethod.GET, "jsessionid", "no_cache"), + + JSONP_SEND("jsonp_send", HttpMethod.POST, "jsessionid", "no_cache"), + + XHR_STREAMING("xhr_streaming", HttpMethod.POST, "cors", "jsessionid", "no_cache"), + + EVENT_SOURCE("eventsource", HttpMethod.GET, "jsessionid", "no_cache"), + + HTML_FILE("htmlfile", HttpMethod.GET, "jsessionid", "no_cache"); private final String value; private final HttpMethod httpMethod; - private final boolean corsSupported; + private final List headerHints; - private TransportType(String value, HttpMethod httpMethod, boolean corsSupported) { + private TransportType(String value, HttpMethod httpMethod, String... headerHints) { this.value = value; this.httpMethod = httpMethod; - this.corsSupported = corsSupported; + this.headerHints = Arrays.asList(headerHints); } public String value() { @@ -62,11 +69,16 @@ public enum TransportType { return this.httpMethod; } - /** - * Are cross-domain requests (CORS) supported? - */ - public boolean isCorsSupported() { - return this.corsSupported; + public boolean setsNoCacheHeader() { + return this.headerHints.contains("no_cache"); + } + + public boolean supportsCors() { + return this.headerHints.contains("cors"); + } + + public boolean setsJsessionIdCookie() { + return this.headerHints.contains("jsessionid"); } public static TransportType fromValue(String transportValue) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 8a6ca6a5646..4cd8af9f1b8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -34,6 +34,7 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.sockjs.SockJsHandler; +import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.AbstractSockJsService; import org.springframework.sockjs.server.TransportHandler; @@ -188,14 +189,14 @@ public class DefaultSockJsService extends AbstractSockJsService HttpMethod supportedMethod = transportType.getHttpMethod(); if (!supportedMethod.equals(request.getMethod())) { - if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.isCorsSupported()) { + if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.supportsCors()) { response.setStatusCode(HttpStatus.NO_CONTENT); addCorsHeaders(request, response, supportedMethod, HttpMethod.OPTIONS); addCacheHeaders(response); } else { List supportedMethods = Arrays.asList(supportedMethod); - if (transportType.isCorsSupported()) { + if (transportType.supportsCors()) { supportedMethods.add(HttpMethod.OPTIONS); } sendMethodNotAllowed(response, supportedMethods); @@ -204,21 +205,22 @@ public class DefaultSockJsService extends AbstractSockJsService } SockJsSessionSupport session = getSockJsSession(sessionId, transportHandler); - if ((session == null) && !transportHandler.handleNoSession(request, response)) { - return; - } - addNoCacheHeaders(response); + if (session != null) { + if (transportType.setsNoCacheHeader()) { + addNoCacheHeaders(response); + } - if (isJsessionIdCookieNeeded()) { - 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=/"); // TODO - } + if (transportType.setsJsessionIdCookie() && 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=/"); // TODO + } - if (transportType.isCorsSupported()) { - addCorsHeaders(request, response); + if (transportType.supportsCors()) { + addCorsHeaders(request, response); + } } transportHandler.handleRequest(request, response, session); @@ -231,22 +233,22 @@ public class DefaultSockJsService extends AbstractSockJsService return session; } - if (!transportHandler.canCreateSession()) { - return null; - } + if (transportHandler instanceof SockJsSessionFactory) { + SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; - synchronized (this.sessions) { - session = this.sessions.get(sessionId); - if (session != null) { + synchronized (this.sessions) { + session = this.sessions.get(sessionId); + if (session != null) { + return session; + } + logger.debug("Creating new session with session id \"" + sessionId + "\""); + session = (SockJsSessionSupport) sessionFactory.createSession(sessionId); + this.sessions.put(sessionId, session); return session; } - - logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = transportHandler.createSession(sessionId); - this.sessions.put(sessionId, session); - - return session; } + + return null; } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 0597e8621f5..589e9770bd1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -51,25 +51,20 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport } @Override - public boolean canCreateSession() { - return false; - } - - @Override - public SockJsSessionSupport createSession(String sessionId) { - throw new IllegalStateException("Transport handlers receiving messages do not create new sessions"); - } - - @Override - public boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response) { - response.setStatusCode(HttpStatus.NOT_FOUND); - return false; - } - - @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception { + if (session == null) { + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + handleRequestInternal(request, response, session); + } + + protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + SockJsSessionSupport session) throws Exception { + String[] messages = null; try { messages = readMessages(request); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 8b43a6aff6a..f8dbd593661 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -34,7 +35,8 @@ import org.springframework.sockjs.server.TransportHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractHttpSendingTransportHandler implements TransportHandler { +public abstract class AbstractHttpSendingTransportHandler + implements TransportHandler, SockJsSessionFactory { protected final Log logger = LogFactory.getLog(this.getClass()); @@ -49,16 +51,6 @@ public abstract class AbstractHttpSendingTransportHandler implements TransportHa return this.sockJsConfig; } - @Override - public boolean canCreateSession() { - return true; - } - - @Override - public boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response) { - return true; - } - @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java index 0221c33a770..ba0a224bf65 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java @@ -50,21 +50,6 @@ public abstract class AbstractWebSocketTransportHandler implements TransportHand return TransportType.WEBSOCKET; } - @Override - public boolean canCreateSession() { - return false; - } - - @Override - public SockJsSessionSupport createSession(String sessionId) { - throw new IllegalStateException("WebSocket transport handlers do not create new sessions"); - } - - @Override - public boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response) { - return true; - } - @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index e839b77f4ee..029e5c4ebb1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -33,7 +33,7 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler } @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport sockJsSession) throws Exception { if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) {