Allow plugging in a WebSocketHandlerDecorator

The WebSocketMessageBroker config now allows wrapping the
SubProtocolWebSocketHandler to enable advanced use cases that may
require access to the underlying WebSocketSession.

Issue: SPR-12314
This commit is contained in:
Rossen Stoyanchev
2014-10-13 09:35:51 -04:00
parent f0323be786
commit 97596fb9f6
8 changed files with 283 additions and 35 deletions

View File

@@ -21,6 +21,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
import org.w3c.dom.Element;
import org.springframework.beans.MutablePropertyValues;
@@ -89,7 +92,9 @@ import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
*/
class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
private static final String SOCKJS_SCHEDULER_BEAN_NAME = "messageBrokerSockJsScheduler";
public static final String WEB_SOCKET_HANDLER_BEAN_NAME = "subProtocolWebSocketHandler";
public static final String SOCKJS_SCHEDULER_BEAN_NAME = "messageBrokerSockJsScheduler";
private static final int DEFAULT_MAPPING_ORDER = 1;
@@ -156,7 +161,7 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
scopeConfigurer.getPropertyValues().add("scopes", scopeMap);
registerBeanDefByName("webSocketScopeConfigurer", scopeConfigurer, context, source);
registerWebSocketMessageBrokerStats(subProtoHandler, broker, inChannel, outChannel, context, source);
registerWebSocketMessageBrokerStats(broker, inChannel, outChannel, context, source);
context.popAndRegisterContainingComponent();
return null;
@@ -228,8 +233,10 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
cavs.addIndexedArgumentValue(0, inChannel);
cavs.addIndexedArgumentValue(1, outChannel);
RootBeanDefinition beanDef = new RootBeanDefinition(SubProtocolWebSocketHandler.class, cavs, null);
beanDef.getPropertyValues().addPropertyValue("protocolHandlers", stompHandlerDef);
RootBeanDefinition handlerDef = new RootBeanDefinition(SubProtocolWebSocketHandler.class, cavs, null);
handlerDef.getPropertyValues().addPropertyValue("protocolHandlers", stompHandlerDef);
registerBeanDefByName(WEB_SOCKET_HANDLER_BEAN_NAME, handlerDef, context, source);
RuntimeBeanReference result = new RuntimeBeanReference(WEB_SOCKET_HANDLER_BEAN_NAME);
Element transportElem = DomUtils.getChildElementByTagName(element, "transport");
if (transportElem != null) {
@@ -237,13 +244,21 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
stompHandlerDef.getPropertyValues().add("messageSizeLimit", transportElem.getAttribute("message-size"));
}
if (transportElem.hasAttribute("send-timeout")) {
beanDef.getPropertyValues().add("sendTimeLimit", transportElem.getAttribute("send-timeout"));
handlerDef.getPropertyValues().add("sendTimeLimit", transportElem.getAttribute("send-timeout"));
}
if (transportElem.hasAttribute("send-buffer-size")) {
beanDef.getPropertyValues().add("sendBufferSizeLimit", transportElem.getAttribute("send-buffer-size"));
handlerDef.getPropertyValues().add("sendBufferSizeLimit", transportElem.getAttribute("send-buffer-size"));
}
Element factoriesElement = DomUtils.getChildElementByTagName(transportElem, "decorator-factories");
if (factoriesElement != null) {
ManagedList<Object> factories = extractBeanSubElements(factoriesElement, context);
RootBeanDefinition factoryBean = new RootBeanDefinition(DecoratingFactoryBean.class);
factoryBean.getConstructorArgumentValues().addIndexedArgumentValue(0, handlerDef);
factoryBean.getConstructorArgumentValues().addIndexedArgumentValue(1, factories);
result = new RuntimeBeanReference(registerBeanDef(factoryBean, context, source));
}
}
return new RuntimeBeanReference(registerBeanDef(beanDef, context, source));
return result;
}
private RuntimeBeanReference registerRequestHandler(Element element, RuntimeBeanReference subProtoHandler,
@@ -448,14 +463,15 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
return new RuntimeBeanReference(registerBeanDef(beanDef, context, source));
}
private void registerWebSocketMessageBrokerStats(RuntimeBeanReference subProtoHandler,
RootBeanDefinition broker, RuntimeBeanReference inChannel, RuntimeBeanReference outChannel,
ParserContext context, Object source) {
private void registerWebSocketMessageBrokerStats(RootBeanDefinition broker, RuntimeBeanReference inChannel,
RuntimeBeanReference outChannel, ParserContext context, Object source) {
RootBeanDefinition beanDef = new RootBeanDefinition(WebSocketMessageBrokerStats.class);
beanDef.getPropertyValues().add("subProtocolWebSocketHandler", subProtoHandler);
if (StompBrokerRelayMessageHandler.class.equals(broker.getBeanClass())) {
RuntimeBeanReference webSocketHandler = new RuntimeBeanReference(WEB_SOCKET_HANDLER_BEAN_NAME);
beanDef.getPropertyValues().add("subProtocolWebSocketHandler", webSocketHandler);
if (StompBrokerRelayMessageHandler.class.equals(broker.getBeanClass())) {
beanDef.getPropertyValues().add("stompBrokerRelay", broker);
}
String name = inChannel.getBeanName() + "Executor";
@@ -486,4 +502,37 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
context.registerComponent(new BeanComponentDefinition(beanDef, name));
}
private static class DecoratingFactoryBean implements FactoryBean<WebSocketHandler> {
private final WebSocketHandler handler;
private final List<WebSocketHandlerDecoratorFactory> factories;
private DecoratingFactoryBean(WebSocketHandler handler, List<WebSocketHandlerDecoratorFactory> factories) {
this.handler = handler;
this.factories = factories;
}
@Override
public WebSocketHandler getObject() throws Exception {
WebSocketHandler result = this.handler;
for (WebSocketHandlerDecoratorFactory factory : this.factories) {
result = factory.decorate(result);
}
return result;
}
@Override
public Class<?> getObjectType() {
return WebSocketHandler.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
}

View File

@@ -23,9 +23,12 @@ import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
import org.springframework.messaging.simp.stomp.StompBrokerRelayMessageHandler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.WebSocketMessageBrokerStats;
import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
/**
@@ -47,7 +50,9 @@ public abstract class WebSocketMessageBrokerConfigurationSupport extends Abstrac
@Bean
public HandlerMapping stompWebSocketHandlerMapping() {
WebMvcStompEndpointRegistry registry = new WebMvcStompEndpointRegistry(subProtocolWebSocketHandler(),
WebSocketHandler handler = subProtocolWebSocketHandler();
handler = decorateWebSocketHandler(handler);
WebMvcStompEndpointRegistry registry = new WebMvcStompEndpointRegistry(handler,
getTransportRegistration(), userSessionRegistry(), messageBrokerSockJsTaskScheduler());
registry.setApplicationContext(getApplicationContext());
registerStompEndpoints(registry);
@@ -59,6 +64,13 @@ public abstract class WebSocketMessageBrokerConfigurationSupport extends Abstrac
return new SubProtocolWebSocketHandler(clientInboundChannel(), clientOutboundChannel());
}
protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) {
for (WebSocketHandlerDecoratorFactory factory : getTransportRegistration().getDecoratorFactories()) {
handler = factory.decorate(handler);
}
return handler;
}
protected final WebSocketTransportRegistration getTransportRegistration() {
if (this.transportRegistration == null) {
this.transportRegistration = new WebSocketTransportRegistration();

View File

@@ -16,6 +16,12 @@
package org.springframework.web.socket.config.annotation;
import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Configure the processing of messages received from and sent to WebSocket clients.
*
@@ -30,6 +36,9 @@ public class WebSocketTransportRegistration {
private Integer sendBufferSizeLimit;
private final List<WebSocketHandlerDecoratorFactory> decoratorFactories =
new ArrayList<WebSocketHandlerDecoratorFactory>(2);
/**
* Configure the maximum size for an incoming sub-protocol message.
@@ -147,4 +156,35 @@ public class WebSocketTransportRegistration {
protected Integer getSendBufferSizeLimit() {
return this.sendBufferSizeLimit;
}
/**
* Configure one or more factories to decorate the handler used to process
* WebSocket messages. This may be useful in some advanced use cases, for
* example to allow Spring Security to forcibly close the WebSocket session
* when the corresponding HTTP session expires.
* @since 4.1.2
*/
public WebSocketTransportRegistration setDecoratorFactories(WebSocketHandlerDecoratorFactory... factories) {
if (factories != null) {
this.decoratorFactories.addAll(Arrays.asList(factories));
}
return this;
}
/**
* Add a factory that to decorate the handler used to process WebSocket
* messages. This may be useful for some advanced use cases, for example
* to allow Spring Security to forcibly close the WebSocket session when
* the corresponding HTTP session expires.
* @since 4.1.2
*/
public WebSocketTransportRegistration addDecoratorFactory(WebSocketHandlerDecoratorFactory factory) {
this.decoratorFactories.add(factory);
return this;
}
protected List<WebSocketHandlerDecoratorFactory> getDecoratorFactories() {
return this.decoratorFactories;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.
* 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.handler;
import org.springframework.web.socket.WebSocketHandler;
/**
* A factory for applying decorators to a WebSocketHandler.
*
* <p>Decoration should be done through sub-classing
* {@link org.springframework.web.socket.handler.WebSocketHandlerDecorator
* WebSocketHandlerDecorator} to allow any code to traverse decorators and/or
* unwrap the original handler when necessary .
*
* @author Rossen Stoyanchev
* @since 4.1.2
*/
public interface WebSocketHandlerDecoratorFactory {
/**
* Decorate the given WebSocketHandler.
* @param handler the handler to be decorated.
* @return the same handler or the handler wrapped with a sub-class of
* {@code WebSocketHandlerDecorator}.
*/
WebSocketHandler decorate(WebSocketHandler handler);
}

View File

@@ -497,6 +497,38 @@
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="decorator-factories" maxOccurs="1" minOccurs="0">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation source="org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory"><![CDATA[
Configure one or more factories to decorate the handler used to process WebSocket
messages. This may be useful for some advanced use cases, for example to allow
Spring Security to forcibly close the WebSocket session when the corresponding
HTTP session expires.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="beans:bean">
<xsd:annotation>
<xsd:documentation source="org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory"><![CDATA[
A WebSocketHandlerDecoratorFactory bean definition.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="beans:ref">
<xsd:annotation>
<xsd:documentation source="org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory"><![CDATA[
A reference to a WebSocketHandlerDecoratorFactory bean.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="message-size" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[