Skip to content

Commit f1e929f

Browse files
mikesir87rstoyanchev
authored andcommitted
Added websocket upgrade support for GlassFish 4.0
Commit 2397b21 changed websocket support to use GlassFish 4.0.1 nightlies, but broke support for 4.0. In GlassFish 4.0.1, the package that TyrusEndpoint is located in changed. This commit provides an abstract handler that does all required GlassFish setup, but delegates to version specific upgrade handlers to create the final TyrusEndpoint. GlassFish 4.0 handler uses reflection to create its endpoint to prevent dependency issues of depending on different versions of tyrus-websocket-core and tyrus-container-servlet
1 parent c06ea3b commit f1e929f

File tree

4 files changed

+232
-131
lines changed

4 files changed

+232
-131
lines changed

spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
6262
private static final boolean glassFishWsPresent = ClassUtils.isPresent(
6363
"org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", HandshakeHandler.class.getClassLoader());
6464

65+
private static final boolean glassFish40WsPresent = ClassUtils.isPresent(
66+
"org.glassfish.tyrus.server.TyrusEndpoint", HandshakeHandler.class.getClassLoader());
67+
6568

6669
private final RequestUpgradeStrategy requestUpgradeStrategy;
6770

@@ -88,7 +91,12 @@ else if (jettyWsPresent) {
8891
className = "org.springframework.web.socket.server.support.JettyRequestUpgradeStrategy";
8992
}
9093
else if (glassFishWsPresent) {
91-
className = "org.springframework.web.socket.server.support.GlassFishRequestUpgradeStrategy";
94+
if (glassFish40WsPresent) {
95+
className = "org.springframework.web.socket.server.support.GlassFish40RequestUpgradeStrategy";
96+
}
97+
else {
98+
className = "org.springframework.web.socket.server.support.GlassFishRequestUpgradeStrategy";
99+
}
92100
}
93101
else {
94102
throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName());
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.socket.server.support;
18+
19+
import java.io.IOException;
20+
import java.lang.reflect.Constructor;
21+
import java.net.URI;
22+
import java.util.Arrays;
23+
import java.util.Random;
24+
25+
import javax.servlet.ServletException;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletResponse;
28+
import javax.websocket.DeploymentException;
29+
import javax.websocket.Endpoint;
30+
31+
import org.glassfish.tyrus.core.ComponentProviderService;
32+
import org.glassfish.tyrus.core.EndpointWrapper;
33+
import org.glassfish.tyrus.core.ErrorCollector;
34+
import org.glassfish.tyrus.core.RequestContext;
35+
import org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler;
36+
import org.glassfish.tyrus.websockets.Connection;
37+
import org.glassfish.tyrus.websockets.Version;
38+
import org.glassfish.tyrus.websockets.WebSocketApplication;
39+
import org.glassfish.tyrus.websockets.WebSocketEngine;
40+
import org.glassfish.tyrus.websockets.WebSocketEngine.WebSocketHolderListener;
41+
import org.springframework.http.HttpHeaders;
42+
import org.springframework.http.server.ServerHttpRequest;
43+
import org.springframework.http.server.ServerHttpResponse;
44+
import org.springframework.http.server.ServletServerHttpRequest;
45+
import org.springframework.http.server.ServletServerHttpResponse;
46+
import org.springframework.util.Assert;
47+
import org.springframework.util.ClassUtils;
48+
import org.springframework.util.ReflectionUtils;
49+
import org.springframework.util.StringUtils;
50+
import org.springframework.web.socket.server.HandshakeFailureException;
51+
import org.springframework.web.socket.server.endpoint.ServerEndpointRegistration;
52+
import org.springframework.web.socket.server.endpoint.ServletServerContainerFactoryBean;
53+
54+
55+
/**
56+
* GlassFish support for upgrading a request during a WebSocket handshake. To modify
57+
* properties of the underlying {@link javax.websocket.server.ServerContainer} you can use
58+
* {@link ServletServerContainerFactoryBean} in XML configuration or if using Java
59+
* configuration, access the container instance through the
60+
* "javax.websocket.server.ServerContainer" ServletContext attribute.
61+
*
62+
* @author Rossen Stoyanchev
63+
* @since 4.0
64+
*/
65+
public abstract class AbstractGlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
66+
67+
private final static Random random = new Random();
68+
69+
@Override
70+
public String[] getSupportedVersions() {
71+
return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions());
72+
}
73+
74+
@Override
75+
public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
76+
String selectedProtocol, Endpoint endpoint) throws HandshakeFailureException {
77+
78+
Assert.isTrue(request instanceof ServletServerHttpRequest);
79+
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
80+
81+
Assert.isTrue(response instanceof ServletServerHttpResponse);
82+
HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse();
83+
84+
WebSocketApplication webSocketApplication = createTyrusEndpoint(servletRequest, endpoint, selectedProtocol);
85+
WebSocketEngine webSocketEngine = WebSocketEngine.getEngine();
86+
87+
try {
88+
webSocketEngine.register(webSocketApplication);
89+
}
90+
catch (DeploymentException ex) {
91+
throw new HandshakeFailureException("Failed to configure endpoint in GlassFish", ex);
92+
}
93+
94+
try {
95+
performUpgrade(servletRequest, servletResponse, request.getHeaders(), webSocketApplication);
96+
}
97+
catch (IOException ex) {
98+
throw new HandshakeFailureException(
99+
"Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex);
100+
}
101+
finally {
102+
webSocketEngine.unregister(webSocketApplication);
103+
}
104+
}
105+
106+
private boolean performUpgrade(HttpServletRequest request, HttpServletResponse response,
107+
HttpHeaders headers, WebSocketApplication wsApp) throws IOException {
108+
109+
final TyrusHttpUpgradeHandler upgradeHandler;
110+
try {
111+
upgradeHandler = request.upgrade(TyrusHttpUpgradeHandler.class);
112+
}
113+
catch (ServletException e) {
114+
throw new HandshakeFailureException("Unable to create UpgardeHandler", e);
115+
}
116+
117+
Connection connection = createConnection(upgradeHandler, response);
118+
119+
RequestContext wsRequest = RequestContext.Builder.create()
120+
.requestURI(URI.create(wsApp.getPath())).requestPath(wsApp.getPath())
121+
.connection(connection).secure(request.isSecure()).build();
122+
123+
for (String header : headers.keySet()) {
124+
wsRequest.getHeaders().put(header, headers.get(header));
125+
}
126+
127+
return WebSocketEngine.getEngine().upgrade(connection, wsRequest, new WebSocketHolderListener() {
128+
@Override
129+
public void onWebSocketHolder(WebSocketEngine.WebSocketHolder webSocketHolder) {
130+
upgradeHandler.setWebSocketHolder(webSocketHolder);
131+
}
132+
});
133+
}
134+
135+
private WebSocketApplication createTyrusEndpoint(HttpServletRequest request,
136+
Endpoint endpoint, String selectedProtocol) {
137+
138+
// shouldn't matter for processing but must be unique
139+
String endpointPath = "/" + random.nextLong();
140+
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint);
141+
endpointConfig.setSubprotocols(Arrays.asList(selectedProtocol));
142+
return createTyrusEndpoint(new EndpointWrapper(endpoint, endpointConfig,
143+
ComponentProviderService.create(), null, "/", new ErrorCollector(),
144+
endpointConfig.getConfigurator()));
145+
}
146+
147+
/**
148+
* Create the actual TyrusEndpoint
149+
* @param endpoint The WebSocket endpoint
150+
* @return The configured WebSocketApplication, most likely {@code TyrusEndpoint}
151+
*/
152+
abstract protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint);
153+
154+
private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) {
155+
try {
156+
String name = "org.glassfish.tyrus.servlet.ConnectionImpl";
157+
Class<?> clazz = ClassUtils.forName(name, GlassFishRequestUpgradeStrategy.class.getClassLoader());
158+
Constructor<?> constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class);
159+
ReflectionUtils.makeAccessible(constructor);
160+
return (Connection) constructor.newInstance(handler, response);
161+
}
162+
catch (Exception ex) {
163+
throw new IllegalStateException("Failed to instantiate GlassFish connection", ex);
164+
}
165+
}
166+
167+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.socket.server.support;
18+
19+
import java.lang.reflect.Constructor;
20+
21+
import org.glassfish.tyrus.core.EndpointWrapper;
22+
import org.glassfish.tyrus.spi.SPIEndpoint;
23+
import org.glassfish.tyrus.websockets.WebSocketApplication;
24+
import org.springframework.util.ClassUtils;
25+
26+
27+
/**
28+
* Extension of the {@link AbstractGlassFishRequestUpgradeStrategy} that provides support
29+
* for only GlassFish 4.0.
30+
*
31+
* @author Rossen Stoyanchev
32+
* @author Michael Irwin
33+
* @since 4.0
34+
*/
35+
public class GlassFish40RequestUpgradeStrategy extends AbstractGlassFishRequestUpgradeStrategy {
36+
37+
38+
protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint) {
39+
try {
40+
String name = "org.glassfish.tyrus.server.TyrusEndpoint";
41+
Class<?> clazz = ClassUtils.forName(name, this.getClass().getClassLoader());
42+
Constructor<?> constructor = clazz.getConstructor(SPIEndpoint.class);
43+
return (WebSocketApplication) constructor.newInstance(endpoint);
44+
}
45+
catch (ReflectiveOperationException exception) {
46+
throw new RuntimeException(exception);
47+
}
48+
}
49+
50+
}

spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java

Lines changed: 6 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -16,148 +16,24 @@
1616

1717
package org.springframework.web.socket.server.support;
1818

19-
import java.io.IOException;
20-
import java.lang.reflect.Constructor;
21-
import java.net.URI;
22-
import java.util.Arrays;
23-
import java.util.Random;
24-
25-
import javax.servlet.ServletException;
26-
import javax.servlet.http.HttpServletRequest;
27-
import javax.servlet.http.HttpServletResponse;
28-
import javax.websocket.DeploymentException;
29-
import javax.websocket.Endpoint;
30-
31-
import org.glassfish.tyrus.core.ComponentProviderService;
3219
import org.glassfish.tyrus.core.EndpointWrapper;
33-
import org.glassfish.tyrus.core.ErrorCollector;
34-
import org.glassfish.tyrus.core.RequestContext;
3520
import org.glassfish.tyrus.core.TyrusEndpoint;
36-
import org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler;
37-
import org.glassfish.tyrus.websockets.Connection;
38-
import org.glassfish.tyrus.websockets.Version;
3921
import org.glassfish.tyrus.websockets.WebSocketApplication;
40-
import org.glassfish.tyrus.websockets.WebSocketEngine;
41-
import org.glassfish.tyrus.websockets.WebSocketEngine.WebSocketHolderListener;
42-
import org.springframework.http.HttpHeaders;
43-
import org.springframework.http.server.ServerHttpRequest;
44-
import org.springframework.http.server.ServerHttpResponse;
45-
import org.springframework.http.server.ServletServerHttpRequest;
46-
import org.springframework.http.server.ServletServerHttpResponse;
47-
import org.springframework.util.Assert;
48-
import org.springframework.util.ClassUtils;
49-
import org.springframework.util.ReflectionUtils;
50-
import org.springframework.util.StringUtils;
51-
import org.springframework.web.socket.server.HandshakeFailureException;
52-
import org.springframework.web.socket.server.endpoint.ServerEndpointRegistration;
53-
import org.springframework.web.socket.server.endpoint.ServletServerContainerFactoryBean;
5422

5523

5624
/**
57-
* GlassFish support for upgrading a request during a WebSocket handshake. To modify
58-
* properties of the underlying {@link javax.websocket.server.ServerContainer} you can use
59-
* {@link ServletServerContainerFactoryBean} in XML configuration or if using Java
60-
* configuration, access the container instance through the
61-
* "javax.websocket.server.ServerContainer" ServletContext attribute.
25+
* Extension of the {@link AbstractGlassFishRequestUpgradeStrategy} that provides support
26+
* for GlassFish 4.0.1 and beyond.
6227
*
6328
* @author Rossen Stoyanchev
29+
* @author Michael Irwin
6430
* @since 4.0
6531
*/
66-
public class GlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
67-
68-
private final static Random random = new Random();
69-
70-
71-
@Override
72-
public String[] getSupportedVersions() {
73-
return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions());
74-
}
32+
public class GlassFishRequestUpgradeStrategy extends AbstractGlassFishRequestUpgradeStrategy {
7533

7634
@Override
77-
public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
78-
String selectedProtocol, Endpoint endpoint) throws HandshakeFailureException {
79-
80-
Assert.isTrue(request instanceof ServletServerHttpRequest);
81-
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
82-
83-
Assert.isTrue(response instanceof ServletServerHttpResponse);
84-
HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse();
85-
86-
WebSocketApplication webSocketApplication = createTyrusEndpoint(servletRequest, endpoint, selectedProtocol);
87-
WebSocketEngine webSocketEngine = WebSocketEngine.getEngine();
88-
89-
try {
90-
webSocketEngine.register(webSocketApplication);
91-
}
92-
catch (DeploymentException ex) {
93-
throw new HandshakeFailureException("Failed to configure endpoint in GlassFish", ex);
94-
}
95-
96-
try {
97-
performUpgrade(servletRequest, servletResponse, request.getHeaders(), webSocketApplication);
98-
}
99-
catch (IOException ex) {
100-
throw new HandshakeFailureException(
101-
"Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex);
102-
}
103-
finally {
104-
webSocketEngine.unregister(webSocketApplication);
105-
}
106-
}
107-
108-
private boolean performUpgrade(HttpServletRequest request, HttpServletResponse response,
109-
HttpHeaders headers, WebSocketApplication wsApp) throws IOException {
110-
111-
final TyrusHttpUpgradeHandler upgradeHandler;
112-
try {
113-
upgradeHandler = request.upgrade(TyrusHttpUpgradeHandler.class);
114-
}
115-
catch (ServletException e) {
116-
throw new HandshakeFailureException("Unable to create UpgardeHandler", e);
117-
}
118-
119-
Connection connection = createConnection(upgradeHandler, response);
120-
121-
RequestContext wsRequest = RequestContext.Builder.create()
122-
.requestURI(URI.create(wsApp.getPath())).requestPath(wsApp.getPath())
123-
.connection(connection).secure(request.isSecure()).build();
124-
125-
for (String header : headers.keySet()) {
126-
wsRequest.getHeaders().put(header, headers.get(header));
127-
}
128-
129-
return WebSocketEngine.getEngine().upgrade(connection, wsRequest, new WebSocketHolderListener() {
130-
@Override
131-
public void onWebSocketHolder(WebSocketEngine.WebSocketHolder webSocketHolder) {
132-
upgradeHandler.setWebSocketHolder(webSocketHolder);
133-
}
134-
});
135-
}
136-
137-
private WebSocketApplication createTyrusEndpoint(HttpServletRequest request,
138-
Endpoint endpoint, String selectedProtocol) {
139-
140-
// shouldn't matter for processing but must be unique
141-
String endpointPath = "/" + random.nextLong();
142-
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint);
143-
endpointConfig.setSubprotocols(Arrays.asList(selectedProtocol));
144-
145-
return new TyrusEndpoint(new EndpointWrapper(endpoint, endpointConfig,
146-
ComponentProviderService.create(), null, "/", new ErrorCollector(),
147-
endpointConfig.getConfigurator()));
148-
}
149-
150-
private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) {
151-
try {
152-
String name = "org.glassfish.tyrus.servlet.ConnectionImpl";
153-
Class<?> clazz = ClassUtils.forName(name, GlassFishRequestUpgradeStrategy.class.getClassLoader());
154-
Constructor<?> constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class);
155-
ReflectionUtils.makeAccessible(constructor);
156-
return (Connection) constructor.newInstance(handler, response);
157-
}
158-
catch (Exception ex) {
159-
throw new IllegalStateException("Failed to instantiate GlassFish connection", ex);
160-
}
35+
protected WebSocketApplication createTyrusEndpoint(EndpointWrapper endpoint) {
36+
return new TyrusEndpoint(endpoint);
16137
}
16238

16339
}

0 commit comments

Comments
 (0)