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
This commit is contained in:
parent
afb56681ac
commit
88d8dff3ac
|
@ -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);
|
||||
ClassLoader classLoader = GlassFishRequestUpgradeStrategy.class.getClassLoader();
|
||||
Class<?> type = classLoader.loadClass("org.glassfish.tyrus.servlet.TyrusServletWriter");
|
||||
tyrusServletWriterConstructor = type.getDeclaredConstructor(TyrusHttpUpgradeHandler.class);
|
||||
ReflectionUtils.makeAccessible(tyrusServletWriterConstructor);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
tyrusEndpoint = createTyrusEndpoint(endpoint, subProtocol, extensions, serverContainer);
|
||||
endpointRegistrationMethod.invoke(engine, tyrusEndpoint);
|
||||
|
||||
try {
|
||||
performUpgrade(servletRequest, servletResponse, request.getHeaders(), webSocketApplication);
|
||||
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()));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new HandshakeFailureException(
|
||||
"Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex);
|
||||
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 (Exception ex) {
|
||||
throw new HandshakeFailureException("Error during handshake: " + request.getURI(), ex);
|
||||
}
|
||||
finally {
|
||||
webSocketEngine.unregister(webSocketApplication);
|
||||
if (tyrusEndpoint != null) {
|
||||
engine.unregister(tyrusEndpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
private Writer createTyrusServletWriter(TyrusHttpUpgradeHandler handler) {
|
||||
try {
|
||||
return (Connection) tyrusConnectionConstructor.newInstance(handler, response);
|
||||
return (Writer) tyrusServletWriterConstructor.newInstance(handler);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to create GlassFish connection", ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint) {
|
||||
try {
|
||||
return (WebSocketApplication) tyrusEndpointConstructor.newInstance(endpoint);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to create GlassFish endpoint", ex);
|
||||
throw new HandshakeFailureException("Failed to instantiate TyrusServletWriter", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue