From 88d8dff3acc9a46dabacb865dc475e33820596b6 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 23 Jul 2014 22:51:28 -0400 Subject: [PATCH] Upgrade to Tyrus 1.7 This change provides WebSocket support for the upcoming Glassfish 4.0.1 release while at the same dropping support for Glassfish 4.0 due to incompatible changes. Issue: SPR-11094 --- build.gradle | 6 +- .../GlassFishRequestUpgradeStrategy.java | 199 +++++++++--------- 2 files changed, 98 insertions(+), 107 deletions(-) diff --git a/build.gradle b/build.gradle index 5f51bdc586..91ea5725e2 100644 --- a/build.gradle +++ b/build.gradle @@ -684,8 +684,10 @@ project("spring-websocket") { exclude group: "org.apache.tomcat", module: "tomcat-websocket-api" exclude group: "org.apache.tomcat", module: "tomcat-servlet-api" } - optional("org.glassfish.tyrus:tyrus-websocket-core:1.2.1") - optional("org.glassfish.tyrus:tyrus-container-servlet:1.2.1") + optional("org.glassfish.tyrus:tyrus-spi:1.7") + optional("org.glassfish.tyrus:tyrus-core:1.7") + optional("org.glassfish.tyrus:tyrus-server:1.7") + optional("org.glassfish.tyrus:tyrus-container-servlet:1.7") optional("org.eclipse.jetty:jetty-webapp:${jettyVersion}") { exclude group: "javax.servlet", module: "javax.servlet" } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java index 065936805e..f3bb35d96b 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java @@ -16,14 +16,14 @@ package org.springframework.web.socket.server.standard; -import java.io.IOException; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Random; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.DeploymentException; @@ -32,16 +32,19 @@ import javax.websocket.Extension; import javax.websocket.WebSocketContainer; import org.glassfish.tyrus.core.ComponentProviderService; -import org.glassfish.tyrus.core.EndpointWrapper; -import org.glassfish.tyrus.core.ErrorCollector; import org.glassfish.tyrus.core.RequestContext; +import org.glassfish.tyrus.core.TyrusEndpointWrapper; +import org.glassfish.tyrus.core.TyrusUpgradeResponse; +import org.glassfish.tyrus.core.TyrusWebSocketEngine; +import org.glassfish.tyrus.core.Utils; +import org.glassfish.tyrus.core.Version; +import org.glassfish.tyrus.core.cluster.ClusterContext; +import org.glassfish.tyrus.core.monitoring.EndpointEventListener; +import org.glassfish.tyrus.server.TyrusServerContainer; import org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler; -import org.glassfish.tyrus.spi.SPIEndpoint; -import org.glassfish.tyrus.websockets.Connection; -import org.glassfish.tyrus.websockets.Version; -import org.glassfish.tyrus.websockets.WebSocketApplication; -import org.glassfish.tyrus.websockets.WebSocketEngine; +import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo; +import org.glassfish.tyrus.spi.Writer; import org.springframework.http.HttpHeaders; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -51,7 +54,7 @@ import org.springframework.web.socket.WebSocketExtension; import org.springframework.web.socket.server.HandshakeFailureException; /** - * {@code RequestUpgradeStrategy} that provides support for GlassFish 4 and beyond. + * A WebSocket request upgrade strategy for GlassFish 4.0.1 and beyond. * * @author Rossen Stoyanchev * @author Juergen Hoeller @@ -60,43 +63,31 @@ import org.springframework.web.socket.server.HandshakeFailureException; */ public class GlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { - private final static Random random = new Random(); + private static final Random random = new Random(); - private static final Constructor tyrusConnectionConstructor; - - private static final Constructor tyrusEndpointConstructor; + private static final Constructor tyrusServletWriterConstructor; + private static final Method endpointRegistrationMethod; static { - ClassLoader cl = GlassFishRequestUpgradeStrategy.class.getClassLoader(); try { - // Tyrus ConnectionImpl is package-visible only - Class tyrusConnectionClass = cl.loadClass("org.glassfish.tyrus.servlet.ConnectionImpl"); - tyrusConnectionConstructor = tyrusConnectionClass.getDeclaredConstructor( - TyrusHttpUpgradeHandler.class, HttpServletResponse.class); - ReflectionUtils.makeAccessible(tyrusConnectionConstructor); - - // TyrusEndpoint package location differs between GlassFish 4.0.0 and 4.0.1 - Class tyrusEndpointClass; - try { - tyrusEndpointClass = cl.loadClass("org.glassfish.tyrus.core.TyrusEndpoint"); - } - catch (ClassNotFoundException ex) { - try { - tyrusEndpointClass = cl.loadClass("org.glassfish.tyrus.server.TyrusEndpoint"); - } - catch (ClassNotFoundException ex2) { - // Propagate original exception for newer version of the class - throw ex; - } - } - tyrusEndpointConstructor = tyrusEndpointClass.getConstructor(SPIEndpoint.class); + ClassLoader classLoader = GlassFishRequestUpgradeStrategy.class.getClassLoader(); + Class type = classLoader.loadClass("org.glassfish.tyrus.servlet.TyrusServletWriter"); + tyrusServletWriterConstructor = type.getDeclaredConstructor(TyrusHttpUpgradeHandler.class); + ReflectionUtils.makeAccessible(tyrusServletWriterConstructor); + + Class endpointType = TyrusEndpointWrapper.class; + endpointRegistrationMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", endpointType); + ReflectionUtils.makeAccessible(endpointRegistrationMethod); } catch (Exception ex) { throw new IllegalStateException("No compatible Tyrus version found", ex); } } + private final ComponentProviderService componentProviderService = ComponentProviderService.create(); + + @Override public String[] getSupportedVersions() { @@ -114,99 +105,97 @@ public class GlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStra @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, List selectedExtensions, - Endpoint endpoint) throws HandshakeFailureException { + String subProtocol, List extensions, Endpoint endpoint) throws HandshakeFailureException { HttpServletRequest servletRequest = getHttpServletRequest(request); HttpServletResponse servletResponse = getHttpServletResponse(response); - WebSocketApplication webSocketApplication = createTyrusEndpoint(endpoint, selectedProtocol, selectedExtensions); - - WebSocketEngine webSocketEngine = WebSocketEngine.getEngine(); + TyrusServerContainer serverContainer = (TyrusServerContainer) getContainer(servletRequest); + TyrusWebSocketEngine engine = (TyrusWebSocketEngine) serverContainer.getWebSocketEngine(); + TyrusEndpointWrapper tyrusEndpoint = null; try { - webSocketEngine.register(webSocketApplication); - } - catch (DeploymentException ex) { - throw new HandshakeFailureException("Failed to configure endpoint in GlassFish", ex); - } - - try { - performUpgrade(servletRequest, servletResponse, request.getHeaders(), webSocketApplication); + tyrusEndpoint = createTyrusEndpoint(endpoint, subProtocol, extensions, serverContainer); + endpointRegistrationMethod.invoke(engine, tyrusEndpoint); + + String endpointPath = tyrusEndpoint.getEndpointPath(); + HttpHeaders headers = request.getHeaders(); + + RequestContext requestContext = createRequestContext(servletRequest, endpointPath, headers); + TyrusUpgradeResponse upgradeResponse = new TyrusUpgradeResponse(); + UpgradeInfo upgradeInfo = engine.upgrade(requestContext, upgradeResponse); + + switch (upgradeInfo.getStatus()) { + case SUCCESS: + TyrusHttpUpgradeHandler handler = servletRequest.upgrade(TyrusHttpUpgradeHandler.class); + Writer servletWriter = createTyrusServletWriter(handler); + handler.preInit(upgradeInfo, servletWriter, servletRequest.getUserPrincipal() != null); + servletResponse.setStatus(upgradeResponse.getStatus()); + for (Map.Entry> entry : upgradeResponse.getHeaders().entrySet()) { + servletResponse.addHeader(entry.getKey(), Utils.getHeaderFromList(entry.getValue())); + } + servletResponse.flushBuffer(); + if (logger.isTraceEnabled()) { + logger.trace("Successful upgrade uri=" + servletRequest.getRequestURI() + + ", response headers=" + upgradeResponse.getHeaders()); + } + break; + case HANDSHAKE_FAILED: + // Should never happen + throw new HandshakeFailureException("Unexpected handshake failure: " + request.getURI()); + case NOT_APPLICABLE: + // Should never happen + throw new HandshakeFailureException("Unexpected handshake mapping failure: " + request.getURI()); + } } - catch (IOException ex) { - throw new HandshakeFailureException( - "Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex); + catch (Exception ex) { + throw new HandshakeFailureException("Error during handshake: " + request.getURI(), ex); } finally { - webSocketEngine.unregister(webSocketApplication); - } - } - - private boolean performUpgrade(HttpServletRequest request, HttpServletResponse response, - HttpHeaders headers, WebSocketApplication wsApp) throws IOException { - - final TyrusHttpUpgradeHandler upgradeHandler; - try { - upgradeHandler = request.upgrade(TyrusHttpUpgradeHandler.class); - } - catch (ServletException ex) { - throw new HandshakeFailureException("Unable to create TyrusHttpUpgradeHandler", ex); - } - - Connection connection = createConnection(upgradeHandler, response); - - RequestContext requestContext = RequestContext.Builder.create(). - requestURI(URI.create(wsApp.getPath())).requestPath(wsApp.getPath()). - userPrincipal(request.getUserPrincipal()). - connection(connection).secure(request.isSecure()).build(); - - for (String header : headers.keySet()) { - requestContext.getHeaders().put(header, headers.get(header)); + if (tyrusEndpoint != null) { + engine.unregister(tyrusEndpoint); + } } - - boolean upgraded = WebSocketEngine.getEngine().upgrade(connection, requestContext, - new WebSocketEngine.WebSocketHolderListener() { - @Override - public void onWebSocketHolder(WebSocketEngine.WebSocketHolder webSocketHolder) { - upgradeHandler.setWebSocketHolder(webSocketHolder); - } - }); - - // GlassFish bug ?? (see same line in TyrusServletFilter.doFilter) - response.flushBuffer(); - - return upgraded; } - private WebSocketApplication createTyrusEndpoint(Endpoint endpoint, String selectedProtocol, - List selectedExtensions) { + private TyrusEndpointWrapper createTyrusEndpoint(Endpoint endpoint, String protocol, + List extensions, WebSocketContainer container) throws DeploymentException { // Shouldn't matter for processing but must be unique String endpointPath = "/" + random.nextLong(); + ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint); - endpointConfig.setSubprotocols(Arrays.asList(selectedProtocol)); - endpointConfig.setExtensions(selectedExtensions); - return createTyrusEndpoint(new EndpointWrapper(endpoint, endpointConfig, - ComponentProviderService.create(), null, "/", new ErrorCollector(), - endpointConfig.getConfigurator())); + endpointConfig.setSubprotocols(Arrays.asList(protocol)); + endpointConfig.setExtensions(extensions); + + TyrusEndpointWrapper.SessionListener sessionListener = new TyrusEndpointWrapper.SessionListener() {}; + ClusterContext clusterContext = null; + EndpointEventListener eventListener = EndpointEventListener.NO_OP; + + return new TyrusEndpointWrapper(endpoint, endpointConfig, this.componentProviderService, + container, "/", endpointConfig.getConfigurator(), sessionListener, clusterContext, eventListener); } - private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) { - try { - return (Connection) tyrusConnectionConstructor.newInstance(handler, response); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to create GlassFish connection", ex); + private RequestContext createRequestContext(HttpServletRequest request, String endpointPath, HttpHeaders headers) { + RequestContext context = + RequestContext.Builder.create() + .requestURI(URI.create(endpointPath)) + .userPrincipal(request.getUserPrincipal()) + .secure(request.isSecure()) + .remoteAddr(request.getRemoteAddr()) + .build(); + for (String header : headers.keySet()) { + context.getHeaders().put(header, headers.get(header)); } + return context; } - protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint) { + private Writer createTyrusServletWriter(TyrusHttpUpgradeHandler handler) { try { - return (WebSocketApplication) tyrusEndpointConstructor.newInstance(endpoint); + return (Writer) tyrusServletWriterConstructor.newInstance(handler); } catch (Exception ex) { - throw new IllegalStateException("Failed to create GlassFish endpoint", ex); + throw new HandshakeFailureException("Failed to instantiate TyrusServletWriter", ex); } }