From 29158aa79acb43d5c3d95689914e54c28426f8d7 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 16 May 2018 10:03:29 -0400 Subject: [PATCH] Expose timeToFirstMessage in Java/XML config Issue: SPR-16531 --- .../MessageBrokerBeanDefinitionParser.java | 3 ++ .../WebMvcStompEndpointRegistry.java | 5 ++- .../WebSocketTransportRegistration.java | 33 +++++++++++++- .../SubProtocolWebSocketHandler.java | 44 +++++++++++++++---- .../web/socket/config/spring-websocket.xsd | 16 +++++++ ...essageBrokerBeanDefinitionParserTests.java | 5 ++- ...essageBrokerConfigurationSupportTests.java | 20 ++++----- .../config/websocket-config-broker-simple.xml | 2 +- 8 files changed, 102 insertions(+), 26 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java index 65bd02fd04..72c439e3ac 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java @@ -308,6 +308,9 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser { if (transportElem.hasAttribute("send-buffer-size")) { handlerDef.getPropertyValues().add("sendBufferSizeLimit", transportElem.getAttribute("send-buffer-size")); } + if (transportElem.hasAttribute("time-to-first-message")) { + handlerDef.getPropertyValues().add("timeToFirstMessage", transportElem.getAttribute("time-to-first-message")); + } Element factoriesElement = DomUtils.getChildElementByTagName(transportElem, "decorator-factories"); if (factoriesElement != null) { ManagedList factories = extractBeanSubElements(factoriesElement, context); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java index ac162ef97b..f2faa62e3b 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -77,6 +77,9 @@ public class WebMvcStompEndpointRegistry implements StompEndpointRegistry { if (transportRegistration.getSendBufferSizeLimit() != null) { this.subProtocolWebSocketHandler.setSendBufferSizeLimit(transportRegistration.getSendBufferSizeLimit()); } + if (transportRegistration.getTimeToFirstMessage() != null) { + this.subProtocolWebSocketHandler.setTimeToFirstMessage(transportRegistration.getTimeToFirstMessage()); + } this.stompHandler = new StompSubProtocolHandler(); if (transportRegistration.getMessageSizeLimit() != null) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketTransportRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketTransportRegistration.java index d02ca122c7..c4114ac960 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketTransportRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketTransportRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -40,6 +40,9 @@ public class WebSocketTransportRegistration { @Nullable private Integer sendBufferSizeLimit; + @Nullable + private Integer timeToFirstMessage; + private final List decoratorFactories = new ArrayList<>(2); @@ -151,6 +154,34 @@ public class WebSocketTransportRegistration { return this.sendBufferSizeLimit; } + /** + * Set the maximum time allowed in milliseconds after the WebSocket + * connection is established and before the first sub-protocol message is + * received. + * + *

This handler is for WebSocket connections that use a sub-protocol. + * Therefore, we expect the client to send at least one sub-protocol message + * in the beginning, or else we assume the connection isn't doing well, e.g. + * proxy issue, slow network, and can be closed. + * + *

By default this is set to {@code 60,000} (1 minute). + * + * @param timeToFirstMessage the maximum time allowed in milliseconds + * @since 5.1 + */ + public WebSocketTransportRegistration setTimeToFirstMessage(int timeToFirstMessage) { + this.timeToFirstMessage = timeToFirstMessage; + return this; + } + + /** + * Protected accessor for internal use. + */ + @Nullable + protected Integer getTimeToFirstMessage() { + return this.timeToFirstMessage; + } + /** * Configure one or more factories to decorate the handler used to process * WebSocket messages. This may be useful in some advanced use cases, for diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java index e78f6874fb..936fc9c535 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/SubProtocolWebSocketHandler.java @@ -68,13 +68,8 @@ import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSe public class SubProtocolWebSocketHandler implements WebSocketHandler, SubProtocolCapable, MessageHandler, SmartLifecycle { - /** - * Sessions connected to this handler use a sub-protocol. Hence we expect to - * receive some client messages. If we don't receive any within a minute, the - * connection isn't doing well (proxy issue, slow network?) and can be closed. - * @see #checkSessions() - */ - private static final int TIME_TO_FIRST_MESSAGE = 60 * 1000; + /** The default value for {@link #setTimeToFirstMessage(int) timeToFirstMessage}. */ + private static final int DEFAULT_TIME_TO_FIRST_MESSAGE = 60 * 1000; private final Log logger = LogFactory.getLog(SubProtocolWebSocketHandler.class); @@ -98,6 +93,8 @@ public class SubProtocolWebSocketHandler private int sendBufferSizeLimit = 512 * 1024; + private int timeToFirstMessage = DEFAULT_TIME_TO_FIRST_MESSAGE; + private volatile long lastSessionCheckTime = System.currentTimeMillis(); private final ReentrantLock sessionCheckLock = new ReentrantLock(); @@ -224,6 +221,35 @@ public class SubProtocolWebSocketHandler return this.sendBufferSizeLimit; } + /** + * Set the maximum time allowed in milliseconds after the WebSocket + * connection is established and before the first sub-protocol message is + * received. + * + *

This handler is for WebSocket connections that use a sub-protocol. + * Therefore, we expect the client to send at least one sub-protocol message + * in the beginning, or else we assume the connection isn't doing well, e.g. + * proxy issue, slow network, and can be closed. + * + *

By default this is set to {@code 60,000} (1 minute). + * + * @param timeToFirstMessage the maximum time allowed in milliseconds + * @since 5.1 + * @see #checkSessions() + */ + public void setTimeToFirstMessage(int timeToFirstMessage) { + this.timeToFirstMessage = timeToFirstMessage; + } + + /** + * Return the maximum time allowed after the WebSocket connection is + * established and before the first sub-protocol message. + * @since 5.1 + */ + public int getTimeToFirstMessage() { + return this.timeToFirstMessage; + } + /** * Return a String describing internal state and counters. */ @@ -457,7 +483,7 @@ public class SubProtocolWebSocketHandler */ private void checkSessions() { long currentTime = System.currentTimeMillis(); - if (!isRunning() || (currentTime - this.lastSessionCheckTime < TIME_TO_FIRST_MESSAGE)) { + if (!isRunning() || (currentTime - this.lastSessionCheckTime < getTimeToFirstMessage())) { return; } @@ -468,7 +494,7 @@ public class SubProtocolWebSocketHandler continue; } long timeSinceCreated = currentTime - holder.getCreateTime(); - if (timeSinceCreated < TIME_TO_FIRST_MESSAGE) { + if (timeSinceCreated < getTimeToFirstMessage()) { continue; } WebSocketSession session = holder.getSession(); diff --git a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.xsd b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.xsd index 659a0bc5a7..fb2a21dcd7 100644 --- a/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.xsd +++ b/spring-websocket/src/main/resources/org/springframework/web/socket/config/spring-websocket.xsd @@ -683,6 +683,22 @@ @param sendBufferSizeLimit the maximum number of bytes to buffer when sending messages; if the value is less than or equal to 0 then buffering is effectively disabled. + ]]> + + + + + diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java index 47b70d7f5a..c6184419a4 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java @@ -94,8 +94,8 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** - * Test fixture for MessageBrokerBeanDefinitionParser. - * See test configuration files websocket-config-broker-*.xml. + * Test fixture for {@link MessageBrokerBeanDefinitionParser}. + * Also see test configuration files websocket-config-broker-*.xml. * * @author Brian Clozel * @author Artem Bilan @@ -147,6 +147,7 @@ public class MessageBrokerBeanDefinitionParserTests { assertEquals(Arrays.asList("v10.stomp", "v11.stomp", "v12.stomp"), subProtocolWsHandler.getSubProtocols()); assertEquals(25 * 1000, subProtocolWsHandler.getSendTimeLimit()); assertEquals(1024 * 1024, subProtocolWsHandler.getSendBufferSizeLimit()); + assertEquals(30 * 1000, subProtocolWsHandler.getTimeToFirstMessage()); Map handlerMap = subProtocolWsHandler.getProtocolHandlerMap(); StompSubProtocolHandler stompHandler = (StompSubProtocolHandler) handlerMap.get("v12.stomp"); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java index aa67273e08..f960bd765b 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2018 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. @@ -48,7 +48,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; -import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.config.WebSocketMessageBrokerStats; @@ -61,16 +60,11 @@ import org.springframework.web.socket.messaging.SubProtocolHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** - * Test fixture for - * {@link org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurationSupport}. + * Test fixture for {@link WebSocketMessageBrokerConfigurationSupport}. * * @author Rossen Stoyanchev */ @@ -100,8 +94,8 @@ public class WebSocketMessageBrokerConfigurationSupportTests { session.setOpen(true); webSocketHandler.afterConnectionEstablished(session); - TextMessage textMessage = StompTextMessageBuilder.create(StompCommand.SEND).headers("destination:/foo").build(); - webSocketHandler.handleMessage(session, textMessage); + webSocketHandler.handleMessage(session, + StompTextMessageBuilder.create(StompCommand.SEND).headers("destination:/foo").build()); Message message = channel.messages.get(0); StompHeaderAccessor accessor = StompHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); @@ -145,6 +139,7 @@ public class WebSocketMessageBrokerConfigurationSupportTests { assertEquals(1024 * 1024, subWsHandler.getSendBufferSizeLimit()); assertEquals(25 * 1000, subWsHandler.getSendTimeLimit()); + assertEquals(30 * 1000, subWsHandler.getTimeToFirstMessage()); Map handlerMap = subWsHandler.getProtocolHandlerMap(); StompSubProtocolHandler protocolHandler = (StompSubProtocolHandler) handlerMap.get("v12.stomp"); @@ -240,6 +235,7 @@ public class WebSocketMessageBrokerConfigurationSupportTests { registration.setMessageSizeLimit(128 * 1024); registration.setSendTimeLimit(25 * 1000); registration.setSendBufferSizeLimit(1024 * 1024); + registration.setTimeToFirstMessage(30 * 1000); } @Override diff --git a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml index 7b3cd93f2a..a5c8bfa2d6 100644 --- a/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml +++ b/spring-websocket/src/test/resources/org/springframework/web/socket/config/websocket-config-broker-simple.xml @@ -10,7 +10,7 @@ path-helper="urlPathHelper"> - +