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
master
Rossen Stoyanchev 10 years ago
parent afb56681ac
commit 88d8dff3ac
  1. 6
      build.gradle
  2. 199
      spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java

@ -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"
}

@ -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<Extension> selectedExtensions,
Endpoint endpoint) throws HandshakeFailureException {
String subProtocol, List<Extension> 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<String, List<String>> 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<Extension> selectedExtensions) {
private TyrusEndpointWrapper createTyrusEndpoint(Endpoint endpoint, String protocol,
List<Extension> 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);
}
}

Loading…
Cancel
Save