From b57018b78bb4854a674c4ab4b46917a6de36a5bc Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 21 Jul 2015 23:40:38 -0400 Subject: [PATCH] INT-3611: Support WebSocketHandlerDecoratorFactory JIRA: https://jira.spring.io/browse/INT-3611 --- .../websocket/ServerWebSocketContainer.java | 40 +++++++++- .../ServerWebSocketContainerParser.java | 10 ++- .../spring-integration-websocket-4.2.xsd | 11 +++ .../client/StompIntegrationTests.java | 2 - .../config/WebSocketParserTests-context.xml | 34 ++++---- .../config/WebSocketParserTests.java | 26 ++++++- .../server/WebSocketServerTests.java | 78 ++++++++++++++++++- src/reference/asciidoc/web-sockets.adoc | 62 +++++++++------ src/reference/asciidoc/whats-new.adoc | 6 ++ 9 files changed, 220 insertions(+), 49 deletions(-) diff --git a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java index 464e01b80d..6d29ac4a2a 100644 --- a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java +++ b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/ServerWebSocketContainer.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.websocket; import java.util.Arrays; @@ -20,10 +21,12 @@ import java.util.Arrays; import org.springframework.scheduling.TaskScheduler; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.SockJsServiceRegistration; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistration; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; @@ -47,9 +50,11 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer impl private final String[] paths; - private volatile HandshakeHandler handshakeHandler; + private HandshakeHandler handshakeHandler; - private volatile HandshakeInterceptor[] interceptors; + private HandshakeInterceptor[] interceptors; + + private WebSocketHandlerDecoratorFactory[] decoratorFactories; private SockJsServiceOptions sockJsServiceOptions; @@ -62,12 +67,30 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer impl return this; } - public ServerWebSocketContainer setInterceptors(HandshakeInterceptor[] interceptors) { + public ServerWebSocketContainer setInterceptors(HandshakeInterceptor... interceptors) { + Assert.notNull(interceptors, "'interceptors' must not be null"); + Assert.noNullElements(interceptors, "'interceptors' must not contain null elements"); this.interceptors = Arrays.copyOf(interceptors, interceptors.length); return this; } + /** + * 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. + * @param factories the WebSocketHandlerDecoratorFactory array to use + * @return the current ServerWebSocketContainer + * @since 4.2 + */ + public ServerWebSocketContainer setDecoratorFactories(WebSocketHandlerDecoratorFactory... factories) { + Assert.notNull(factories, "'factories' must not be null"); + Assert.noNullElements(factories, "'factories' must not contain null elements"); + this.decoratorFactories = Arrays.copyOf(factories, factories.length); + return this; + } + public ServerWebSocketContainer withSockJs(SockJsServiceOptions... sockJsServiceOptions) { if (ObjectUtils.isEmpty(sockJsServiceOptions)) { this.sockJsServiceOptions = new SockJsServiceOptions(); @@ -85,9 +108,18 @@ public class ServerWebSocketContainer extends IntegrationWebSocketContainer impl @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - WebSocketHandlerRegistration registration = registry.addHandler(this.webSocketHandler, this.paths) + WebSocketHandler webSocketHandler = this.webSocketHandler; + + if (this.decoratorFactories != null) { + for (WebSocketHandlerDecoratorFactory factory : this.decoratorFactories) { + webSocketHandler = factory.decorate(webSocketHandler); + } + } + + WebSocketHandlerRegistration registration = registry.addHandler(webSocketHandler, this.paths) .setHandshakeHandler(this.handshakeHandler) .addInterceptors(this.interceptors); + if (this.sockJsServiceOptions != null) { SockJsServiceRegistration sockJsServiceRegistration = registration.withSockJS(); if (this.sockJsServiceOptions.webSocketEnabled != null) { diff --git a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/ServerWebSocketContainerParser.java b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/ServerWebSocketContainerParser.java index 5363ad9637..481f37b0b6 100644 --- a/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/ServerWebSocketContainerParser.java +++ b/spring-integration-websocket/src/main/java/org/springframework/integration/websocket/config/ServerWebSocketContainerParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -62,6 +62,14 @@ public class ServerWebSocketContainerParser extends AbstractSingleBeanDefinition } builder.addPropertyValue("interceptors", handshakeInterceptorList); + String decoratorFactories = element.getAttribute("decorator-factories"); + List decoratorFactoryList = new ManagedList(); + ids = StringUtils.commaDelimitedListToStringArray(decoratorFactories); + for (String id : ids) { + decoratorFactoryList.add(new RuntimeBeanReference(id)); + } + builder.addPropertyValue("decoratorFactories", decoratorFactoryList); + Element sockjs = DomUtils.getChildElementByTagName(element, "sockjs"); if (sockjs != null) { diff --git a/spring-integration-websocket/src/main/resources/org/springframework/integration/websocket/config/spring-integration-websocket-4.2.xsd b/spring-integration-websocket/src/main/resources/org/springframework/integration/websocket/config/spring-integration-websocket-4.2.xsd index 19f7feb7b3..646b11e982 100644 --- a/spring-integration-websocket/src/main/resources/org/springframework/integration/websocket/config/spring-integration-websocket-4.2.xsd +++ b/spring-integration-websocket/src/main/resources/org/springframework/integration/websocket/config/spring-integration-websocket-4.2.xsd @@ -280,6 +280,17 @@ + + + + List of 'org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory' bean references. + 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. + + + diff --git a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/StompIntegrationTests.java b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/StompIntegrationTests.java index d5ec8eb533..42174d94ab 100644 --- a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/StompIntegrationTests.java +++ b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/client/StompIntegrationTests.java @@ -105,8 +105,6 @@ import org.springframework.web.socket.server.support.DefaultHandshakeHandler; @DirtiesContext public class StompIntegrationTests { - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - @Value("#{server.serverContext}") private ApplicationContext serverContext; diff --git a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests-context.xml b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests-context.xml index d6438fe662..fdf9f48a35 100644 --- a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests-context.xml +++ b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests-context.xml @@ -18,16 +18,17 @@ send-buffer-size-limit="100000" send-time-limit="100" handshake-handler="handshakeHandler" - handshake-interceptors="handshakeInterceptor"> + handshake-interceptors="handshakeInterceptor" + decorator-factories="decoratorFactory"> + disconnect-delay="4000" + heartbeat-time="30000" + message-cache-size="10000" + session-cookie-needed="false" + stream-bytes-limit="2000" + websocket-enabled="false" + scheduler="taskScheduler" + message-codec="sockJsMessageCodec"/> @@ -42,13 +43,16 @@ + + + use-broker="true"/> @@ -93,11 +97,11 @@ + default-protocol-handler="stompSubProtocolHandler" + protocol-handlers="passThruSubProtocolHandler" + message-converters="simpleMessageConverter,mapMessageConverter" + merge-with-default-converters="true" + channel="clientOutboundChannel"/> diff --git a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests.java b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests.java index abb0d98931..62d6d86f67 100644 --- a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests.java +++ b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/config/WebSocketParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -19,6 +19,7 @@ package org.springframework.integration.websocket.config; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; @@ -54,8 +55,10 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHttpHeaders; import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory; import org.springframework.web.socket.messaging.StompSubProtocolHandler; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.HandshakeInterceptor; @@ -138,7 +141,11 @@ public class WebSocketParserTests { @Qualifier("customOutboundAdapter.handler") private WebSocketOutboundMessageHandler customOutboundAdapter; + @Autowired + private WebSocketHandlerDecoratorFactory decoratorFactory; + @Test + @SuppressWarnings("unckecked") public void testDefaultInboundChannelAdapterAndServerContainer() { Map urlMap = TestUtils.getPropertyValue(this.handlerMapping, "urlMap", Map.class); assertEquals(1, urlMap.size()); @@ -152,11 +159,19 @@ public class WebSocketParserTests { TestUtils.getPropertyValue(this.serverWebSocketContainer, "handshakeHandler")); HandshakeInterceptor[] interceptors = TestUtils.getPropertyValue(this.serverWebSocketContainer, "interceptors", HandshakeInterceptor[].class); + assertNotNull(interceptors); assertEquals(1, interceptors.length); assertSame(this.handshakeInterceptor, interceptors[0]); assertEquals(100, TestUtils.getPropertyValue(this.serverWebSocketContainer, "sendTimeLimit")); assertEquals(100000, TestUtils.getPropertyValue(this.serverWebSocketContainer, "sendBufferSizeLimit")); + WebSocketHandlerDecoratorFactory[] decoratorFactories = + TestUtils.getPropertyValue(this.serverWebSocketContainer, "decoratorFactories", + WebSocketHandlerDecoratorFactory[].class); + assertNotNull(decoratorFactories); + assertEquals(1, decoratorFactories.length); + assertSame(this.decoratorFactory, decoratorFactories[0]); + TransportHandlingSockJsService sockJsService = TestUtils.getPropertyValue(mappedHandler, "sockJsService", TransportHandlingSockJsService.class); assertSame(this.taskScheduler, sockJsService.getTaskScheduler()); @@ -298,4 +313,13 @@ public class WebSocketParserTests { assertTrue(TestUtils.getPropertyValue(this.customOutboundAdapter, "client", Boolean.class)); } + private static class TestWebSocketHandlerDecoratorFactory implements WebSocketHandlerDecoratorFactory { + + @Override + public WebSocketHandler decorate(WebSocketHandler handler) { + return handler; + } + + } + } diff --git a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/server/WebSocketServerTests.java b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/server/WebSocketServerTests.java index b6d1b91f79..5cdcaddc6f 100644 --- a/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/server/WebSocketServerTests.java +++ b/spring-integration-websocket/src/test/java/org/springframework/integration/websocket/server/WebSocketServerTests.java @@ -37,6 +37,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; +import org.springframework.context.PayloadApplicationEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -46,6 +51,7 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.core.MessageProducer; +import org.springframework.integration.event.inbound.ApplicationEventListeningMessageProducer; import org.springframework.integration.transformer.ExpressionEvaluatingTransformer; import org.springframework.integration.websocket.ClientWebSocketContainer; import org.springframework.integration.websocket.IntegrationWebSocketContainer; @@ -68,11 +74,16 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.MultiValueMap; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.client.WebSocketClient; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.handler.WebSocketHandlerDecorator; +import org.springframework.web.socket.handler.WebSocketHandlerDecoratorFactory; import org.springframework.web.socket.messaging.StompSubProtocolHandler; import org.springframework.web.socket.messaging.SubProtocolHandler; import org.springframework.web.socket.sockjs.client.SockJsClient; @@ -101,6 +112,9 @@ public class WebSocketServerTests { @Value("#{server.serverContext.getBean('simpleBrokerMessageHandler')}") private SimpleBrokerMessageHandler brokerHandler; + @Value("#{server.serverContext.getBean('webSocketEvents')}") + private PollableChannel webSocketEvents; + @Test public void testWebSocketOutboundMessageHandler() throws Exception { StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.SUBSCRIBE); @@ -133,6 +147,10 @@ public class WebSocketServerTests { List subscription = subscriptions.values().iterator().next(); assertEquals(1, subscription.size()); assertEquals("subs1", subscription.get(0)); + + Message event = this.webSocketEvents.receive(10000); + assertNotNull(event); + assertThat(event.getPayload(), instanceOf(WebSocketSession.class)); } @Test @@ -224,9 +242,16 @@ public class WebSocketServerTests { .enableSimpleBroker("/queue/", "/topic/"); } + @Bean + public WebSocketHandlerDecoratorFactory testWebSocketHandlerDecoratorFactory() { + return new TestWebSocketHandlerDecoratorFactory(); + } + @Bean public ServerWebSocketContainer serverWebSocketContainer() { - return new ServerWebSocketContainer("/ws").withSockJs(); + return new ServerWebSocketContainer("/ws") + .setDecoratorFactories(testWebSocketHandlerDecoratorFactory()) + .withSockJs(); } @Bean @@ -267,6 +292,57 @@ public class WebSocketServerTests { new SubProtocolHandlerRegistry(stompSubProtocolHandler())); } + @Bean + public PollableChannel webSocketEvents() { + return new QueueChannel(); + } + + @Bean + @SuppressWarnings("unchecked") + public ApplicationListener webSocketEventListener() { + ApplicationEventListeningMessageProducer producer = new ApplicationEventListeningMessageProducer(); + producer.setEventTypes(PayloadApplicationEvent.class); + producer.setExpressionPayload(new SpelExpressionParser().parseExpression("payload")); + producer.setOutputChannel(webSocketEvents()); + return producer; + } + + } + + private static class TestWebSocketHandlerDecoratorFactory + implements WebSocketHandlerDecoratorFactory, ApplicationEventPublisherAware { + + private ApplicationEventPublisher applicationEventPublisher; + + @Override + public WebSocketHandler decorate(WebSocketHandler handler) { + return new TestWebSocketHandler(handler); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + private class TestWebSocketHandler extends WebSocketHandlerDecorator { + + public TestWebSocketHandler(WebSocketHandler delegate) { + super(delegate); + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { + super.handleMessage(session, message); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + super.afterConnectionEstablished(session); + applicationEventPublisher.publishEvent(session); + } + + } + } } diff --git a/src/reference/asciidoc/web-sockets.adoc b/src/reference/asciidoc/web-sockets.adoc index e9a61b54cc..549f3207e7 100644 --- a/src/reference/asciidoc/web-sockets.adoc +++ b/src/reference/asciidoc/web-sockets.adoc @@ -9,7 +9,7 @@ It is based on architecture, infrastructure and API from the Spring Framework's Therefore, many of Spring WebSocket's components (e.g. `SubProtocolHandler` or `WebSocketClient`) and configuration options (e.g. `@EnableWebSocketMessageBroker`) can be reused within Spring Integration. -For more information, please, refer to thehttp://docs.spring.io/spring/docs/current/spring-framework-reference/html/#websocket[Spring Framework WebSocket Support] chapter in the Spring Framework reference manual. +For more information, please, refer to the http://docs.spring.io/spring/docs/current/spring-framework-reference/html/#websocket[Spring Framework WebSocket Support] chapter in the Spring Framework reference manual. NOTE: Since the Spring Framework WebSocket infrastructure is based on the _Spring Messaging_ foundation and provides a basic Messaging framework based on the same `MessageChannel` s, `MessageHandler` s that Spring Integration uses, and some POJO-method annotation mappings, Spring Integration can be directly involved in a WebSocket flow, even without WebSocket adapters. For this purpose you can simply configure a Spring Integration `@MessagingGateway` with appropriate annotations: @@ -195,19 +195,20 @@ See `SmartLifeCycle`. path="" <2> handshake-handler="" <3> handshake-interceptors="" <4> - send-time-limit="" <5> - send-buffer-size-limit=""> <6> + decorator-factories="" <5> + send-time-limit="" <6> + send-buffer-size-limit=""> <7> - stream-bytes-limit="" <8> - session-cookie-needed="" <9> - heartbeat-time="" <10> - disconnect-delay="" <11> - message-cache-size="" <12> - websocket-enabled="" <13> - scheduler="" <14> - message-codec="" <15> - transport-handlers="" /> <16> + client-library-url="" <8> + stream-bytes-limit="" <9> + session-cookie-needed="" <10> + heartbeat-time="" <11> + disconnect-delay="" <12> + message-cache-size="" <13> + websocket-enabled="" <14> + scheduler="" <15> + message-codec="" <16> + transport-handlers="" /> <17> ---- @@ -225,13 +226,20 @@ Default to `DefaultHandshakeHandler`. <4> List of `HandshakeInterceptor` bean references. -<5> See the same option on the ``. - +<5> Configure one or more factories (`WebSocketHandlerDecoratorFactory`) 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. +See http://docs.spring.io/spring-session/docs/current/reference/html5/#websocket[Spring Session Project] +for more information. <6> See the same option on the ``. -<7> Transports with no native cross-domain communication (e.g. +<7> See the same option on the ``. + + +<8> Transports with no native cross-domain communication (e.g. "eventsource", "htmlfile") must get a simple page from the "foreign" domain in an invisible iframe so that code in the iframe can run from a domain local to the SockJS server. Since the iframe needs to load the SockJS javascript client library, this property allows specifying where to load it from. By default this is set to point to `https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js`. @@ -241,43 +249,47 @@ For example assuming a SockJS endpoint mapped to "/sockjs", and resulting iframe In case of a prefix-based Servlet mapping one more traversal may be needed. -<8> Minimum number of bytes that can be send over a single HTTP streaming request before it will be closed. +<9> Minimum number of bytes that can be send over a single HTTP streaming request before it will be closed. Defaults to `128K` (i.e. 128*1024 bytes). -<9> The "cookie_needed" value in the response from the SockJs `"/info"` endpoint. +<10> The "cookie_needed" value in the response from the SockJs `"/info"` endpoint. This property indicates whether the use of a JSESSIONID cookie is required for the application to function correctly, e.g. for load balancing or in Java Servlet containers for the use of an HTTP session. -<10> The amount of time in milliseconds when the server has not sent any messages and after which the server should send a heartbeat frame to the client in order to keep the connection from breaking. +<11> The amount of time in milliseconds when the server has not sent any messages and after which the server should +send a heartbeat frame to the client in order to keep the connection from breaking. The default value is `25,000` (25 seconds). -<11> The amount of time in milliseconds before a client is considered disconnected after not having a receiving connection, i.e. +<12> The amount of time in milliseconds before a client is considered disconnected after not having a receiving +connection, i.e. an active connection over which the server can send data to the client. The default value is `5000`. -<12> The number of server-to-client messages that a session can cache while waiting for the next HTTP polling request from the client. +<13> The number of server-to-client messages that a session can cache while waiting for the next HTTP polling request + from the client. The default size is `100`. -<13> Some load balancers don't support websockets. +<14> Some load balancers don't support websockets. Set this option to `false` to disable the WebSocket transport on the server side. The default value is `true`. -<14> The `TaskScheduler` bean reference; a new `ThreadPoolTaskScheduler` instance will be created if no value is provided. +<15> The `TaskScheduler` bean reference; a new `ThreadPoolTaskScheduler` instance will be created if no value is +provided. This scheduler instance will be used for scheduling heart-beat messages. -<15> The `SockJsMessageCodec` bean reference to use for encoding and decoding SockJS messages. +<16> The `SockJsMessageCodec` bean reference to use for encoding and decoding SockJS messages. By default `Jackson2SockJsMessageCodec` is used requiring the Jackson library to be present on the classpath. -<16> List of `TransportHandler` bean references. +<17> List of `TransportHandler` bean references. ** diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 070671b24f..05e86cf00a 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -260,3 +260,9 @@ When use FTP/SFTP outbound gateways to operate on multiple files (`mget`, `mput` occur after part of the request is completed. If such a condition occurs, a `PartialSuccessException` is thrown containing the partial results. See <> and <> for more information. + +==== Websocket Changes + +`WebSocketHandlerDecoratorFactory` support has been added to the `ServerWebSocketContainer` +to allow chained customization for the internal `WebSocketHandler`. +See <> for more information.