Skip to content

Commit a14161f

Browse files
committed
JettyRequestUpgradeStrategy
1 parent 3cd4909 commit a14161f

File tree

3 files changed

+315
-0
lines changed

3 files changed

+315
-0
lines changed

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,11 @@ project("spring-websocket") {
528528
optional("org.glassfish.tyrus:tyrus-websocket-core:1.0-SNAPSHOT")
529529
optional("org.glassfish.tyrus:tyrus-container-servlet:1.0-SNAPSHOT")
530530

531+
optional("org.eclipse.jetty:jetty-webapp:9.0.1.v20130408") {
532+
exclude group: "org.eclipse.jetty.orbit", module: "javax.servlet"
533+
}
534+
optional("org.eclipse.jetty.websocket:websocket-server:9.0.1.v20130408")
535+
531536
optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") // required for SockJS support currently
532537

533538
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ private static class RequestUpgradeStrategyFactory {
214214
private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent(
215215
"org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", DefaultHandshakeHandler.class.getClassLoader());
216216

217+
private static final boolean jettyWebSocketPresent = ClassUtils.isPresent(
218+
"org.eclipse.jetty.websocket.server.UpgradeContext", DefaultHandshakeHandler.class.getClassLoader());
217219

218220
private RequestUpgradeStrategy create() {
219221
String className;
@@ -223,6 +225,9 @@ private RequestUpgradeStrategy create() {
223225
else if (glassfishWebSocketPresent) {
224226
className = "org.springframework.websocket.server.support.GlassfishRequestUpgradeStrategy";
225227
}
228+
else if (jettyWebSocketPresent) {
229+
className = "org.springframework.websocket.server.support.JettyRequestUpgradeStrategy";
230+
}
226231
else {
227232
throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName());
228233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
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.websocket.server.support;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import org.eclipse.jetty.websocket.api.Session;
28+
import org.eclipse.jetty.websocket.api.UpgradeRequest;
29+
import org.eclipse.jetty.websocket.api.UpgradeResponse;
30+
import org.eclipse.jetty.websocket.api.WebSocketListener;
31+
import org.eclipse.jetty.websocket.server.HandshakeRFC6455;
32+
import org.eclipse.jetty.websocket.server.ServletWebSocketRequest;
33+
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
34+
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
35+
import org.springframework.http.server.ServerHttpRequest;
36+
import org.springframework.http.server.ServerHttpResponse;
37+
import org.springframework.http.server.ServletServerHttpRequest;
38+
import org.springframework.http.server.ServletServerHttpResponse;
39+
import org.springframework.util.Assert;
40+
import org.springframework.util.ObjectUtils;
41+
import org.springframework.websocket.BinaryMessage;
42+
import org.springframework.websocket.BinaryMessageHandler;
43+
import org.springframework.websocket.CloseStatus;
44+
import org.springframework.websocket.HandlerProvider;
45+
import org.springframework.websocket.TextMessage;
46+
import org.springframework.websocket.TextMessageHandler;
47+
import org.springframework.websocket.WebSocketHandler;
48+
import org.springframework.websocket.WebSocketMessage;
49+
import org.springframework.websocket.WebSocketSession;
50+
import org.springframework.websocket.server.RequestUpgradeStrategy;
51+
52+
/**
53+
* {@link RequestUpgradeStrategy} for use with Jetty. Based on Jetty's internal
54+
* {@code org.eclipse.jetty.websocket.server.WebSocketHandler} class.
55+
*
56+
* @author Phillip Webb
57+
*/
58+
public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy {
59+
60+
private static Log logger = LogFactory.getLog(JettyRequestUpgradeStrategy.class);
61+
62+
// FIXME jetty has options, timeouts etc. Do we need a common abstraction
63+
64+
// FIXME need a way for someone to plug their own RequestUpgradeStrategy or override
65+
// Jetty settings
66+
67+
// FIXME when to call factory.cleanup();
68+
69+
private static final String HANDLER_PROVIDER = JettyRequestUpgradeStrategy.class.getName()
70+
+ ".HANDLER_PROVIDER";
71+
72+
private WebSocketServerFactory factory;
73+
74+
75+
public JettyRequestUpgradeStrategy() {
76+
this.factory = new WebSocketServerFactory();
77+
this.factory.setCreator(new WebSocketCreator() {
78+
@Override
79+
@SuppressWarnings("unchecked")
80+
public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) {
81+
Assert.isInstanceOf(ServletWebSocketRequest.class, req);
82+
ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) req;
83+
HandlerProvider<WebSocketHandler> handlerProvider = (HandlerProvider<WebSocketHandler>) servletRequest.getServletAttributes().get(
84+
HANDLER_PROVIDER);
85+
return new WebSocketHandlerAdapter(handlerProvider);
86+
}
87+
});
88+
try {
89+
this.factory.init();
90+
}
91+
catch (Exception ex) {
92+
throw new IllegalStateException(ex);
93+
}
94+
}
95+
96+
@Override
97+
public String[] getSupportedVersions() {
98+
return new String[] { String.valueOf(HandshakeRFC6455.VERSION) };
99+
}
100+
101+
@Override
102+
public void upgrade(ServerHttpRequest request, ServerHttpResponse response,
103+
String selectedProtocol, HandlerProvider<WebSocketHandler> handlerProvider)
104+
throws Exception {
105+
Assert.isInstanceOf(ServletServerHttpRequest.class, request);
106+
Assert.isInstanceOf(ServletServerHttpResponse.class, response);
107+
upgrade(((ServletServerHttpRequest) request).getServletRequest(),
108+
((ServletServerHttpResponse) response).getServletResponse(),
109+
selectedProtocol, handlerProvider);
110+
}
111+
112+
private void upgrade(HttpServletRequest request, HttpServletResponse response,
113+
String selectedProtocol, final HandlerProvider<WebSocketHandler> handlerProvider)
114+
throws Exception {
115+
request.setAttribute(HANDLER_PROVIDER, handlerProvider);
116+
Assert.state(factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request");
117+
Assert.state(factory.acceptWebSocket(request, response), "Unable to accept WebSocket");
118+
}
119+
120+
121+
/**
122+
* Adapts Spring's {@link WebSocketHandler} to Jetty's {@link WebSocketListener}.
123+
*/
124+
private static class WebSocketHandlerAdapter implements WebSocketListener {
125+
126+
private final HandlerProvider<WebSocketHandler> provider;
127+
128+
private WebSocketHandler handler;
129+
130+
private WebSocketSession session;
131+
132+
133+
public WebSocketHandlerAdapter(HandlerProvider<WebSocketHandler> provider) {
134+
Assert.notNull(provider, "Provider must not be null");
135+
Assert.isAssignable(WebSocketHandler.class, provider.getHandlerType());
136+
this.provider = provider;
137+
}
138+
139+
140+
@Override
141+
public void onWebSocketConnect(Session session) {
142+
Assert.state(this.session == null, "WebSocket already open");
143+
try {
144+
this.session = new WebSocketSessionAdapter(session);
145+
if (logger.isDebugEnabled()) {
146+
logger.debug("Client connected, WebSocket session id="
147+
+ this.session.getId() + ", uri=" + this.session.getURI());
148+
}
149+
this.handler = this.provider.getHandler();
150+
this.handler.afterConnectionEstablished(this.session);
151+
}
152+
catch (Exception ex) {
153+
try {
154+
// FIXME revisit after error handling
155+
onWebSocketError(ex);
156+
}
157+
finally {
158+
this.session = null;
159+
this.handler = null;
160+
}
161+
}
162+
}
163+
164+
@Override
165+
public void onWebSocketClose(int statusCode, String reason) {
166+
Assert.state(this.session != null, "WebSocket not open");
167+
try {
168+
CloseStatus closeStatus = new CloseStatus(statusCode, reason);
169+
if (logger.isDebugEnabled()) {
170+
logger.debug("Client disconnected, WebSocket session id="
171+
+ this.session.getId() + ", " + closeStatus);
172+
}
173+
this.handler.afterConnectionClosed(closeStatus, this.session);
174+
}
175+
catch (Exception ex) {
176+
onWebSocketError(ex);
177+
}
178+
finally {
179+
try {
180+
if (this.handler != null) {
181+
this.provider.destroy(this.handler);
182+
}
183+
}
184+
finally {
185+
this.session = null;
186+
this.handler = null;
187+
}
188+
}
189+
}
190+
191+
@Override
192+
public void onWebSocketText(String payload) {
193+
try {
194+
TextMessage message = new TextMessage(payload);
195+
if (logger.isTraceEnabled()) {
196+
logger.trace("Received message for WebSocket session id="
197+
+ this.session.getId() + ": " + message);
198+
}
199+
if (this.handler instanceof TextMessageHandler) {
200+
((TextMessageHandler) this.handler).handleTextMessage(message, this.session);
201+
}
202+
}
203+
catch(Exception ex) {
204+
ex.printStackTrace(); //FIXME
205+
}
206+
}
207+
208+
@Override
209+
public void onWebSocketBinary(byte[] payload, int offset, int len) {
210+
try {
211+
BinaryMessage message = new BinaryMessage(payload, offset, len);
212+
if (logger.isTraceEnabled()) {
213+
logger.trace("Received binary data for WebSocket session id="
214+
+ this.session.getId() + ": " + message);
215+
}
216+
if (this.handler instanceof BinaryMessageHandler) {
217+
((BinaryMessageHandler) this.handler).handleBinaryMessage(message,
218+
this.session);
219+
}
220+
}
221+
catch(Exception ex) {
222+
ex.printStackTrace(); //FIXME
223+
}
224+
}
225+
226+
@Override
227+
public void onWebSocketError(Throwable cause) {
228+
try {
229+
this.handler.handleError(cause, this.session);
230+
}
231+
catch (Throwable ex) {
232+
// FIXME exceptions
233+
logger.error("Error for WebSocket session id=" + this.session.getId(),
234+
cause);
235+
}
236+
}
237+
}
238+
239+
240+
/**
241+
* Adapts Jetty's {@link Session} to Spring's {@link WebSocketSession}.
242+
*/
243+
private static class WebSocketSessionAdapter implements WebSocketSession {
244+
245+
private Session session;
246+
247+
248+
public WebSocketSessionAdapter(Session session) {
249+
this.session = session;
250+
}
251+
252+
253+
@Override
254+
public String getId() {
255+
return ObjectUtils.getIdentityHexString(this.session);
256+
}
257+
258+
@Override
259+
public boolean isOpen() {
260+
return this.session.isOpen();
261+
}
262+
263+
@Override
264+
public boolean isSecure() {
265+
return this.session.isSecure();
266+
}
267+
268+
@Override
269+
public URI getURI() {
270+
return this.session.getUpgradeRequest().getRequestURI();
271+
}
272+
273+
@Override
274+
public void sendMessage(WebSocketMessage message) throws Exception {
275+
if (message instanceof BinaryMessage) {
276+
sendMessage((BinaryMessage) message);
277+
}
278+
else if (message instanceof TextMessage) {
279+
sendMessage((TextMessage) message);
280+
}
281+
else {
282+
throw new IllegalArgumentException("Unsupported message type");
283+
}
284+
}
285+
286+
private void sendMessage(BinaryMessage message) throws Exception {
287+
this.session.getRemote().sendBytes(message.getPayload());
288+
}
289+
290+
private void sendMessage(TextMessage message) throws Exception {
291+
this.session.getRemote().sendString(message.getPayload());
292+
}
293+
294+
@Override
295+
public void close() throws IOException {
296+
this.session.close();
297+
}
298+
299+
@Override
300+
public void close(CloseStatus status) throws IOException {
301+
this.session.close(status.getCode(), status.getReason());
302+
}
303+
}
304+
305+
}

0 commit comments

Comments
 (0)