diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java index 6d5edef28d..3734d2e404 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java @@ -16,13 +16,15 @@ package org.springframework.web.socket.server.standard; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Decoder; @@ -58,210 +60,269 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.web.socket.server.HandshakeFailureException; + /** - * A WebSocket {@code RequestUpgradeStrategy} for use with WildFly and its - * underlying Undertow web server. Also compatible with embedded Undertow usage. + * RequestUpgradeStrategy for WildFly and its underlying Undertow web + * server. Also compatible with embedded Undertow usage. * - *

Compatible with Undertow 1.0 to 1.3 - as included in WildFly 8.x, 9 and 10. + *

Compatible with Undertow 1.0 to 1.3 and also 1.3.5+ - as included in + * WildFly 8.x, 9 and 10. * * @author Rossen Stoyanchev - * @author Brian Clozel - * @author Juergen Hoeller * @since 4.0.1 */ public class UndertowRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { - private static final Constructor exchangeConstructor; + private static final boolean HAS_DO_UPGRADE = ClassUtils.hasMethod(ServerWebSocketContainer.class, + "doUpgrade", (Class[]) null); - private static final boolean exchangeConstructorWithPeerConnections; + private static final FallbackStrategy FALLBACK_STRATEGY = (HAS_DO_UPGRADE ? null : new FallbackStrategy()); - private static final Constructor endpointConstructor; - - private static final boolean endpointConstructorWithEndpointFactory; - - private static final Method getBufferPoolMethod; - - private static final Method createChannelMethod; - - static { - try { - Class exchangeType = ServletWebSocketHttpExchange.class; - Class[] exchangeParamTypes = - new Class[] {HttpServletRequest.class, HttpServletResponse.class, Set.class}; - Constructor exchangeCtor = - ClassUtils.getConstructorIfAvailable(exchangeType, exchangeParamTypes); - if (exchangeCtor != null) { - // Undertow 1.1+ - exchangeConstructor = exchangeCtor; - exchangeConstructorWithPeerConnections = true; - } - else { - // Undertow 1.0 - exchangeParamTypes = new Class[] {HttpServletRequest.class, HttpServletResponse.class}; - exchangeConstructor = exchangeType.getConstructor(exchangeParamTypes); - exchangeConstructorWithPeerConnections = false; - } - - Class endpointType = ConfiguredServerEndpoint.class; - Class[] endpointParamTypes = new Class[] {ServerEndpointConfig.class, InstanceFactory.class, - PathTemplate.class, EncodingFactory.class, AnnotatedEndpointFactory.class}; - Constructor endpointCtor = - ClassUtils.getConstructorIfAvailable(endpointType, endpointParamTypes); - if (endpointCtor != null) { - // Undertow 1.1+ - endpointConstructor = endpointCtor; - endpointConstructorWithEndpointFactory = true; - } - else { - // Undertow 1.0 - endpointParamTypes = new Class[] {ServerEndpointConfig.class, InstanceFactory.class, - PathTemplate.class, EncodingFactory.class}; - endpointConstructor = endpointType.getConstructor(endpointParamTypes); - endpointConstructorWithEndpointFactory = false; - } - - // Adapting between different Pool API types in Undertow 1.0-1.2 vs 1.3 - getBufferPoolMethod = WebSocketHttpExchange.class.getMethod("getBufferPool"); - createChannelMethod = ReflectionUtils.findMethod(Handshake.class, "createChannel", (Class[]) null); - } - catch (Throwable ex) { - throw new IllegalStateException("Incompatible Undertow API version", ex); - } - } - - private static final String[] supportedVersions = new String[] { + private static final String[] VERSIONS = new String[] { WebSocketVersion.V13.toHttpHeaderValue(), WebSocketVersion.V08.toHttpHeaderValue(), WebSocketVersion.V07.toHttpHeaderValue() }; - private final Set peerConnections; - - - public UndertowRequestUpgradeStrategy() { - if (exchangeConstructorWithPeerConnections) { - this.peerConnections = Collections.newSetFromMap(new ConcurrentHashMap()); - } - else { - this.peerConnections = null; - } - } - - @Override public String[] getSupportedVersions() { - return supportedVersions; + return VERSIONS; } @Override protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, List selectedExtensions, final Endpoint endpoint) + String selectedProtocol, List selectedExtensions, Endpoint endpoint) throws HandshakeFailureException { - HttpServletRequest servletRequest = getHttpServletRequest(request); - HttpServletResponse servletResponse = getHttpServletResponse(response); + if (HAS_DO_UPGRADE) { + HttpServletRequest servletRequest = getHttpServletRequest(request); + HttpServletResponse servletResponse = getHttpServletResponse(response); - final ServletWebSocketHttpExchange exchange = createHttpExchange(servletRequest, servletResponse); - exchange.putAttachment(HandshakeUtil.PATH_PARAMS, Collections.emptyMap()); + StringBuffer requestUrl = servletRequest.getRequestURL(); + String path = servletRequest.getRequestURI(); // shouldn't matter + Map pathParams = Collections.emptyMap(); - ServerWebSocketContainer wsContainer = (ServerWebSocketContainer) getContainer(servletRequest); - final EndpointSessionHandler endpointSessionHandler = new EndpointSessionHandler(wsContainer); + ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint); + endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol)); + endpointConfig.setExtensions(selectedExtensions); - final ConfiguredServerEndpoint configuredServerEndpoint = createConfiguredServerEndpoint( - selectedProtocol, selectedExtensions, endpoint, servletRequest); - - final Handshake handshake = getHandshakeToUse(exchange, configuredServerEndpoint); - - exchange.upgradeChannel(new HttpUpgradeListener() { - @Override - public void handleUpgrade(StreamConnection connection, HttpServerExchange serverExchange) { - Object bufferPool = ReflectionUtils.invokeMethod(getBufferPoolMethod, exchange); - WebSocketChannel channel = (WebSocketChannel) ReflectionUtils.invokeMethod( - createChannelMethod, handshake, exchange, connection, bufferPool); - if (peerConnections != null) { - peerConnections.add(channel); - } - endpointSessionHandler.onConnect(exchange, channel); + try { + getContainer(servletRequest).doUpgrade(servletRequest, servletResponse, + endpointConfig, pathParams); + } + catch (ServletException ex) { + throw new HandshakeFailureException( + "Servlet request failed to upgrade to WebSocket: " + requestUrl, ex); + } + catch (IOException ex) { + throw new HandshakeFailureException( + "Response update failed during upgrade to WebSocket: " + requestUrl, ex); } - }); - - handshake.handshake(exchange); - } - - private ServletWebSocketHttpExchange createHttpExchange(HttpServletRequest request, HttpServletResponse response) { - try { - return (this.peerConnections != null ? - exchangeConstructor.newInstance(request, response, this.peerConnections) : - exchangeConstructor.newInstance(request, response)); } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to instantiate ServletWebSocketHttpExchange", ex); + else { + FALLBACK_STRATEGY.upgradeInternal(request, response, selectedProtocol, + selectedExtensions, endpoint); } } - private Handshake getHandshakeToUse(ServletWebSocketHttpExchange exchange, ConfiguredServerEndpoint endpoint) { - Handshake handshake = new JsrHybi13Handshake(endpoint); - if (handshake.matches(exchange)) { - return handshake; - } - handshake = new JsrHybi08Handshake(endpoint); - if (handshake.matches(exchange)) { - return handshake; - } - handshake = new JsrHybi07Handshake(endpoint); - if (handshake.matches(exchange)) { - return handshake; - } - // Should never occur - throw new HandshakeFailureException("No matching Undertow Handshake found: " + exchange.getRequestHeaders()); - } - - private ConfiguredServerEndpoint createConfiguredServerEndpoint(String selectedProtocol, - List selectedExtensions, Endpoint endpoint, HttpServletRequest servletRequest) { - - String path = servletRequest.getRequestURI(); // shouldn't matter - ServerEndpointRegistration endpointRegistration = new ServerEndpointRegistration(path, endpoint); - endpointRegistration.setSubprotocols(Arrays.asList(selectedProtocol)); - endpointRegistration.setExtensions(selectedExtensions); - - EncodingFactory encodingFactory = new EncodingFactory( - Collections., List>>emptyMap(), - Collections., List>>emptyMap(), - Collections., List>>emptyMap(), - Collections., List>>emptyMap()); - try { - return (endpointConstructorWithEndpointFactory ? - endpointConstructor.newInstance(endpointRegistration, - new EndpointInstanceFactory(endpoint), null, encodingFactory, null) : - endpointConstructor.newInstance(endpointRegistration, - new EndpointInstanceFactory(endpoint), null, encodingFactory)); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to instantiate ConfiguredServerEndpoint", ex); - } + public ServerWebSocketContainer getContainer(HttpServletRequest request) { + return (ServerWebSocketContainer) super.getContainer(request); } - private static class EndpointInstanceFactory implements InstanceFactory { + /** + * Strategy for use with Undertow 1.0 to 1.3 before there was a public API + * to perform a WebSocket upgrade. + */ + private static class FallbackStrategy extends AbstractStandardUpgradeStrategy { - private final Endpoint endpoint; + private static final Constructor exchangeConstructor; - public EndpointInstanceFactory(Endpoint endpoint) { - this.endpoint = endpoint; + private static final boolean exchangeConstructorWithPeerConnections; + + private static final Constructor endpointConstructor; + + private static final boolean endpointConstructorWithEndpointFactory; + + private static final Method getBufferPoolMethod; + + private static final Method createChannelMethod; + + static { + try { + Class exchangeType = ServletWebSocketHttpExchange.class; + Class[] exchangeParamTypes = + new Class[] {HttpServletRequest.class, HttpServletResponse.class, Set.class}; + Constructor exchangeCtor = + ClassUtils.getConstructorIfAvailable(exchangeType, exchangeParamTypes); + if (exchangeCtor != null) { + // Undertow 1.1+ + exchangeConstructor = exchangeCtor; + exchangeConstructorWithPeerConnections = true; + } + else { + // Undertow 1.0 + exchangeParamTypes = new Class[] {HttpServletRequest.class, HttpServletResponse.class}; + exchangeConstructor = exchangeType.getConstructor(exchangeParamTypes); + exchangeConstructorWithPeerConnections = false; + } + + Class endpointType = ConfiguredServerEndpoint.class; + Class[] endpointParamTypes = new Class[] {ServerEndpointConfig.class, InstanceFactory.class, + PathTemplate.class, EncodingFactory.class, AnnotatedEndpointFactory.class}; + Constructor endpointCtor = + ClassUtils.getConstructorIfAvailable(endpointType, endpointParamTypes); + if (endpointCtor != null) { + // Undertow 1.1+ + endpointConstructor = endpointCtor; + endpointConstructorWithEndpointFactory = true; + } + else { + // Undertow 1.0 + endpointParamTypes = new Class[] {ServerEndpointConfig.class, InstanceFactory.class, + PathTemplate.class, EncodingFactory.class}; + endpointConstructor = endpointType.getConstructor(endpointParamTypes); + endpointConstructorWithEndpointFactory = false; + } + + // Adapting between different Pool API types in Undertow 1.0-1.2 vs 1.3 + getBufferPoolMethod = WebSocketHttpExchange.class.getMethod("getBufferPool"); + createChannelMethod = ReflectionUtils.findMethod(Handshake.class, "createChannel", (Class[]) null); + } + catch (Throwable ex) { + throw new IllegalStateException("Incompatible Undertow API version", ex); + } + } + + + private final Set peerConnections; + + + public FallbackStrategy() { + if (exchangeConstructorWithPeerConnections) { + this.peerConnections = Collections.newSetFromMap(new ConcurrentHashMap()); + } + else { + this.peerConnections = null; + } + } + + + @Override + public String[] getSupportedVersions() { + return VERSIONS; } @Override - public InstanceHandle createInstance() throws InstantiationException { - return new InstanceHandle() { + protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, + String selectedProtocol, List selectedExtensions, final Endpoint endpoint) + throws HandshakeFailureException { + + HttpServletRequest servletRequest = getHttpServletRequest(request); + HttpServletResponse servletResponse = getHttpServletResponse(response); + + final ServletWebSocketHttpExchange exchange = createHttpExchange(servletRequest, servletResponse); + exchange.putAttachment(HandshakeUtil.PATH_PARAMS, Collections.emptyMap()); + + ServerWebSocketContainer wsContainer = (ServerWebSocketContainer) getContainer(servletRequest); + final EndpointSessionHandler endpointSessionHandler = new EndpointSessionHandler(wsContainer); + + final ConfiguredServerEndpoint configuredServerEndpoint = createConfiguredServerEndpoint( + selectedProtocol, selectedExtensions, endpoint, servletRequest); + + final Handshake handshake = getHandshakeToUse(exchange, configuredServerEndpoint); + + exchange.upgradeChannel(new HttpUpgradeListener() { @Override - public Endpoint getInstance() { - return endpoint; + public void handleUpgrade(StreamConnection connection, HttpServerExchange serverExchange) { + Object bufferPool = ReflectionUtils.invokeMethod(getBufferPoolMethod, exchange); + WebSocketChannel channel = (WebSocketChannel) ReflectionUtils.invokeMethod( + createChannelMethod, handshake, exchange, connection, bufferPool); + if (peerConnections != null) { + peerConnections.add(channel); + } + endpointSessionHandler.onConnect(exchange, channel); } - @Override - public void release() { - } - }; + }); + + handshake.handshake(exchange); + } + + private ServletWebSocketHttpExchange createHttpExchange(HttpServletRequest request, HttpServletResponse response) { + try { + return (this.peerConnections != null ? + exchangeConstructor.newInstance(request, response, this.peerConnections) : + exchangeConstructor.newInstance(request, response)); + } + catch (Exception ex) { + throw new HandshakeFailureException("Failed to instantiate ServletWebSocketHttpExchange", ex); + } + } + + private Handshake getHandshakeToUse(ServletWebSocketHttpExchange exchange, ConfiguredServerEndpoint endpoint) { + Handshake handshake = new JsrHybi13Handshake(endpoint); + if (handshake.matches(exchange)) { + return handshake; + } + handshake = new JsrHybi08Handshake(endpoint); + if (handshake.matches(exchange)) { + return handshake; + } + handshake = new JsrHybi07Handshake(endpoint); + if (handshake.matches(exchange)) { + return handshake; + } + // Should never occur + throw new HandshakeFailureException("No matching Undertow Handshake found: " + exchange.getRequestHeaders()); + } + + private ConfiguredServerEndpoint createConfiguredServerEndpoint(String selectedProtocol, + List selectedExtensions, Endpoint endpoint, HttpServletRequest servletRequest) { + + String path = servletRequest.getRequestURI(); // shouldn't matter + ServerEndpointRegistration endpointRegistration = new ServerEndpointRegistration(path, endpoint); + endpointRegistration.setSubprotocols(Collections.singletonList(selectedProtocol)); + endpointRegistration.setExtensions(selectedExtensions); + + EncodingFactory encodingFactory = new EncodingFactory( + Collections., List>>emptyMap(), + Collections., List>>emptyMap(), + Collections., List>>emptyMap(), + Collections., List>>emptyMap()); + try { + return (endpointConstructorWithEndpointFactory ? + endpointConstructor.newInstance(endpointRegistration, + new EndpointInstanceFactory(endpoint), null, encodingFactory, null) : + endpointConstructor.newInstance(endpointRegistration, + new EndpointInstanceFactory(endpoint), null, encodingFactory)); + } + catch (Exception ex) { + throw new HandshakeFailureException("Failed to instantiate ConfiguredServerEndpoint", ex); + } + } + + + private static class EndpointInstanceFactory implements InstanceFactory { + + private final Endpoint endpoint; + + public EndpointInstanceFactory(Endpoint endpoint) { + this.endpoint = endpoint; + } + + @Override + public InstanceHandle createInstance() throws InstantiationException { + return new InstanceHandle() { + @Override + public Endpoint getInstance() { + return endpoint; + } + @Override + public void release() { + } + }; + } } }