Merge pull request #364 from mikesir87/ws-glassfish4-support
* mikesir87-ws-glassfish4-support: Added websocket upgrade support for GlassFish 4.0master
commit
f3611e767e
4 changed files with 232 additions and 131 deletions
@ -0,0 +1,167 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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.web.socket.server.support; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Constructor; |
||||||
|
import java.net.URI; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Random; |
||||||
|
|
||||||
|
import javax.servlet.ServletException; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
import javax.websocket.DeploymentException; |
||||||
|
import javax.websocket.Endpoint; |
||||||
|
|
||||||
|
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.servlet.TyrusHttpUpgradeHandler; |
||||||
|
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.websockets.WebSocketEngine.WebSocketHolderListener; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.server.ServerHttpRequest; |
||||||
|
import org.springframework.http.server.ServerHttpResponse; |
||||||
|
import org.springframework.http.server.ServletServerHttpRequest; |
||||||
|
import org.springframework.http.server.ServletServerHttpResponse; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
import org.springframework.util.ReflectionUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
import org.springframework.web.socket.server.HandshakeFailureException; |
||||||
|
import org.springframework.web.socket.server.endpoint.ServerEndpointRegistration; |
||||||
|
import org.springframework.web.socket.server.endpoint.ServletServerContainerFactoryBean; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* GlassFish support for upgrading a request during a WebSocket handshake. To modify |
||||||
|
* properties of the underlying {@link javax.websocket.server.ServerContainer} you can use |
||||||
|
* {@link ServletServerContainerFactoryBean} in XML configuration or if using Java |
||||||
|
* configuration, access the container instance through the |
||||||
|
* "javax.websocket.server.ServerContainer" ServletContext attribute. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public abstract class AbstractGlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { |
||||||
|
|
||||||
|
private final static Random random = new Random(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public String[] getSupportedVersions() { |
||||||
|
return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, |
||||||
|
String selectedProtocol, Endpoint endpoint) throws HandshakeFailureException { |
||||||
|
|
||||||
|
Assert.isTrue(request instanceof ServletServerHttpRequest); |
||||||
|
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); |
||||||
|
|
||||||
|
Assert.isTrue(response instanceof ServletServerHttpResponse); |
||||||
|
HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); |
||||||
|
|
||||||
|
WebSocketApplication webSocketApplication = createTyrusEndpoint(servletRequest, endpoint, selectedProtocol); |
||||||
|
WebSocketEngine webSocketEngine = WebSocketEngine.getEngine(); |
||||||
|
|
||||||
|
try { |
||||||
|
webSocketEngine.register(webSocketApplication); |
||||||
|
} |
||||||
|
catch (DeploymentException ex) { |
||||||
|
throw new HandshakeFailureException("Failed to configure endpoint in GlassFish", ex); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
performUpgrade(servletRequest, servletResponse, request.getHeaders(), webSocketApplication); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new HandshakeFailureException( |
||||||
|
"Response update failed during upgrade to WebSocket, uri=" + 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 e) { |
||||||
|
throw new HandshakeFailureException("Unable to create UpgardeHandler", e); |
||||||
|
} |
||||||
|
|
||||||
|
Connection connection = createConnection(upgradeHandler, response); |
||||||
|
|
||||||
|
RequestContext wsRequest = RequestContext.Builder.create() |
||||||
|
.requestURI(URI.create(wsApp.getPath())).requestPath(wsApp.getPath()) |
||||||
|
.connection(connection).secure(request.isSecure()).build(); |
||||||
|
|
||||||
|
for (String header : headers.keySet()) { |
||||||
|
wsRequest.getHeaders().put(header, headers.get(header)); |
||||||
|
} |
||||||
|
|
||||||
|
return WebSocketEngine.getEngine().upgrade(connection, wsRequest, new WebSocketHolderListener() { |
||||||
|
@Override |
||||||
|
public void onWebSocketHolder(WebSocketEngine.WebSocketHolder webSocketHolder) { |
||||||
|
upgradeHandler.setWebSocketHolder(webSocketHolder); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private WebSocketApplication createTyrusEndpoint(HttpServletRequest request, |
||||||
|
Endpoint endpoint, String selectedProtocol) { |
||||||
|
|
||||||
|
// shouldn't matter for processing but must be unique
|
||||||
|
String endpointPath = "/" + random.nextLong(); |
||||||
|
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint); |
||||||
|
endpointConfig.setSubprotocols(Arrays.asList(selectedProtocol)); |
||||||
|
return createTyrusEndpoint(new EndpointWrapper(endpoint, endpointConfig, |
||||||
|
ComponentProviderService.create(), null, "/", new ErrorCollector(), |
||||||
|
endpointConfig.getConfigurator())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create the actual TyrusEndpoint |
||||||
|
* @param endpoint The WebSocket endpoint |
||||||
|
* @return The configured WebSocketApplication, most likely {@code TyrusEndpoint} |
||||||
|
*/ |
||||||
|
abstract protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint); |
||||||
|
|
||||||
|
private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) { |
||||||
|
try { |
||||||
|
String name = "org.glassfish.tyrus.servlet.ConnectionImpl"; |
||||||
|
Class<?> clazz = ClassUtils.forName(name, GlassFishRequestUpgradeStrategy.class.getClassLoader()); |
||||||
|
Constructor<?> constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class); |
||||||
|
ReflectionUtils.makeAccessible(constructor); |
||||||
|
return (Connection) constructor.newInstance(handler, response); |
||||||
|
} |
||||||
|
catch (Exception ex) { |
||||||
|
throw new IllegalStateException("Failed to instantiate GlassFish connection", ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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.web.socket.server.support; |
||||||
|
|
||||||
|
import java.lang.reflect.Constructor; |
||||||
|
|
||||||
|
import org.glassfish.tyrus.core.EndpointWrapper; |
||||||
|
import org.glassfish.tyrus.spi.SPIEndpoint; |
||||||
|
import org.glassfish.tyrus.websockets.WebSocketApplication; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Extension of the {@link AbstractGlassFishRequestUpgradeStrategy} that provides support |
||||||
|
* for only GlassFish 4.0. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @author Michael Irwin |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public class GlassFish40RequestUpgradeStrategy extends AbstractGlassFishRequestUpgradeStrategy { |
||||||
|
|
||||||
|
|
||||||
|
protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint) { |
||||||
|
try { |
||||||
|
String name = "org.glassfish.tyrus.server.TyrusEndpoint"; |
||||||
|
Class<?> clazz = ClassUtils.forName(name, this.getClass().getClassLoader()); |
||||||
|
Constructor<?> constructor = clazz.getConstructor(SPIEndpoint.class); |
||||||
|
return (WebSocketApplication) constructor.newInstance(endpoint); |
||||||
|
} |
||||||
|
catch (ReflectiveOperationException exception) { |
||||||
|
throw new RuntimeException(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue