Browse Source
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-10624pull/333/merge
46 changed files with 900 additions and 167 deletions
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
/* |
||||
* 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.web.socket.server; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.WebSocketSession; |
||||
|
||||
|
||||
/** |
||||
* Interceptor for WebSocket handshake requests. Can be used to inspect the handshake |
||||
* request and response as well as to pass attributes to the target |
||||
* {@link WebSocketHandler}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
* |
||||
* @see org.springframework.web.socket.server.support.WebSocketHttpRequestHandler |
||||
* @see org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService |
||||
*/ |
||||
public interface HandshakeInterceptor { |
||||
|
||||
/** |
||||
* Invoked before the handshake is processed. |
||||
* |
||||
* @param request the current request |
||||
* @param response the current response |
||||
* @param wsHandler the target WebSocket handler |
||||
* @param attributes attributes to make available via |
||||
* {@link WebSocketSession#getHandshakeAttributes()} |
||||
* |
||||
* @return whether to proceed with the handshake {@code true} or abort {@code false} |
||||
*/ |
||||
boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, |
||||
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception; |
||||
|
||||
/** |
||||
* Invoked after the handshake is done. The response status and headers indicate the |
||||
* results of the handshake, i.e. whether it was successful or not. |
||||
* |
||||
* @param request the current request |
||||
* @param response the current response |
||||
* @param wsHandler the target WebSocket handler |
||||
* @param exception an exception raised during the handshake, or {@code null} |
||||
*/ |
||||
void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, |
||||
WebSocketHandler wsHandler, Exception exception); |
||||
|
||||
} |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
/* |
||||
* 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.web.socket.server.support; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.HandshakeInterceptor; |
||||
|
||||
|
||||
/** |
||||
* A helper class that assists with invoking a list of handshake interceptors. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public class HandshakeInterceptorChain { |
||||
|
||||
private static final Log logger = LogFactory.getLog(WebSocketHttpRequestHandler.class); |
||||
|
||||
private final List<HandshakeInterceptor> interceptors; |
||||
|
||||
private final WebSocketHandler wsHandler; |
||||
|
||||
private int interceptorIndex = -1; |
||||
|
||||
|
||||
public HandshakeInterceptorChain(List<HandshakeInterceptor> interceptors, WebSocketHandler wsHandler) { |
||||
this.interceptors = (interceptors != null) ? interceptors : Collections.<HandshakeInterceptor>emptyList(); |
||||
this.wsHandler = wsHandler; |
||||
} |
||||
|
||||
|
||||
public boolean applyBeforeHandshake(ServerHttpRequest request, ServerHttpResponse response, |
||||
Map<String, Object> attributes) throws Exception { |
||||
|
||||
for (int i = 0; i < this.interceptors.size(); i++) { |
||||
HandshakeInterceptor interceptor = this.interceptors.get(i); |
||||
if (!interceptor.beforeHandshake(request, response, this.wsHandler, attributes)) { |
||||
applyAfterHandshake(request, response, null); |
||||
return false; |
||||
} |
||||
this.interceptorIndex = i; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
|
||||
public void applyAfterHandshake(ServerHttpRequest request, ServerHttpResponse response, Exception failure) { |
||||
for (int i = this.interceptorIndex; i >= 0; i--) { |
||||
HandshakeInterceptor interceptor = this.interceptors.get(i); |
||||
try { |
||||
interceptor.afterHandshake(request, response, this.wsHandler, failure); |
||||
} |
||||
catch (Throwable t) { |
||||
logger.warn("HandshakeInterceptor afterHandshake threw exception " + t); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
/* |
||||
* 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.web.socket.server.support; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Enumeration; |
||||
import java.util.Map; |
||||
|
||||
import javax.servlet.http.HttpSession; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.http.server.ServletServerHttpRequest; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.WebSocketSession; |
||||
import org.springframework.web.socket.server.HandshakeInterceptor; |
||||
|
||||
|
||||
/** |
||||
* An interceptor to copy HTTP session attributes into the map of "handshake attributes" |
||||
* made available through {@link WebSocketSession#getHandshakeAttributes()}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public class HttpSessionHandshakeInterceptor implements HandshakeInterceptor { |
||||
|
||||
private static Log logger = LogFactory.getLog(HttpSessionHandshakeInterceptor.class); |
||||
|
||||
private Collection<String> attributeNames; |
||||
|
||||
|
||||
/** |
||||
* A constructor for copying all available HTTP session attributes. |
||||
*/ |
||||
public HttpSessionHandshakeInterceptor() { |
||||
this(null); |
||||
} |
||||
|
||||
/** |
||||
* A constructor for copying a subset of HTTP session attributes. |
||||
* @param attributeNames the HTTP session attributes to copy |
||||
*/ |
||||
public HttpSessionHandshakeInterceptor(Collection<String> attributeNames) { |
||||
this.attributeNames = attributeNames; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, |
||||
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { |
||||
|
||||
if (request instanceof ServletServerHttpRequest) { |
||||
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; |
||||
HttpSession session = servletRequest.getServletRequest().getSession(false); |
||||
if (session != null) { |
||||
Enumeration<String> names = session.getAttributeNames(); |
||||
while (names.hasMoreElements()) { |
||||
String name = names.nextElement(); |
||||
if (CollectionUtils.isEmpty(this.attributeNames) || this.attributeNames.contains(name)) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace("Adding HTTP session attribute to handshake attributes: " + name); |
||||
} |
||||
attributes.put(name, session.getAttribute(name)); |
||||
} |
||||
else { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace("Skipped HTTP session attribute"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, |
||||
WebSocketHandler wsHandler, Exception ex) { |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* 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.web.socket.server.support; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.web.socket.AbstractHttpRequestTests; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.HandshakeInterceptor; |
||||
|
||||
import static org.mockito.Mockito.*; |
||||
|
||||
|
||||
/** |
||||
* Test fixture for {@link HandshakeInterceptorChain}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class HandshakeInterceptorChainTests extends AbstractHttpRequestTests { |
||||
|
||||
private HandshakeInterceptor i1; |
||||
|
||||
private HandshakeInterceptor i2; |
||||
|
||||
private HandshakeInterceptor i3; |
||||
|
||||
private List<HandshakeInterceptor> interceptors; |
||||
|
||||
private WebSocketHandler wsHandler; |
||||
|
||||
private Map<String, Object> attributes; |
||||
|
||||
|
||||
@Before |
||||
public void setup() { |
||||
i1 = mock(HandshakeInterceptor.class); |
||||
i2 = mock(HandshakeInterceptor.class); |
||||
i3 = mock(HandshakeInterceptor.class); |
||||
interceptors = Arrays.asList(i1, i2, i3); |
||||
wsHandler = mock(WebSocketHandler.class); |
||||
attributes = new HashMap<String, Object>(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void success() throws Exception { |
||||
when(i1.beforeHandshake(request, response, wsHandler, attributes)).thenReturn(true); |
||||
when(i2.beforeHandshake(request, response, wsHandler, attributes)).thenReturn(true); |
||||
when(i3.beforeHandshake(request, response, wsHandler, attributes)).thenReturn(true); |
||||
|
||||
HandshakeInterceptorChain chain = new HandshakeInterceptorChain(interceptors, wsHandler); |
||||
chain.applyBeforeHandshake(request, response, attributes); |
||||
|
||||
verify(i1).beforeHandshake(request, response, wsHandler, attributes); |
||||
verify(i2).beforeHandshake(request, response, wsHandler, attributes); |
||||
verify(i3).beforeHandshake(request, response, wsHandler, attributes); |
||||
verifyNoMoreInteractions(i1, i2, i3); |
||||
} |
||||
|
||||
@Test |
||||
public void applyBeforeHandshakeWithFalseReturnValue() throws Exception { |
||||
when(i1.beforeHandshake(request, response, wsHandler, attributes)).thenReturn(true); |
||||
when(i2.beforeHandshake(request, response, wsHandler, attributes)).thenReturn(false); |
||||
|
||||
HandshakeInterceptorChain chain = new HandshakeInterceptorChain(interceptors, wsHandler); |
||||
chain.applyBeforeHandshake(request, response, attributes); |
||||
|
||||
verify(i1).beforeHandshake(request, response, wsHandler, attributes); |
||||
verify(i1).afterHandshake(request, response, wsHandler, null); |
||||
verify(i2).beforeHandshake(request, response, wsHandler, attributes); |
||||
verifyNoMoreInteractions(i1, i2, i3); |
||||
} |
||||
|
||||
@Test |
||||
public void applyAfterHandshakeOnly() { |
||||
HandshakeInterceptorChain chain = new HandshakeInterceptorChain(interceptors, wsHandler); |
||||
chain.applyAfterHandshake(request, response, null); |
||||
|
||||
verifyNoMoreInteractions(i1, i2, i3); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* |
||||
* 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.web.socket.server.support; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
import org.springframework.web.socket.AbstractHttpRequestTests; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
|
||||
/** |
||||
* Test fixture for {@link HttpSessionHandshakeInterceptor}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class HttpSessionHandshakeInterceptorTests extends AbstractHttpRequestTests { |
||||
|
||||
|
||||
@Test |
||||
public void copyAllAttributes() throws Exception { |
||||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>(); |
||||
WebSocketHandler wsHandler = Mockito.mock(WebSocketHandler.class); |
||||
|
||||
this.servletRequest.getSession().setAttribute("foo", "bar"); |
||||
this.servletRequest.getSession().setAttribute("bar", "baz"); |
||||
|
||||
HttpSessionHandshakeInterceptor interceptor = new HttpSessionHandshakeInterceptor(); |
||||
interceptor.beforeHandshake(request, response, wsHandler, attributes); |
||||
|
||||
assertEquals(2, attributes.size()); |
||||
assertEquals("bar", attributes.get("foo")); |
||||
assertEquals("baz", attributes.get("bar")); |
||||
} |
||||
|
||||
@Test |
||||
public void copySelectedAttributes() throws Exception { |
||||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>(); |
||||
WebSocketHandler wsHandler = Mockito.mock(WebSocketHandler.class); |
||||
|
||||
this.servletRequest.getSession().setAttribute("foo", "bar"); |
||||
this.servletRequest.getSession().setAttribute("bar", "baz"); |
||||
|
||||
Set<String> names = Collections.singleton("foo"); |
||||
HttpSessionHandshakeInterceptor interceptor = new HttpSessionHandshakeInterceptor(names); |
||||
interceptor.beforeHandshake(request, response, wsHandler, attributes); |
||||
|
||||
assertEquals(1, attributes.size()); |
||||
assertEquals("bar", attributes.get("foo")); |
||||
} |
||||
|
||||
@Test |
||||
public void doNotCauseSessionCreation() throws Exception { |
||||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>(); |
||||
WebSocketHandler wsHandler = Mockito.mock(WebSocketHandler.class); |
||||
|
||||
HttpSessionHandshakeInterceptor interceptor = new HttpSessionHandshakeInterceptor(); |
||||
interceptor.beforeHandshake(request, response, wsHandler, attributes); |
||||
|
||||
assertNull(this.servletRequest.getSession(false)); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue