Require Undertow 1.3.5+, Tyrus 1.11+, Jetty 9.3+, Tomcat 8.5+

Issue: SPR-13495
This commit is contained in:
Juergen Hoeller 2016-07-04 23:27:07 +02:00
parent ccf791b63f
commit 770f0c0661
11 changed files with 137 additions and 834 deletions

View File

@ -1,44 +0,0 @@
/*
* Copyright 2002-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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.loader;
/**
* A mock of Tomcat's {@code WebappClassLoader} just for Spring's compilation purposes.
* Exposes both pre-7.0.63 as well as 7.0.63+ variants of {@code findResourceInternal}.
*
* @author Juergen Hoeller
* @since 4.2
*/
public class WebappClassLoader extends ClassLoader {
public WebappClassLoader() {
}
public WebappClassLoader(ClassLoader parent) {
super(parent);
}
protected ResourceEntry findResourceInternal(String name, String path) {
throw new UnsupportedOperationException();
}
protected ResourceEntry findResourceInternal(String name, String path, boolean manifestRequired) {
throw new UnsupportedOperationException();
}
}

View File

@ -1,107 +0,0 @@
/*
* Copyright 2002-2007 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.instrument.classloading;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
/**
* ClassFileTransformer-based weaver, allowing for a list of transformers to be
* applied on a class byte array. Normally used inside class loaders.
*
* <p>Note: This class is deliberately implemented for minimal external dependencies,
* since it is included in weaver jars (to be deployed into application servers).
*
* @author Rod Johnson
* @author Costin Leau
* @author Juergen Hoeller
* @since 2.0
*/
public class WeavingTransformer {
private final ClassLoader classLoader;
private final List<ClassFileTransformer> transformers = new ArrayList<ClassFileTransformer>();
/**
* Create a new WeavingTransformer for the given class loader.
* @param classLoader the ClassLoader to build a transformer for
*/
public WeavingTransformer(ClassLoader classLoader) {
if (classLoader == null) {
throw new IllegalArgumentException("ClassLoader must not be null");
}
this.classLoader = classLoader;
}
/**
* Add a class file transformer to be applied by this weaver.
* @param transformer the class file transformer to register
*/
public void addTransformer(ClassFileTransformer transformer) {
if (transformer == null) {
throw new IllegalArgumentException("Transformer must not be null");
}
this.transformers.add(transformer);
}
/**
* Apply transformation on a given class byte definition.
* The method will always return a non-null byte array (if no transformation has taken place
* the array content will be identical to the original one).
* @param className the full qualified name of the class in dot format (i.e. some.package.SomeClass)
* @param bytes class byte definition
* @return (possibly transformed) class byte definition
*/
public byte[] transformIfNecessary(String className, byte[] bytes) {
String internalName = className.replace(".", "/");
return transformIfNecessary(className, internalName, bytes, null);
}
/**
* Apply transformation on a given class byte definition.
* The method will always return a non-null byte array (if no transformation has taken place
* the array content will be identical to the original one).
* @param className the full qualified name of the class in dot format (i.e. some.package.SomeClass)
* @param internalName class name internal name in / format (i.e. some/package/SomeClass)
* @param bytes class byte definition
* @param pd protection domain to be used (can be null)
* @return (possibly transformed) class byte definition
*/
public byte[] transformIfNecessary(String className, String internalName, byte[] bytes, ProtectionDomain pd) {
byte[] result = bytes;
for (ClassFileTransformer cft : this.transformers) {
try {
byte[] transformed = cft.transform(this.classLoader, internalName, null, pd, result);
if (transformed != null) {
result = transformed;
}
}
catch (IllegalClassFormatException ex) {
throw new IllegalStateException("Class file transformation failed", ex);
}
}
return result;
}
}

View File

