Skip to content

Commit 319f18d

Browse files
committed
Add HandshakeInterceptor
A HandshakeInterceptor can be used to intercept WebSocket handshakes (or SockJS requests where a new session is created) in order to inspect the request and response before and after the handshake including the ability to pass attributes to the WebSocketHandler, which the hander can access through WebSocketSession.getHandshakeAttributes() An HttpSessionHandshakeInterceptor is available that can copy attributes from the HTTP session to make them available to the WebSocket session. Issue: SPR-10624
1 parent 9925d83 commit 319f18d

46 files changed

Lines changed: 900 additions & 167 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.InetSocketAddress;
2121
import java.net.URI;
2222
import java.security.Principal;
23+
import java.util.Map;
2324

2425
import org.springframework.http.HttpHeaders;
2526

@@ -48,6 +49,13 @@ public interface WebSocketSession {
4849
*/
4950
HttpHeaders getHandshakeHeaders();
5051

52+
/**
53+
* Handshake request specific attributes.
54+
* To add attributes to a server-side WebSocket session see
55+
* {@link org.springframework.web.socket.server.HandshakeInterceptor}.
56+
*/
57+
Map<String, Object> getHandshakeAttributes();
58+
5159
/**
5260
* Return a {@link java.security.Principal} instance containing the name of the
5361
* authenticated user. If the user has not been authenticated, the method returns

spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssion.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.web.socket.adapter;
1717

1818
import java.io.IOException;
19+
import java.util.Map;
1920

2021
import org.apache.commons.logging.Log;
2122
import org.apache.commons.logging.LogFactory;
@@ -38,6 +39,24 @@ public abstract class AbstractWebSocketSesssion<T> implements DelegatingWebSocke
3839

3940
private T delegateSession;
4041

42+
private final Map<String, Object> handshakeAttributes;
43+
44+
45+
/**
46+
* Class constructor
47+
*
48+
* @param handshakeAttributes attributes from the HTTP handshake to make available
49+
* through the WebSocket session
50+
*/
51+
public AbstractWebSocketSesssion(Map<String, Object> handshakeAttributes) {
52+
this.handshakeAttributes = handshakeAttributes;
53+
}
54+
55+
56+
@Override
57+
public Map<String, Object> getHandshakeAttributes() {
58+
return this.handshakeAttributes;
59+
}
4160

4261
/**
4362
* @return the WebSocket session to delegate to

spring-websocket/src/main/java/org/springframework/web/socket/adapter/DelegatingWebSocketSession.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121

2222
/**
23-
* A contract for {@link WebSocketSession} implementations that delegate to another
24-
* WebSocket session (e.g. a native session).
23+
* A contract for a {@link WebSocketSession} that delegates to another WebSocket session
24+
* (e.g. a native session).
2525
*
2626
* @param T the type of the delegate WebSocket session
2727
*

spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSession.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.InetSocketAddress;
2121
import java.net.URI;
2222
import java.security.Principal;
23+
import java.util.Map;
2324

2425
import org.springframework.http.HttpHeaders;
2526
import org.springframework.util.ObjectUtils;
@@ -46,8 +47,11 @@ public class JettyWebSocketSession extends AbstractWebSocketSesssion<org.eclipse
4647
* Class constructor.
4748
*
4849
* @param principal the user associated with the session, or {@code null}
50+
* @param handshakeAttributes attributes from the HTTP handshake to make available
51+
* through the WebSocket session
4952
*/
50-
public JettyWebSocketSession(Principal principal) {
53+
public JettyWebSocketSession(Principal principal, Map<String, Object> handshakeAttributes) {
54+
super(handshakeAttributes);
5155
this.principal = principal;
5256
}
5357

spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSession.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.InetSocketAddress;
2121
import java.net.URI;
2222
import java.security.Principal;
23+
import java.util.Map;
2324

2425
import javax.websocket.CloseReason;
2526
import javax.websocket.CloseReason.CloseCodes;
@@ -39,7 +40,7 @@
3940
*/
4041
public class StandardWebSocketSession extends AbstractWebSocketSesssion<javax.websocket.Session> {
4142

42-
private final HttpHeaders headers;
43+
private final HttpHeaders handshakeHeaders;
4344

4445
private final InetSocketAddress localAddress;
4546

@@ -50,12 +51,17 @@ public class StandardWebSocketSession extends AbstractWebSocketSesssion<javax.we
5051
* Class constructor.
5152
*
5253
* @param handshakeHeaders the headers of the handshake request
54+
* @param handshakeAttributes attributes from the HTTP handshake to make available
55+
* through the WebSocket session
56+
* @param localAddress the address on which the request was received
57+
* @param remoteAddress the address of the remote client
5358
*/
54-
public StandardWebSocketSession(HttpHeaders handshakeHeaders, InetSocketAddress localAddress,
55-
InetSocketAddress remoteAddress) {
59+
public StandardWebSocketSession(HttpHeaders handshakeHeaders, Map<String, Object> handshakeAttributes,
60+
InetSocketAddress localAddress, InetSocketAddress remoteAddress) {
5661

62+
super(handshakeAttributes);
5763
handshakeHeaders = (handshakeHeaders != null) ? handshakeHeaders : new HttpHeaders();
58-
this.headers = HttpHeaders.readOnlyHttpHeaders(handshakeHeaders);
64+
this.handshakeHeaders = HttpHeaders.readOnlyHttpHeaders(handshakeHeaders);
5965
this.localAddress = localAddress;
6066
this.remoteAddress = remoteAddress;
6167
}
@@ -74,7 +80,7 @@ public URI getUri() {
7480

7581
@Override
7682
public HttpHeaders getHandshakeHeaders() {
77-
return this.headers;
83+
return this.handshakeHeaders;
7884
}
7985

8086
@Override

spring-websocket/src/main/java/org/springframework/web/socket/client/AbstractWebSocketClient.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import java.net.URI;
2020
import java.util.ArrayList;
21+
import java.util.Collections;
2122
import java.util.HashSet;
2223
import java.util.List;
24+
import java.util.Map;
2325
import java.util.Set;
2426

2527
import org.apache.commons.logging.Log;
@@ -94,21 +96,27 @@ public final WebSocketSession doHandshake(WebSocketHandler webSocketHandler,
9496
subProtocols.addAll(headers.getSecWebSocketProtocol());
9597
}
9698

97-
return doHandshakeInternal(webSocketHandler, headersToUse, uri, subProtocols);
99+
return doHandshakeInternal(webSocketHandler, headersToUse, uri, subProtocols,
100+
Collections.<String, Object>emptyMap());
98101
}
99102

100103
/**
101-
*
104+
* Perform the actual handshake to establish a connection to the server.
102105
*
103106
* @param webSocketHandler the client-side handler for WebSocket messages
104107
* @param headers HTTP headers to use for the handshake, with unwanted (forbidden)
105108
* headers filtered out, never {@code null}
106109
* @param uri the target URI for the handshake, never {@code null}
107110
* @param subProtocols requested sub-protocols, or an empty list
111+
* @param handshakeAttributes attributes to make available via
112+
* {@link WebSocketSession#getHandshakeAttributes()}; currently always an empty map.
113+
*
108114
* @return the established WebSocket session
115+
*
109116
* @throws WebSocketConnectFailureException
110117
*/
111118
protected abstract WebSocketSession doHandshakeInternal(WebSocketHandler webSocketHandler,
112-
HttpHeaders headers, URI uri, List<String> subProtocols) throws WebSocketConnectFailureException;
119+
HttpHeaders headers, URI uri, List<String> subProtocols,
120+
Map<String, Object> handshakeAttributes) throws WebSocketConnectFailureException;
113121

114122
}

spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,16 @@ public StandardWebSocketClient(WebSocketContainer webSocketContainer) {
6363

6464

6565
@Override
66-
protected WebSocketSession doHandshakeInternal(WebSocketHandler webSocketHandler,
67-
HttpHeaders headers, URI uri, List<String> protocols) throws WebSocketConnectFailureException {
66+
protected WebSocketSession doHandshakeInternal(WebSocketHandler webSocketHandler, HttpHeaders headers,
67+
URI uri, List<String> protocols, Map<String, Object> handshakeAttributes)
68+
throws WebSocketConnectFailureException {
6869

6970
int port = getPort(uri);
7071
InetSocketAddress localAddress = new InetSocketAddress(getLocalHost(), port);
7172
InetSocketAddress remoteAddress = new InetSocketAddress(uri.getHost(), port);
7273

73-
StandardWebSocketSession session = new StandardWebSocketSession(headers, localAddress, remoteAddress);
74+
StandardWebSocketSession session = new StandardWebSocketSession(headers,
75+
handshakeAttributes, localAddress, remoteAddress);
7476

7577
ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create();
7678
configBuidler.configurator(new StandardWebSocketClientConfigurator(headers));

spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package org.springframework.web.socket.client.jetty;
1818

1919
import java.net.URI;
20+
import java.security.Principal;
2021
import java.util.List;
22+
import java.util.Map;
2123

2224
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
2325
import org.springframework.context.SmartLifecycle;
@@ -122,25 +124,26 @@ public void stop(Runnable callback) {
122124
}
123125

124126
@Override
125-
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables)
127+
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVars)
126128
throws WebSocketConnectFailureException {
127129

128-
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode();
130+
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVars).encode();
129131
return doHandshake(webSocketHandler, null, uriComponents.toUri());
130132
}
131133

132134
@Override
133135
public WebSocketSession doHandshakeInternal(WebSocketHandler wsHandler, HttpHeaders headers,
134-
URI uri, List<String> protocols) throws WebSocketConnectFailureException {
136+
URI uri, List<String> protocols, Map<String, Object> handshakeAttributes)
137+
throws WebSocketConnectFailureException {
135138

136139
ClientUpgradeRequest request = new ClientUpgradeRequest();
137140
request.setSubProtocols(protocols);
138-
139141
for (String header : headers.keySet()) {
140142
request.setHeader(header, headers.get(header));
141143
}
142144

143-
JettyWebSocketSession wsSession = new JettyWebSocketSession(null);
145+
Principal user = getUser();
146+
JettyWebSocketSession wsSession = new JettyWebSocketSession(user, handshakeAttributes);
144147
JettyWebSocketHandlerAdapter listener = new JettyWebSocketHandlerAdapter(wsHandler, wsSession);
145148

146149
try {
@@ -153,4 +156,13 @@ public WebSocketSession doHandshakeInternal(WebSocketHandler wsHandler, HttpHead
153156
}
154157
}
155158

159+
160+
/**
161+
* @return the user to make available through {@link WebSocketSession#getPrincipal()};
162+
* by default this method returns {@code null}
163+
*/
164+
protected Principal getUser() {
165+
return null;
166+
}
167+
156168
}

spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.ArrayList;
2424
import java.util.Collections;
2525
import java.util.List;
26+
import java.util.Map;
2627

2728
import javax.xml.bind.DatatypeConverter;
2829

@@ -98,7 +99,7 @@ public String[] getSupportedProtocols() {
9899

99100
@Override
100101
public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response,
101-
WebSocketHandler webSocketHandler) throws IOException, HandshakeFailureException {
102+
WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws IOException, HandshakeFailureException {
102103

103104
logger.debug("Starting handshake for " + request.getURI());
104105

@@ -150,7 +151,7 @@ public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse r
150151
logger.trace("Upgrading with " + webSocketHandler);
151152
}
152153

153-
this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, webSocketHandler);
154+
this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, webSocketHandler, attributes);
154155

155156
return true;
156157
}

spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.socket.server;
1818

1919
import java.io.IOException;
20+
import java.util.Map;
2021

2122
import org.springframework.http.server.ServerHttpRequest;
2223
import org.springframework.http.server.ServerHttpResponse;
@@ -29,7 +30,9 @@
2930
* @author Rossen Stoyanchev
3031
* @since 4.0
3132
*
33+
* @see HandshakeInterceptor
3234
* @see org.springframework.web.socket.server.support.WebSocketHttpRequestHandler
35+
* @see org.springframework.web.socket.sockjs.SockJsService
3336
*/
3437
public interface HandshakeHandler {
3538

@@ -38,9 +41,11 @@ public interface HandshakeHandler {
3841
*
3942
* @param request the current request
4043
* @param response the current response
41-
* @param webSocketHandler the handler to process WebSocket messages; see
44+
* @param wsHandler the handler to process WebSocket messages; see
4245
* {@link PerConnectionWebSocketHandler} for providing a handler with
4346
* per-connection lifecycle.
47+
* @param attributes handshake request specific attributes to be set on the WebSocket
48+
* session and thus made available to the {@link WebSocketHandler}
4449
*
4550
* @return whether the handshake negotiation was successful or not. In either case the
4651
* response status, headers, and body will have been updated to reflect the
@@ -53,7 +58,7 @@ public interface HandshakeHandler {
5358
* opposed to a failure to successfully negotiate the requirements of the
5459
* handshake request.
5560
*/
56-
boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler)
57-
throws IOException, HandshakeFailureException;
61+
boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
62+
Map<String, Object> attributes) throws IOException, HandshakeFailureException;
5863

5964
}

0 commit comments

Comments
 (0)