Skip to content

Commit c89325b

Browse files
committed
Add WebLogicRequestUpgradeStrategy
This change creates an AbstractTyrusRequestUpgradeStrategy shared between the WebLogic and GlassFish sub-classes. The version of Tyrus is lowered to 1.3.5 to match the version used in WebLogic (12.1.3) and that in turn requires a little extra effort in the base AbstractTyrusRequestUpgradeStrategy to make up for changes that have taken place from Tyrus 1.3.5 to 1.7. Issue: SPR-11293
1 parent 0ad5de3 commit c89325b

File tree

5 files changed

+544
-157
lines changed

5 files changed

+544
-157
lines changed

build.gradle

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,10 +684,10 @@ project("spring-websocket") {
684684
exclude group: "org.apache.tomcat", module: "tomcat-websocket-api"
685685
exclude group: "org.apache.tomcat", module: "tomcat-servlet-api"
686686
}
687-
optional("org.glassfish.tyrus:tyrus-spi:1.7")
688-
optional("org.glassfish.tyrus:tyrus-core:1.7")
689-
optional("org.glassfish.tyrus:tyrus-server:1.7")
690-
optional("org.glassfish.tyrus:tyrus-container-servlet:1.7")
687+
optional("org.glassfish.tyrus:tyrus-spi:1.3.5")
688+
optional("org.glassfish.tyrus:tyrus-core:1.3.5")
689+
optional("org.glassfish.tyrus:tyrus-server:1.3.5")
690+
optional("org.glassfish.tyrus:tyrus-container-servlet:1.3.5")
691691
optional("org.eclipse.jetty:jetty-webapp:${jettyVersion}") {
692692
exclude group: "javax.servlet", module: "javax.servlet"
693693
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/*
2+
* Copyright 2002-2014 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.standard;
18+
19+
import org.glassfish.tyrus.core.ComponentProviderService;
20+
import org.glassfish.tyrus.core.RequestContext;
21+
import org.glassfish.tyrus.core.TyrusEndpoint;
22+
import org.glassfish.tyrus.core.TyrusEndpointWrapper;
23+
import org.glassfish.tyrus.core.TyrusUpgradeResponse;
24+
import org.glassfish.tyrus.core.TyrusWebSocketEngine;
25+
import org.glassfish.tyrus.core.Version;
26+
import org.glassfish.tyrus.core.WebSocketApplication;
27+
import org.glassfish.tyrus.server.TyrusServerContainer;
28+
import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo;
29+
import org.springframework.beans.DirectFieldAccessor;
30+
import org.springframework.http.HttpHeaders;
31+
import org.springframework.http.server.ServerHttpRequest;
32+
import org.springframework.http.server.ServerHttpResponse;
33+
import org.springframework.util.ReflectionUtils;
34+
import org.springframework.util.StringUtils;
35+
import org.springframework.web.socket.WebSocketExtension;
36+
import org.springframework.web.socket.server.HandshakeFailureException;
37+
38+
import javax.servlet.ServletException;
39+
import javax.servlet.http.HttpServletRequest;
40+
import javax.servlet.http.HttpServletResponse;
41+
import javax.websocket.DeploymentException;
42+
import javax.websocket.Endpoint;
43+
import javax.websocket.EndpointConfig;
44+
import javax.websocket.Extension;
45+
import javax.websocket.WebSocketContainer;
46+
import java.io.IOException;
47+
import java.lang.reflect.Constructor;
48+
import java.lang.reflect.Method;
49+
import java.net.URI;
50+
import java.util.ArrayList;
51+
import java.util.Arrays;
52+
import java.util.List;
53+
import java.util.Random;
54+
55+
/**
56+
* An base class for WebSocket servers using Tyrus.
57+
*
58+
* <p>Works with Tyrus 1.3.5 (WebLogic 12.1.3) and Tyrus 1.7 (GlassFish 4.0.1).
59+
*
60+
* @author Rossen Stoyanchev
61+
* @since 4.1
62+
* @see <a href="https://tyrus.java.net/">Project Tyrus</a>
63+
*/
64+
public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
65+
66+
private static final Random random = new Random();
67+
68+
69+
private final ComponentProviderService componentProvider = ComponentProviderService.create();
70+
71+
72+
@Override
73+
public String[] getSupportedVersions() {
74+
return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions());
75+
}
76+
77+
protected List<WebSocketExtension> getInstalledExtensions(WebSocketContainer container) {
78+
try {
79+
return super.getInstalledExtensions(container);
80+
}
81+
catch (UnsupportedOperationException e) {
82+
return new ArrayList<WebSocketExtension>();
83+
}
84+
}
85+
86+
protected abstract TyrusEndpointHelper getEndpointHelper();
87+
88+
89+
@Override
90+
public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
91+
String subProtocol, List<Extension> extensions, Endpoint endpoint) throws HandshakeFailureException {
92+
93+
HttpServletRequest servletRequest = getHttpServletRequest(request);
94+
HttpServletResponse servletResponse = getHttpServletResponse(response);
95+
96+
TyrusServerContainer serverContainer = (TyrusServerContainer) getContainer(servletRequest);
97+
TyrusWebSocketEngine engine = (TyrusWebSocketEngine) serverContainer.getWebSocketEngine();
98+
Object tyrusEndpoint = null;
99+
100+
try {
101+
// Shouldn't matter for processing but must be unique
102+
String path = "/" + random.nextLong();
103+
tyrusEndpoint = createTyrusEndpoint(endpoint, path, subProtocol, extensions, serverContainer, engine);
104+
getEndpointHelper().register(engine, tyrusEndpoint);
105+
106+
HttpHeaders headers = request.getHeaders();
107+
RequestContext requestContext = createRequestContext(servletRequest, path, headers);
108+
TyrusUpgradeResponse upgradeResponse = new TyrusUpgradeResponse();
109+
UpgradeInfo upgradeInfo = engine.upgrade(requestContext, upgradeResponse);
110+
111+
switch (upgradeInfo.getStatus()) {
112+
case SUCCESS:
113+
if (logger.isTraceEnabled()) {
114+
logger.trace("Successful upgrade: " + upgradeResponse.getHeaders());
115+
}
116+
handleSuccess(servletRequest, servletResponse, upgradeInfo, upgradeResponse);
117+
break;
118+
case HANDSHAKE_FAILED:
119+
// Should never happen
120+
throw new HandshakeFailureException("Unexpected handshake failure: " + request.getURI());
121+
case NOT_APPLICABLE:
122+
// Should never happen
123+
throw new HandshakeFailureException("Unexpected handshake mapping failure: " + request.getURI());
124+
}
125+
}
126+
catch (Exception ex) {
127+
throw new HandshakeFailureException("Error during handshake: " + request.getURI(), ex);
128+
}
129+
finally {
130+
if (tyrusEndpoint != null) {
131+
getEndpointHelper().unregister(engine, tyrusEndpoint);
132+
}
133+
}
134+
}
135+
136+
protected abstract void handleSuccess(HttpServletRequest request, HttpServletResponse response,
137+
UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException;
138+
139+
private Object createTyrusEndpoint(Endpoint endpoint, String endpointPath, String protocol,
140+
List<Extension> extensions, WebSocketContainer container, TyrusWebSocketEngine engine)
141+
throws DeploymentException {
142+
143+
ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint);
144+
endpointConfig.setSubprotocols(Arrays.asList(protocol));
145+
endpointConfig.setExtensions(extensions);
146+
return getEndpointHelper().createdEndpoint(endpointConfig, this.componentProvider, container, engine);
147+
}
148+
149+
private RequestContext createRequestContext(HttpServletRequest request, String endpointPath, HttpHeaders headers) {
150+
RequestContext context =
151+
RequestContext.Builder.create()
152+
.requestURI(URI.create(endpointPath))
153+
.userPrincipal(request.getUserPrincipal())
154+
.secure(request.isSecure())
155+
// .remoteAddr(request.getRemoteAddr()) # Not available in 1.3.5
156+
.build();
157+
for (String header : headers.keySet()) {
158+
context.getHeaders().put(header, headers.get(header));
159+
}
160+
return context;
161+
}
162+
163+
164+
/**
165+
* Helps with the creation, registration, and un-registration of endpoints.
166+
*/
167+
protected interface TyrusEndpointHelper {
168+
169+
Object createdEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
170+
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException;
171+
172+
void register(TyrusWebSocketEngine engine, Object endpoint);
173+
174+
void unregister(TyrusWebSocketEngine engine, Object endpoint);
175+
176+
}
177+
178+
protected static class Tyrus17EndpointHelper implements TyrusEndpointHelper {
179+
180+
private static final Constructor<?> constructor;
181+
182+
private static final Method registerMethod;
183+
184+
private static final Method unRegisterMethod;
185+
186+
static {
187+
try {
188+
constructor = getEndpointConstructor();
189+
registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", TyrusEndpointWrapper.class);
190+
unRegisterMethod = TyrusWebSocketEngine.class.getDeclaredMethod("unregister", TyrusEndpointWrapper.class);
191+
ReflectionUtils.makeAccessible(registerMethod);
192+
}
193+
catch (Exception ex) {
194+
throw new IllegalStateException("No compatible Tyrus version found", ex);
195+
}
196+
}
197+
198+
private static Constructor<?> getEndpointConstructor() {
199+
for (Constructor<?> current : TyrusEndpointWrapper.class.getConstructors()) {
200+
Class<?>[] types = current.getParameterTypes();
201+
if (types[0].equals(Endpoint.class) && types[1].equals(EndpointConfig.class)) {
202+
return current;
203+
}
204+
}
205+
throw new IllegalStateException("No compatible Tyrus version found");
206+
}
207+
208+
209+
@Override
210+
public Object createdEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
211+
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException {
212+
213+
DirectFieldAccessor accessor = new DirectFieldAccessor(engine);
214+
Object sessionListener = accessor.getPropertyValue("sessionListener");
215+
Object clusterContext = accessor.getPropertyValue("clusterContext");
216+
try {
217+
return constructor.newInstance(registration.getEndpoint(), registration, provider, container,
218+
"/", registration.getConfigurator(), sessionListener, clusterContext, null);
219+
}
220+
catch (Exception ex) {
221+
throw new HandshakeFailureException("Failed to register " + registration, ex);
222+
}
223+
}
224+
225+
@Override
226+
public void register(TyrusWebSocketEngine engine, Object endpoint) {
227+
try {
228+
registerMethod.invoke(engine, endpoint);
229+
}
230+
catch (Exception ex) {
231+
throw new HandshakeFailureException("Failed to register " + endpoint, ex);
232+
}
233+
}
234+
235+
@Override
236+
public void unregister(TyrusWebSocketEngine engine, Object endpoint) {
237+
try {
238+
unRegisterMethod.invoke(engine, endpoint);
239+
}
240+
catch (Exception ex) {
241+
throw new HandshakeFailureException("Failed to unregister " + endpoint, ex);
242+
}
243+
}
244+
}
245+
246+
protected static class Tyrus135EndpointHelper implements TyrusEndpointHelper {
247+
248+
private static final Method registerMethod;
249+
250+
static {
251+
try {
252+
registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", WebSocketApplication.class);
253+
ReflectionUtils.makeAccessible(registerMethod);
254+
}
255+
catch (Exception ex) {
256+
throw new IllegalStateException("No compatible Tyrus version found", ex);
257+
}
258+
}
259+
260+
@Override
261+
public Object createdEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider,
262+
WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException {
263+
264+
TyrusEndpointWrapper endpointWrapper = new TyrusEndpointWrapper(registration.getEndpoint(),
265+
registration, provider, container, "/", registration.getConfigurator());
266+
267+
return new TyrusEndpoint(endpointWrapper);
268+
}
269+
270+
@Override
271+
public void register(TyrusWebSocketEngine engine, Object endpoint) {
272+
try {
273+
registerMethod.invoke(engine, endpoint);
274+
}
275+
catch (Exception ex) {
276+
throw new HandshakeFailureException("Failed to register " + endpoint, ex);
277+
}
278+
}
279+
280+
@Override
281+
public void unregister(TyrusWebSocketEngine engine, Object endpoint) {
282+
engine.unregister((TyrusEndpoint) endpoint);
283+
}
284+
}
285+
286+
}

0 commit comments

Comments
 (0)