@ -1,166 +0,0 @@
/*
* Copyright 2002-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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.instrument.classloading.tomcat;
import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.apache.catalina.loader.ResourceEntry;
import org.apache.catalina.loader.WebappClassLoader;
import org.springframework.instrument.classloading.WeavingTransformer;
/**
* Extension of Tomcat's default class loader which adds instrumentation
* to loaded classes without the need to use a VM-wide agent.
*
* <p>To be registered using a
* <a href="http://tomcat.apache.org/tomcat-6.0-doc/config/loader.html">{@code Loader}</a> tag
* in Tomcat's <a href="http://tomcat.apache.org/tomcat-6.0-doc/config/context.html">{@code Context}</a>
* definition in the {@code server.xml} file, with the Spring-provided "spring-instrument-tomcat.jar"
* file deployed into Tomcat's "lib" directory. The required configuration tag looks as follows:
*
* <pre class="code">&lt;Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/&gt;</pre>
*
* <p>Typically used in combination with a
* {@link org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver}
* defined in the Spring application context. The {@code addTransformer} and
* {@code getThrowawayClassLoader} methods mirror the corresponding methods
* in the LoadTimeWeaver interface, as expected by ReflectiveLoadTimeWeaver.
*
* <p><b>NOTE:</b> Requires Apache Tomcat version 6.0 or higher, as of Spring 4.0.
* This class is not intended to work on Tomcat 8.0+; please rely on Tomcat's own
* {@code InstrumentableClassLoader} facility instead, as autodetected by Spring's
* {@link org.springframework.instrument.classloading.tomcat.TomcatLoadTimeWeaver}.
*
* @author Costin Leau
* @author Juergen Hoeller
* @since 2.0
* @see #addTransformer
* @see #getThrowawayClassLoader
* @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
* @see org.springframework.instrument.classloading.tomcat.TomcatLoadTimeWeaver
*/
public class TomcatInstrumentableClassLoader extends WebappClassLoader {
private static final String CLASS_SUFFIX = ".class";
/** Use an internal WeavingTransformer */
private final WeavingTransformer weavingTransformer;
/**
* Create a new {@code TomcatInstrumentableClassLoader} using the
* current context class loader.
* @see #TomcatInstrumentableClassLoader(ClassLoader)
*/
public TomcatInstrumentableClassLoader() {
super();
this.weavingTransformer = new WeavingTransformer(this);
}
/**
* Create a new {@code TomcatInstrumentableClassLoader} with the
* supplied class loader as parent.
* @param parent the parent {@link ClassLoader} to be used
*/
public TomcatInstrumentableClassLoader(ClassLoader parent) {
super(parent);
this.weavingTransformer = new WeavingTransformer(this);
}
/**
* Delegate for LoadTimeWeaver's {@code addTransformer} method.
* Typically called through ReflectiveLoadTimeWeaver.
* @see org.springframework.instrument.classloading.LoadTimeWeaver#addTransformer
* @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
*/
public void addTransformer(ClassFileTransformer transformer) {
this.weavingTransformer.addTransformer(transformer);
}
/**
* Delegate for LoadTimeWeaver's {@code getThrowawayClassLoader} method.
* Typically called through ReflectiveLoadTimeWeaver.
* @see org.springframework.instrument.classloading.LoadTimeWeaver#getThrowawayClassLoader
* @see org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver
*/
public ClassLoader getThrowawayClassLoader() {
WebappClassLoader tempLoader = new WebappClassLoader();
// Use reflection to copy all the fields since they are not exposed any other way.
shallowCopyFieldState(this, tempLoader);
return tempLoader;
}
@Override // overriding the pre-7.0.63 variant of findResourceInternal
protected ResourceEntry findResourceInternal(String name, String path) {
ResourceEntry entry = super.findResourceInternal(name, path);
if (entry != null && entry.binaryContent != null && path.endsWith(CLASS_SUFFIX)) {
String className = (name.endsWith(CLASS_SUFFIX) ? name.substring(0, name.length() - CLASS_SUFFIX.length()) : name);
entry.binaryContent = this.weavingTransformer.transformIfNecessary(className, entry.binaryContent);
}
return entry;
}
@Override // overriding the 7.0.63+ variant of findResourceInternal
protected ResourceEntry findResourceInternal(String name, String path, boolean manifestRequired) {
ResourceEntry entry = super.findResourceInternal(name, path, manifestRequired);
if (entry != null && entry.binaryContent != null && path.endsWith(CLASS_SUFFIX)) {
String className = (name.endsWith(CLASS_SUFFIX) ? name.substring(0, name.length() - CLASS_SUFFIX.length()) : name);
entry.binaryContent = this.weavingTransformer.transformIfNecessary(className, entry.binaryContent);
}
return entry;
}
@Override
public String toString() {
return getClass().getName() + "\r\n" + super.toString();
}
// The code below is originally taken from ReflectionUtils and optimized for
// local usage. There is no dependency on ReflectionUtils to keep this class
// self-contained (since it gets deployed into Tomcat's server class loader).
private static void shallowCopyFieldState(final WebappClassLoader src, final WebappClassLoader dest) {
Class<?> targetClass = WebappClassLoader.class;
// Keep backing up the inheritance hierarchy.
do {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
// Do not copy resourceEntries - it's a cache that holds class entries.
if (!(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()) ||
field.getName().equals("resourceEntries"))) {
try {
field.setAccessible(true);
Object srcValue = field.get(src);
field.set(dest, srcValue);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException(
"Shouldn't be illegal to access field '" + field.getName() + "': " + ex);
}
}
}
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
}
}

View File

@ -1,7 +0,0 @@
<html>
<body>
<p>
Spring's instrumentation agent for Tomcat.
</p>
</body>
</html>

View File

@ -1,10 +0,0 @@
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%c] - %m%n
log4j.rootCategory=WARN, console
log4j.logger.org.springframework.beans=WARN
log4j.logger.org.springframework.binding=DEBUG
#log4j.logger.org.springframework.instrument.classloading=TRACE

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -56,8 +56,8 @@ import org.springframework.web.socket.server.HandshakeFailureException;
import org.springframework.web.socket.server.RequestUpgradeStrategy;
/**
* A {@link RequestUpgradeStrategy} for use with Jetty 9.0-9.3. Based on Jetty's
* internal {@code org.eclipse.jetty.websocket.server.WebSocketHandler} class.
* A {@link RequestUpgradeStrategy} for use with Jetty 9.3 and higher. Based on
* Jetty's internal {@code org.eclipse.jetty.websocket.server.WebSocketHandler} class.
*
* @author Phillip Webb
* @author Rossen Stoyanchev
@ -65,10 +65,6 @@ import org.springframework.web.socket.server.RequestUpgradeStrategy;
*/
public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Lifecycle, ServletContextAware {
// Pre-Jetty 9.3 init method without ServletContext
private static final Method webSocketFactoryInitMethod =
ClassUtils.getMethodIfAvailable(WebSocketServerFactory.class, "init");
private static final ThreadLocal<WebSocketHandlerContainer> wsContainerHolder =
new NamedThreadLocal<WebSocketHandlerContainer>("WebSocket Handler Container");
@ -153,12 +149,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
if (!isRunning()) {
this.running = true;
try {
if (webSocketFactoryInitMethod != null) {
webSocketFactoryInitMethod.invoke(this.factory);
}
else {
this.factory.init(this.servletContext);
}
this.factory.init(this.servletContext);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to initialize Jetty WebSocketServerFactory", ex);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -35,12 +35,10 @@ import javax.websocket.WebSocketContainer;
import org.glassfish.tyrus.core.ComponentProviderService;
import org.glassfish.tyrus.core.RequestContext;
import org.glassfish.tyrus.core.TyrusEndpoint;
import org.glassfish.tyrus.core.TyrusEndpointWrapper;
import org.glassfish.tyrus.core.TyrusUpgradeResponse;
import org.glassfish.tyrus.core.TyrusWebSocketEngine;
import org.glassfish.tyrus.core.Version;
import org.glassfish.tyrus.core.WebSocketApplication;
import org.glassfish.tyrus.server.TyrusServerContainer;
import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo;
@ -59,11 +57,11 @@ import static org.glassfish.tyrus.spi.WebSocketEngine.UpgradeStatus.*;
* A base class for {@code RequestUpgradeStrategy} implementations on top of
* JSR-356 based servers which include Tyrus as their WebSocket engine.
*
* <p>Works with Tyrus 1.3.5 (WebLogic 12.1.3), Tyrus 1.7 (GlassFish 4.1.0),
* Tyrus 1.11 (WebLogic 12.2.1), and Tyrus 1.12 (GlassFish 4.1.1).
* <p>Works with Tyrus 1.11 (WebLogic 12.2.1) and Tyrus 1.12 (GlassFish 4.1.1).
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Juergen Hoeller
* @since 4.1
* @see <a href="https://tyrus.java.net/">Project Tyrus</a>
*/
@ -71,6 +69,42 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
private static final Random random = new Random();
private static final Constructor<?> constructor;
private static boolean constructorWithBooleanArgument;
private static final Method registerMethod;
private static final Method unRegisterMethod;
static {
try {
constructor = getEndpointConstructor();
int parameterCount = constructor.getParameterTypes().length;
constructorWithBooleanArgument = (parameterCount == 10);
if (!constructorWithBooleanArgument && parameterCount != 9) {
throw new IllegalStateException("Expected TyrusEndpointWrapper constructor with 9 or 10 arguments");
}
registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", TyrusEndpointWrapper.class);
unRegisterMethod = TyrusWebSocketEngine.class.getDeclaredMethod("unregister", TyrusEndpointWrapper.class);
ReflectionUtils.makeAccessible(registerMethod);
}
catch (Exception ex) {
throw new IllegalStateException("No compatible Tyrus version found", ex);
}
}
private static Constructor<?> getEndpointConstructor() {
for (Constructor<?> current : TyrusEndpointWrapper.class.getConstructors()) {
Class<?>[] types = current.getParameterTypes();
if (Endpoint.class == types[0] && EndpointConfig.class == types[1]) {
return current;
}
}
throw new IllegalStateException("No compatible Tyrus version found");
}
private final ComponentProviderService componentProvider = ComponentProviderService.create();
@ -105,7 +139,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
// Shouldn't matter for processing but must be unique
String path = "/" + random.nextLong();
tyrusEndpoint = createTyrusEndpoint(endpoint, path, selectedProtocol, extensions, serverContainer, engine);
getEndpointHelper().register(engine, tyrusEndpoint);
register(engine, tyrusEndpoint);
HttpHeaders headers = request.getHeaders();
RequestContext requestContext = createRequestContext(servletRequest, path, headers);
@ -137,7 +171,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint);
endpointConfig.setSubprotocols(Collections.singletonList(protocol));
endpointConfig.setExtensions(extensions);
return getEndpointHelper().createdEndpoint(endpointConfig, this.componentProvider, container, engine);
return createEndpoint(endpointConfig, this.componentProvider, container, engine);
}
private RequestContext createRequestContext(HttpServletRequest request, String endpointPath, HttpHeaders headers) {
@ -146,7 +180,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
.requestURI(URI.create(endpointPath))
.userPrincipal(request.getUserPrincipal())
.secure(request.isSecure())
// .remoteAddr(request.getRemoteAddr()) # Not available in 1.3.5
.remoteAddr(request.getRemoteAddr())
.build();
for (String header : headers.keySet()) {
context.getHeaders().put(header, headers.get(header));
@ -157,7 +191,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
private void unregisterTyrusEndpoint(TyrusWebSocketEngine engine, Object tyrusEndpoint) {
if (tyrusEndpoint != null) {
try {
getEndpointHelper().unregister(engine, tyrusEndpoint);
unregister(engine, tyrusEndpoint);
}
catch (Throwable ex) {
// ignore
@ -165,147 +199,48 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
}
}
protected abstract TyrusEndpointHelper getEndpointHelper();
private Object createEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException {
DirectFieldAccessor accessor = new DirectFieldAccessor(engine);
Object sessionListener = accessor.getPropertyValue("sessionListener");
Object clusterContext = accessor.getPropertyValue("clusterContext");
try {
if (constructorWithBooleanArgument) {
// Tyrus 1.11+
return constructor.newInstance(registration.getEndpoint(), registration, provider, container,
"/", registration.getConfigurator(), sessionListener, clusterContext, null, Boolean.TRUE);
}
else {
return constructor.newInstance(registration.getEndpoint(), registration, provider, container,
"/", registration.getConfigurator(), sessionListener, clusterContext, null);
}
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to register " + registration, ex);
}
}
private void register(TyrusWebSocketEngine engine, Object endpoint) {
try {
registerMethod.invoke(engine, endpoint);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to register " + endpoint, ex);
}
}
private void unregister(TyrusWebSocketEngine engine, Object endpoint) {
try {
unRegisterMethod.invoke(engine, endpoint);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to unregister " + endpoint, ex);
}
}
protected abstract void handleSuccess(HttpServletRequest request, HttpServletResponse response,
UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException;
/**
* Helps with the creation, registration, and un-registration of endpoints.
*/
protected interface TyrusEndpointHelper {
Object createdEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException;
void register(TyrusWebSocketEngine engine, Object endpoint);
void unregister(TyrusWebSocketEngine engine, Object endpoint);
}
protected static class Tyrus17EndpointHelper implements TyrusEndpointHelper {
private static final Constructor<?> constructor;
private static boolean constructorWithBooleanArgument;
private static final Method registerMethod;
private static final Method unRegisterMethod;
static {
try {
constructor = getEndpointConstructor();
int parameterCount = constructor.getParameterTypes().length;
constructorWithBooleanArgument = (parameterCount == 10);
if (!constructorWithBooleanArgument && parameterCount != 9) {
throw new IllegalStateException("Expected TyrusEndpointWrapper constructor with 9 or 10 arguments");
}
registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", TyrusEndpointWrapper.class);
unRegisterMethod = TyrusWebSocketEngine.class.getDeclaredMethod("unregister", TyrusEndpointWrapper.class);
ReflectionUtils.makeAccessible(registerMethod);
}
catch (Exception ex) {
throw new IllegalStateException("No compatible Tyrus version found", ex);
}
}
private static Constructor<?> getEndpointConstructor() {
for (Constructor<?> current : TyrusEndpointWrapper.class.getConstructors()) {
Class<?>[] types = current.getParameterTypes();
if (Endpoint.class == types[0] && EndpointConfig.class == types[1]) {
return current;
}
}
throw new IllegalStateException("No compatible Tyrus version found");
}
@Override
public Object createdEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException {
DirectFieldAccessor accessor = new DirectFieldAccessor(engine);
Object sessionListener = accessor.getPropertyValue("sessionListener");
Object clusterContext = accessor.getPropertyValue("clusterContext");
try {
if (constructorWithBooleanArgument) {
// Tyrus 1.11+
return constructor.newInstance(registration.getEndpoint(), registration, provider, container,
"/", registration.getConfigurator(), sessionListener, clusterContext, null, Boolean.TRUE);
}
else {
return constructor.newInstance(registration.getEndpoint(), registration, provider, container,
"/", registration.getConfigurator(), sessionListener, clusterContext, null);
}
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to register " + registration, ex);
}
}
@Override
public void register(TyrusWebSocketEngine engine, Object endpoint) {
try {
registerMethod.invoke(engine, endpoint);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to register " + endpoint, ex);
}
}
@Override
public void unregister(TyrusWebSocketEngine engine, Object endpoint) {
try {
unRegisterMethod.invoke(engine, endpoint);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to unregister " + endpoint, ex);
}
}
}
protected static class Tyrus135EndpointHelper implements TyrusEndpointHelper {
private static final Method registerMethod;
static {
try {
registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", WebSocketApplication.class);
ReflectionUtils.makeAccessible(registerMethod);
}
catch (Exception ex) {
throw new IllegalStateException("No compatible Tyrus version found", ex);
}
}
@Override
public Object createdEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException {
TyrusEndpointWrapper endpointWrapper = new TyrusEndpointWrapper(registration.getEndpoint(),
registration, provider, container, "/", registration.getConfigurator());
return new TyrusEndpoint(endpointWrapper);
}
@Override
public void register(TyrusWebSocketEngine engine, Object endpoint) {
try {
registerMethod.invoke(engine, endpoint);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to register " + endpoint, ex);
}
}
@Override
public void unregister(TyrusWebSocketEngine engine, Object endpoint) {
engine.unregister((TyrusEndpoint) endpoint);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -43,22 +43,27 @@ import org.springframework.web.socket.server.HandshakeFailureException;
*/
public class GlassFishRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeStrategy {
private static final TyrusEndpointHelper endpointHelper = new Tyrus17EndpointHelper();
private static final Constructor<?> constructor;
private static final GlassFishServletWriterHelper servletWriterHelper = new GlassFishServletWriterHelper();
@Override
protected TyrusEndpointHelper getEndpointHelper() {
return endpointHelper;
static {
try {
ClassLoader classLoader = GlassFishRequestUpgradeStrategy.class.getClassLoader();
Class<?> type = classLoader.loadClass("org.glassfish.tyrus.servlet.TyrusServletWriter");
constructor = type.getDeclaredConstructor(TyrusHttpUpgradeHandler.class);
ReflectionUtils.makeAccessible(constructor);
}
catch (Exception ex) {
throw new IllegalStateException("No compatible Tyrus version found", ex);
}
}
@Override
protected void handleSuccess(HttpServletRequest request, HttpServletResponse response,
UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException {
TyrusHttpUpgradeHandler handler = request.upgrade(TyrusHttpUpgradeHandler.class);
Writer servletWriter = servletWriterHelper.newInstance(handler);
Writer servletWriter = newServletWriter(handler);
handler.preInit(upgradeInfo, servletWriter, request.getUserPrincipal() != null);
response.setStatus(upgradeResponse.getStatus());
@ -68,33 +73,12 @@ public class GlassFishRequestUpgradeStrategy extends AbstractTyrusRequestUpgrade
response.flushBuffer();
}
/**
* Helps to create and invoke {@code org.glassfish.tyrus.servlet.TyrusServletWriter}.
*/
private static class GlassFishServletWriterHelper {
private static final Constructor<?> constructor;
static {
try {
ClassLoader classLoader = GlassFishRequestUpgradeStrategy.class.getClassLoader();
Class<?> type = classLoader.loadClass("org.glassfish.tyrus.servlet.TyrusServletWriter");
constructor = type.getDeclaredConstructor(TyrusHttpUpgradeHandler.class);
ReflectionUtils.makeAccessible(constructor);
}
catch (Exception ex) {
throw new IllegalStateException("No compatible Tyrus version found", ex);
}
private Writer newServletWriter(TyrusHttpUpgradeHandler handler) {
try {
return (Writer) constructor.newInstance(handler);
}
private Writer newInstance(TyrusHttpUpgradeHandler handler) {
try {
return (Writer) constructor.newInstance(handler);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to instantiate TyrusServletWriter", ex);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to instantiate TyrusServletWriter", ex);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -17,66 +17,33 @@
package org.springframework.web.socket.server.standard;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
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;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.Extension;
import javax.websocket.server.ServerEndpointConfig;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.HttpUpgradeListener;
import io.undertow.servlet.api.InstanceFactory;
import io.undertow.servlet.api.InstanceHandle;
import io.undertow.servlet.websockets.ServletWebSocketHttpExchange;
import io.undertow.util.PathTemplate;
import io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.core.WebSocketVersion;
import io.undertow.websockets.core.protocol.Handshake;
import io.undertow.websockets.jsr.ConfiguredServerEndpoint;
import io.undertow.websockets.jsr.EncodingFactory;
import io.undertow.websockets.jsr.EndpointSessionHandler;
import io.undertow.websockets.jsr.ServerWebSocketContainer;
import io.undertow.websockets.jsr.annotated.AnnotatedEndpointFactory;
import io.undertow.websockets.jsr.handshake.HandshakeUtil;
import io.undertow.websockets.jsr.handshake.JsrHybi07Handshake;
import io.undertow.websockets.jsr.handshake.JsrHybi08Handshake;
import io.undertow.websockets.jsr.handshake.JsrHybi13Handshake;
import io.undertow.websockets.spi.WebSocketHttpExchange;
import org.xnio.StreamConnection;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.socket.server.HandshakeFailureException;
/**
* A WebSocket {@code RequestUpgradeStrategy} for WildFly and its underlying
* Undertow web server. Also compatible with embedded Undertow usage.
*
* <p>Designed for Undertow 1.3.5+ as of Spring Framework 4.3, with a fallback
* strategy for Undertow 1.0 to 1.3 - as included in WildFly 8.x, 9 and 10.
* <p>Requires Undertow 1.3.5+ as of Spring Framework 5.0.
*
* @author Rossen Stoyanchev
* @since 4.0.1
*/
public class UndertowRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
private static final boolean HAS_DO_UPGRADE = ClassUtils.hasMethod(ServerWebSocketContainer.class, "doUpgrade",
HttpServletRequest.class, HttpServletResponse.class, ServerEndpointConfig.class, Map.class);
private static final FallbackStrategy FALLBACK_STRATEGY = (HAS_DO_UPGRADE ? null : new FallbackStrategy());
private static final String[] VERSIONS = new String[] {
WebSocketVersion.V13.toHttpHeaderValue(),
WebSocketVersion.V08.toHttpHeaderValue(),
@ -94,32 +61,27 @@ public class UndertowRequestUpgradeStrategy extends AbstractStandardUpgradeStrat
String selectedProtocol, List<Extension> selectedExtensions, Endpoint endpoint)
throws HandshakeFailureException {
if (HAS_DO_UPGRADE) {
HttpServletRequest servletRequest = getHttpServletRequest(request);
HttpServletResponse servletResponse = getHttpServletResponse(response);
HttpServletRequest servletRequest = getHttpServletRequest(request);
HttpServletResponse servletResponse = getHttpServletResponse(response);
StringBuffer requestUrl = servletRequest.getRequestURL();
String path = servletRequest.getRequestURI(); // shouldn't matter
Map<String, String> pathParams = Collections.<String, String>emptyMap();
StringBuffer requestUrl = servletRequest.getRequestURL();
String path = servletRequest.getRequestURI(); // shouldn't matter
Map<String, String> pathParams = Collections.<String, String>emptyMap();
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint);
endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol));
endpointConfig.setExtensions(selectedExtensions);
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint);
endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol));
endpointConfig.setExtensions(selectedExtensions);
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);
}
try {
getContainer(servletRequest).doUpgrade(servletRequest, servletResponse, endpointConfig, pathParams);
}
else {
FALLBACK_STRATEGY.upgradeInternal(request, response, selectedProtocol, selectedExtensions, endpoint);
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);
}
}
@ -127,197 +89,4 @@ public class UndertowRequestUpgradeStrategy extends AbstractStandardUpgradeStrat
return (ServerWebSocketContainer) super.getContainer(request);
}
/**
* 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 static final Constructor<ServletWebSocketHttpExchange> exchangeConstructor;
private static final boolean exchangeConstructorWithPeerConnections;
private static final Constructor<ConfiguredServerEndpoint> endpointConstructor;
private static final boolean endpointConstructorWithEndpointFactory;
private static final Method getBufferPoolMethod;
private static final Method createChannelMethod;
static {
try {
Class<ServletWebSocketHttpExchange> exchangeType = ServletWebSocketHttpExchange.class;
Class<?>[] exchangeParamTypes =
new Class<?>[] {HttpServletRequest.class, HttpServletResponse.class, Set.class};
Constructor<ServletWebSocketHttpExchange> 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<ConfiguredServerEndpoint> endpointType = ConfiguredServerEndpoint.class;
Class<?>[] endpointParamTypes = new Class<?>[] {ServerEndpointConfig.class, InstanceFactory.class,
PathTemplate.class, EncodingFactory.class, AnnotatedEndpointFactory.class};
Constructor<ConfiguredServerEndpoint> 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<WebSocketChannel> peerConnections;
public FallbackStrategy() {
if (exchangeConstructorWithPeerConnections) {
this.peerConnections = Collections.newSetFromMap(new ConcurrentHashMap<WebSocketChannel, Boolean>());
}
else {
this.peerConnections = null;
}
}
@Override
public String[] getSupportedVersions() {
return VERSIONS;
}
@Override
protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
String selectedProtocol, List<Extension> 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.<String, String>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 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);
}
});
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<Extension> 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.<Class<?>, List<InstanceFactory<? extends Encoder>>>emptyMap(),
Collections.<Class<?>, List<InstanceFactory<? extends Decoder>>>emptyMap(),
Collections.<Class<?>, List<InstanceFactory<? extends Encoder>>>emptyMap(),
Collections.<Class<?>, List<InstanceFactory<? extends Decoder>>>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<Endpoint> {
private final Endpoint endpoint;
public EndpointInstanceFactory(Endpoint endpoint) {
this.endpoint = endpoint;
}
@Override
public InstanceHandle<Endpoint> createInstance() throws InstantiationException {
return new InstanceHandle<Endpoint>() {
@Override
public Endpoint getInstance() {
return endpoint;
}
@Override
public void release() {
}
};
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -46,33 +46,22 @@ import org.springframework.web.socket.server.HandshakeFailureException;
* Supports 12.1.3 as well as 12.2.1, as of Spring Framework 4.2.3.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.1
*/
public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeStrategy {
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 final Connection.CloseListener noOpCloseListener = new Connection.CloseListener() {
@Override
public void close(CloseReason reason) {
}
};
@Override
protected TyrusEndpointHelper getEndpointHelper() {
return endpointHelper;
}
@Override
protected void handleSuccess(HttpServletRequest request, HttpServletResponse response,
UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException {
@ -94,7 +83,7 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
response.flushBuffer();
boolean isProtected = request.getUserPrincipal() != null;
Writer servletWriter = servletWriterHelper.newInstance(response, webSocket, isProtected);
Writer servletWriter = servletWriterHelper.newInstance(webSocket, isProtected);
Connection connection = upgradeInfo.createConnection(servletWriter, noOpCloseListener);
new BeanWrapperImpl(webSocket).setPropertyValue("connection", connection);
new BeanWrapperImpl(servletWriter).setPropertyValue("connection", connection);
@ -102,20 +91,6 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
}
private static boolean isWebLogic1213() {
try {
type("weblogic.websocket.tyrus.TyrusMuxableWebSocket").getDeclaredConstructor(
type("weblogic.servlet.internal.MuxableSocketHTTP"));
return true;
}
catch (NoSuchMethodException ex) {
return false;
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("No compatible WebSocket version found", ex);
}
}
private static Class<?> type(String className) throws ClassNotFoundException {
return WebLogicRequestUpgradeStrategy.class.getClassLoader().loadClass(className);
}
@ -153,17 +128,11 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
try {
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();
}
constructor = type.getDeclaredConstructor(
type("weblogic.servlet.internal.MuxableSocketHTTP"),
type("weblogic.websocket.tyrus.CoherenceServletFilterService"),
type("weblogic.servlet.spi.SubjectHandle"));
subjectHelper = new SubjectHelper();
upgradeMethod = type.getMethod("upgrade", type("weblogic.socket.MuxableSocket"), ServletContext.class);
readEventMethod = type.getMethod("registerForReadEvent");
@ -175,8 +144,7 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
private Object newInstance(HttpServletRequest request, Object httpSocket) {
try {
Object[] args = (WLS_12_1_3 ? new Object[] {httpSocket} :
new Object[] {httpSocket, null, subjectHelper.getSubject(request)});
Object[] args = new Object[] {httpSocket, null, subjectHelper.getSubject(request)};
return constructor.newInstance(args);
}
catch (Exception ex) {
@ -263,13 +231,7 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
Class<?> writerType = type("weblogic.websocket.tyrus.TyrusServletWriter");
Class<?> listenerType = type("weblogic.websocket.tyrus.TyrusServletWriter$CloseListener");
Class<?> webSocketType = TyrusMuxableWebSocketHelper.type;
Class<HttpServletResponse> responseType = HttpServletResponse.class;
Class<?>[] argTypes = (WLS_12_1_3 ?
new Class<?>[] {webSocketType, responseType, listenerType, boolean.class} :
new Class<?>[] {webSocketType, listenerType, boolean.class});
constructor = writerType.getDeclaredConstructor(argTypes);
constructor = writerType.getDeclaredConstructor(webSocketType, listenerType, boolean.class);
ReflectionUtils.makeAccessible(constructor);
}
catch (Exception ex) {
@ -277,13 +239,9 @@ public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeS
}
}
private Writer newInstance(HttpServletResponse response, Object webSocket, boolean isProtected) {
private Writer newInstance(Object webSocket, boolean isProtected) {
try {
Object[] args = (WLS_12_1_3 ?
new Object[] {webSocket, response, null, isProtected} :
new Object[] {webSocket, null, isProtected});
return (Writer) constructor.newInstance(args);
return (Writer) constructor.newInstance(webSocket, null, isProtected);
}
catch (Exception ex) {
throw new HandshakeFailureException("Failed to create TyrusServletWriter", ex);