diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java index 614dbd405c..66297bdb62 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java @@ -21,7 +21,7 @@ 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.Collections; import java.util.List; import java.util.Random; import javax.servlet.ServletException; @@ -53,6 +53,8 @@ import org.springframework.util.StringUtils; import org.springframework.web.socket.WebSocketExtension; import org.springframework.web.socket.server.HandshakeFailureException; +import static org.glassfish.tyrus.spi.WebSocketEngine.UpgradeStatus.SUCCESS; + /** * A base class for {@code RequestUpgradeStrategy} implementations on top of * JSR-356 based servers which include Tyrus as their WebSocket engine. @@ -96,6 +98,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda TyrusServerContainer serverContainer = (TyrusServerContainer) getContainer(servletRequest); TyrusWebSocketEngine engine = (TyrusWebSocketEngine) serverContainer.getWebSocketEngine(); Object tyrusEndpoint = null; + boolean success; try { // Shouldn't matter for processing but must be unique @@ -107,29 +110,22 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda RequestContext requestContext = createRequestContext(servletRequest, path, headers); TyrusUpgradeResponse upgradeResponse = new TyrusUpgradeResponse(); UpgradeInfo upgradeInfo = engine.upgrade(requestContext, upgradeResponse); - - switch (upgradeInfo.getStatus()) { - case SUCCESS: - if (logger.isTraceEnabled()) { - logger.trace("Successful upgrade: " + upgradeResponse.getHeaders()); - } - handleSuccess(servletRequest, servletResponse, upgradeInfo, upgradeResponse); - 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()); + success = SUCCESS.equals(upgradeInfo.getStatus()); + if (success) { + if (logger.isTraceEnabled()) { + logger.trace("Successful upgrade: " + upgradeResponse.getHeaders()); + } + handleSuccess(servletRequest, servletResponse, upgradeInfo, upgradeResponse); } } catch (Exception ex) { + unregisterTyrusEndpoint(engine, tyrusEndpoint); throw new HandshakeFailureException("Error during handshake: " + request.getURI(), ex); } - finally { - if (tyrusEndpoint != null) { - getEndpointHelper().unregister(engine, tyrusEndpoint); - } + + unregisterTyrusEndpoint(engine, tyrusEndpoint); + if (!success) { + throw new HandshakeFailureException("Unexpected handshake failure: " + request.getURI()); } } @@ -138,7 +134,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda throws DeploymentException { ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint); - endpointConfig.setSubprotocols(Arrays.asList(protocol)); + endpointConfig.setSubprotocols(Collections.singletonList(protocol)); endpointConfig.setExtensions(extensions); return getEndpointHelper().createdEndpoint(endpointConfig, this.componentProvider, container, engine); } @@ -157,6 +153,16 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda return context; } + private void unregisterTyrusEndpoint(TyrusWebSocketEngine engine, Object tyrusEndpoint) { + if (tyrusEndpoint != null) { + try { + getEndpointHelper().unregister(engine, tyrusEndpoint); + } + catch (Throwable ex) { + // ignore + } + } + } protected abstract TyrusEndpointHelper getEndpointHelper(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java index 13843b9bb6..64f1057f6a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java @@ -30,7 +30,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.CloseReason; +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.spi.Connection; import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo; @@ -38,24 +40,47 @@ import org.glassfish.tyrus.spi.Writer; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.web.socket.server.HandshakeFailureException; /** - * A WebSocket {@code RequestUpgradeStrategy} for Oracle's WebLogic 12.1.3 and higher. + * A WebSocket {@code RequestUpgradeStrategy} for Oracle's WebLogic. + * Supports 12.1.3 and 12.2.1.0. * * @author Rossen Stoyanchev * @since 4.1 */ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeStrategy { - private static final TyrusEndpointHelper endpointHelper = new Tyrus135EndpointHelper(); + private static ClassLoader classLoader = WebLogicRequestUpgradeStrategy.class.getClassLoader(); + + private static final boolean WLS_12_1_3 = isWebLogic1213(); + + private static final TyrusEndpointHelper endpointHelper = WLS_12_1_3 ? + new Tyrus135EndpointHelper() : new Tyrus17EndpointHelper(); private static final TyrusMuxableWebSocketHelper webSocketHelper = new TyrusMuxableWebSocketHelper(); private static final WebLogicServletWriterHelper servletWriterHelper = new WebLogicServletWriterHelper(); + + private static boolean isWebLogic1213() { + try { + type("weblogic.websocket.tyrus.TyrusMuxableWebSocket").getDeclaredConstructor( + type("weblogic.servlet.internal.MuxableSocketHTTP")); + return true; + } + catch (NoSuchMethodException e) { + return false; + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("No compatible WebSocket version found", ex); + } + } + + @Override protected TyrusEndpointHelper getEndpointHelper() { return endpointHelper; @@ -76,7 +101,7 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS Object nativeRequest = getNativeRequest(request); BeanWrapper beanWrapper = new BeanWrapperImpl(nativeRequest); Object httpSocket = beanWrapper.getPropertyValue("connection.connectionHandler.rawConnection"); - Object webSocket = webSocketHelper.newInstance(httpSocket); + Object webSocket = webSocketHelper.newInstance(request, httpSocket); webSocketHelper.upgrade(webSocket, httpSocket, request.getServletContext()); response.flushBuffer(); @@ -96,6 +121,16 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS return request; } + private static Class type(String className) throws ClassNotFoundException { + return classLoader.loadClass(className); + } + + private static Method method(String className, String method, Class... paramTypes) + throws ClassNotFoundException, NoSuchMethodException { + + return type(className).getDeclaredMethod(method, paramTypes); + } + private static final Connection.CloseListener noOpCloseListener = new Connection.CloseListener() { @@ -110,33 +145,47 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS */ private static class TyrusMuxableWebSocketHelper { - public static final Class webSocketType; + private static final Class type; - private static final Constructor webSocketConstructor; + private static final Constructor constructor; - private static final Method webSocketUpgradeMethod; + private static final SubjectHelper subjectHelper; - private static final Method webSocketReadEventMethod; + private static final Method upgradeMethod; + + private static final Method readEventMethod; static { try { - ClassLoader classLoader = WebLogicRequestUpgradeStrategy.class.getClassLoader(); - Class socketType = classLoader.loadClass("weblogic.socket.MuxableSocket"); - Class httpSocketType = classLoader.loadClass("weblogic.servlet.internal.MuxableSocketHTTP"); + type = type("weblogic.websocket.tyrus.TyrusMuxableWebSocket"); + if (WLS_12_1_3) { + constructor = type.getDeclaredConstructor(type("weblogic.servlet.internal.MuxableSocketHTTP")); + subjectHelper = null; + } + else { + constructor = type.getDeclaredConstructor( + type("weblogic.servlet.internal.MuxableSocketHTTP"), + type("weblogic.websocket.tyrus.CoherenceServletFilterService"), + type("weblogic.servlet.spi.SubjectHandle")); + subjectHelper = new SubjectHelper(); + } - webSocketType = classLoader.loadClass("weblogic.websocket.tyrus.TyrusMuxableWebSocket"); - webSocketConstructor = webSocketType.getDeclaredConstructor(httpSocketType); - webSocketUpgradeMethod = webSocketType.getMethod("upgrade", socketType, ServletContext.class); - webSocketReadEventMethod = webSocketType.getMethod("registerForReadEvent"); + upgradeMethod = type.getMethod("upgrade", type("weblogic.socket.MuxableSocket"), ServletContext.class); + + readEventMethod = type.getMethod("registerForReadEvent"); } catch (Exception ex) { throw new IllegalStateException("No compatible WebSocket version found", ex); } } - private Object newInstance(Object httpSocket) { + private Object newInstance(HttpServletRequest request, Object httpSocket) { try { - return webSocketConstructor.newInstance(httpSocket); + Object[] args = (WLS_12_1_3 ? + new Object[] {httpSocket} : + new Object[] {httpSocket, null, subjectHelper.getSubject(request)}); + + return constructor.newInstance(args); } catch (Exception ex) { throw new HandshakeFailureException("Failed to create TyrusMuxableWebSocket", ex); @@ -145,7 +194,7 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS private void upgrade(Object webSocket, Object httpSocket, ServletContext servletContext) { try { - webSocketUpgradeMethod.invoke(webSocket, httpSocket, servletContext); + upgradeMethod.invoke(webSocket, httpSocket, servletContext); } catch (Exception ex) { throw new HandshakeFailureException("Failed to upgrade TyrusMuxableWebSocket", ex); @@ -154,7 +203,7 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS private void registerForReadEvent(Object webSocket) { try { - webSocketReadEventMethod.invoke(webSocket); + readEventMethod.invoke(webSocket); } catch (Exception ex) { throw new HandshakeFailureException("Failed to register WebSocket for read event", ex); @@ -162,6 +211,52 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS } } + private static class SubjectHelper { + + private final Method securityContextMethod; + + private final Method currentUserMethod; + + private final Method providerMethod; + + private final Method anonymousSubjectMethod; + + + public SubjectHelper() { + try { + String className = "weblogic.servlet.internal.WebAppServletContext"; + securityContextMethod = method(className, "getSecurityContext"); + + className = "weblogic.servlet.security.internal.SecurityModule"; + currentUserMethod = method(className, "getCurrentUser", + type("weblogic.servlet.security.internal.ServletSecurityContext"), + HttpServletRequest.class); + + className = "weblogic.servlet.security.internal.WebAppSecurity"; + providerMethod = method(className, "getProvider"); + anonymousSubjectMethod = providerMethod.getReturnType().getDeclaredMethod("getAnonymousSubject"); + } + catch (Exception ex) { + throw new IllegalStateException("No compatible WebSocket version found", ex); + } + } + + public Object getSubject(HttpServletRequest request) { + try { + ServletContext servletContext = request.getServletContext(); + Object securityContext = securityContextMethod.invoke(servletContext); + Object subject = currentUserMethod.invoke(null, securityContext, request); + if (subject == null) { + Object securityProvider = providerMethod.invoke(null); + subject = anonymousSubjectMethod.invoke(securityProvider); + } + return subject; + } + catch (Exception ex) { + throw new HandshakeFailureException("Failed to obtain SubjectHandle", ex); + } + } + } /** * Helps to create and invoke {@code weblogic.websocket.tyrus.TyrusServletWriter}. @@ -172,12 +267,16 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS static { try { - ClassLoader loader = WebLogicRequestUpgradeStrategy.class.getClassLoader(); - Class type = loader.loadClass("weblogic.websocket.tyrus.TyrusServletWriter"); - Class listenerType = loader.loadClass("weblogic.websocket.tyrus.TyrusServletWriter$CloseListener"); - Class webSocketType = TyrusMuxableWebSocketHelper.webSocketType; + Class writerType = type("weblogic.websocket.tyrus.TyrusServletWriter"); + Class listenerType = type("weblogic.websocket.tyrus.TyrusServletWriter$CloseListener"); + Class webSocketType = TyrusMuxableWebSocketHelper.type; Class responseType = HttpServletResponse.class; - constructor = type.getDeclaredConstructor(webSocketType, responseType, listenerType, boolean.class); + + Class[] argTypes = (WLS_12_1_3 ? + new Class[] {webSocketType, responseType, listenerType, boolean.class} : + new Class[] {webSocketType, listenerType, boolean.class}); + + constructor = writerType.getDeclaredConstructor(argTypes); ReflectionUtils.makeAccessible(constructor); } catch (Exception ex) { @@ -187,7 +286,11 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS private Writer newInstance(HttpServletResponse response, Object webSocket, boolean isProtected) { try { - return (Writer) constructor.newInstance(webSocket, response, null, isProtected); + Object[] args = (WLS_12_1_3 ? + new Object[] {webSocket, response, null, isProtected} : + new Object[] {webSocket, null, isProtected}); + + return (Writer) constructor.newInstance(args); } catch (Exception ex) { throw new HandshakeFailureException("Failed to create TyrusServletWriter", ex);