diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/SimpMessageHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/SimpMessageHeaderAccessor.java index 69d26190b2..7af2a47af8 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/SimpMessageHeaderAccessor.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/SimpMessageHeaderAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -46,6 +46,8 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor { public static final String SESSION_ID_HEADER = "simpSessionId"; + public static final String SESSION_ATTRIBUTES = "simpSessionAttributes"; + public static final String SUBSCRIPTION_ID_HEADER = "simpSubscriptionId"; public static final String USER_HEADER = "simpUser"; @@ -127,6 +129,15 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor { setHeader(SESSION_ID_HEADER, sessionId); } + @SuppressWarnings("unchecked") + public Map getSessionAttributes() { + return (Map) getHeader(SESSION_ATTRIBUTES); + } + + public void setSessionAttributes(Map attributes) { + setHeader(SESSION_ATTRIBUTES, attributes); + } + public Principal getUser() { return (Principal) getHeader(USER_HEADER); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java index 3024319983..2d8f4e1636 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSession.java @@ -18,6 +18,7 @@ package org.springframework.web.socket.adapter; import java.io.IOException; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,16 +44,19 @@ public abstract class AbstractWebSocketSession implements NativeWebSocketSess private T nativeSession; - private final Map attributes; + private final Map attributes = new ConcurrentHashMap(); /** * Create a new instance and associate the given attributes with it. * - * @param attributes attributes from the HTTP handshake to associate with the WebSocket session + * @param attributes attributes from the HTTP handshake to associate with the WebSocket + * session; the provided attributes are copied, the original map is not used. */ public AbstractWebSocketSession(Map attributes) { - this.attributes = attributes; + if (attributes != null) { + this.attributes.putAll(attributes); + } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java index 46a2bda28a..0d4ff77a16 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/jetty/JettyWebSocketSession.java @@ -65,7 +65,8 @@ public class JettyWebSocketSession extends AbstractWebSocketSession { /** * Create a new {@link JettyWebSocketSession} instance associated with the given user. * - * @param attributes attributes from the HTTP handshake to associate with the WebSocket session + * @param attributes attributes from the HTTP handshake to associate with the WebSocket + * session; the provided attributes are copied, the original map is not used. * @param user the user associated with the session; if {@code null} we'll fallback on the user * available via {@link org.eclipse.jetty.websocket.api.Session#getUpgradeRequest()} */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSession.java index aa962a13b5..82d40ac978 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/standard/StandardWebSocketSession.java @@ -63,7 +63,8 @@ public class StandardWebSocketSession extends AbstractWebSocketSession * Class constructor. * * @param headers the headers of the handshake request - * @param attributes attributes from the HTTP handshake to associate with the WebSocket session + * @param attributes attributes from the HTTP handshake to associate with the WebSocket + * session; the provided attributes are copied, the original map is not used. * @param localAddress the address on which the request was received * @param remoteAddress the address of the remote client */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java index 911e574a7e..04506bb068 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java @@ -133,6 +133,7 @@ public class StompSubProtocolHandler implements SubProtocolHandler { } headers.setSessionId(session.getId()); + headers.setSessionAttributes(session.getAttributes()); headers.setUser(session.getPrincipal()); message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java index ecc4ac3d30..7660673edf 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java @@ -37,17 +37,19 @@ public interface HandshakeHandler { /** * Initiate the handshake. + * * @param request the current request * @param response the current response * @param wsHandler the handler to process WebSocket messages; see * {@link PerConnectionWebSocketHandler} for providing a handler with * per-connection lifecycle. - * @param attributes handshake request specific attributes to be set on the WebSocket - * session via {@link HandshakeInterceptor} and thus made available to the - * {@link WebSocketHandler}; + * @param attributes attributes from the HTTP handshake to associate with the WebSocket + * session; the provided attributes are copied, the original map is not used. + * * @return whether the handshake negotiation was successful or not. In either case the * response status, headers, and body will have been updated to reflect the * result of the negotiation + * * @throws HandshakeFailureException thrown when handshake processing failed to * complete due to an internal, unrecoverable error, i.e. a server error as * opposed to a failure to successfully negotiate the handshake. diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java index b02dfdaddf..8047038451 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java @@ -40,8 +40,8 @@ public interface HandshakeInterceptor { * @param request the current request * @param response the current response * @param wsHandler the target WebSocket handler - * @param attributes attributes from the HTTP handshake to associate with the - * WebSocket session, i.e. via {@link WebSocketSession#getAttributes()} + * @param attributes attributes from the HTTP handshake to associate with the WebSocket + * session; the provided attributes are copied, the original map is not used. * @return whether to proceed with the handshake {@code true} or abort {@code false} */ boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java index 9bf13d9a90..b9a11c882d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java @@ -110,7 +110,8 @@ public abstract class AbstractSockJsSession implements SockJsSession { * @param id the session ID * @param config SockJS service configuration options * @param handler the recipient of SockJS messages - * @param attributes attributes from the HTTP handshake to associate with the WebSocket session + * @param attributes attributes from the HTTP handshake to associate with the WebSocket + * session; the provided attributes are copied, the original map is not used. */ public AbstractSockJsSession(String id, SockJsServiceConfig config, WebSocketHandler handler, Map attributes) { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java index 733a140cf6..bec416f9dc 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompSubProtocolHandlerTests.java @@ -170,6 +170,7 @@ public class StompSubProtocolHandlerTests { StompHeaderAccessor headers = StompHeaderAccessor.wrap(actual); assertEquals(StompCommand.CONNECT, headers.getCommand()); assertEquals("s1", headers.getSessionId()); + assertNotNull(headers.getSessionAttributes()); assertEquals("joe", headers.getUser().getName()); assertEquals("guest", headers.getLogin()); assertEquals("PROTECTED", headers.getPasscode());