1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.client.io;
20
21 import java.io.EOFException;
22 import java.io.IOException;
23 import java.net.URI;
24 import java.nio.ByteBuffer;
25 import java.nio.charset.StandardCharsets;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.concurrent.Executor;
29
30 import org.eclipse.jetty.io.AbstractConnection;
31 import org.eclipse.jetty.io.ByteBufferPool;
32 import org.eclipse.jetty.io.EndPoint;
33 import org.eclipse.jetty.util.BufferUtil;
34 import org.eclipse.jetty.util.FutureCallback;
35 import org.eclipse.jetty.util.QuotedStringTokenizer;
36 import org.eclipse.jetty.util.log.Log;
37 import org.eclipse.jetty.util.log.Logger;
38 import org.eclipse.jetty.websocket.api.UpgradeException;
39 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
40 import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
41 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
42 import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
43 import org.eclipse.jetty.websocket.common.AcceptHash;
44 import org.eclipse.jetty.websocket.common.SessionFactory;
45 import org.eclipse.jetty.websocket.common.WebSocketSession;
46 import org.eclipse.jetty.websocket.common.events.EventDriver;
47 import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
48 import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
49 import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException;
50
51
52
53
54
55
56 public class UpgradeConnection extends AbstractConnection
57 {
58 public class SendUpgradeRequest extends FutureCallback implements Runnable
59 {
60 @Override
61 public void run()
62 {
63 URI uri = connectPromise.getRequest().getRequestURI();
64 request.setRequestURI(uri);
65
66 UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
67 if (handshakeListener != null)
68 {
69 handshakeListener.onHandshakeRequest(request);
70 }
71
72 String rawRequest = request.generate();
73
74 ByteBuffer buf = BufferUtil.toBuffer(rawRequest, StandardCharsets.UTF_8);
75 getEndPoint().write(this,buf);
76 }
77
78 @Override
79 public void succeeded()
80 {
81 LOG.debug("Upgrade Request Write Success");
82
83 super.succeeded();
84
85 fillInterested();
86 }
87
88 @Override
89 public void failed(Throwable cause)
90 {
91 LOG.warn("Upgrade Request Write Failure", cause);
92 super.failed(cause);
93
94 connectPromise.failed(cause);
95 }
96 }
97
98
99 private static final int SWITCHING_PROTOCOLS = 101;
100
101 private static final Logger LOG = Log.getLogger(UpgradeConnection.class);
102 private final ByteBufferPool bufferPool;
103 private final ConnectPromise connectPromise;
104 private final HttpResponseHeaderParser parser;
105 private ClientUpgradeRequest request;
106
107 public UpgradeConnection(EndPoint endp, Executor executor, ConnectPromise connectPromise)
108 {
109 super(endp,executor);
110 this.connectPromise = connectPromise;
111 this.bufferPool = connectPromise.getClient().getBufferPool();
112 this.request = connectPromise.getRequest();
113
114
115 this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
116 }
117
118 public void disconnect(boolean onlyOutput)
119 {
120 EndPoint endPoint = getEndPoint();
121
122
123 if (LOG.isDebugEnabled())
124 LOG.debug("Shutting down output {}",endPoint);
125 endPoint.shutdownOutput();
126 if (!onlyOutput)
127 {
128 if (LOG.isDebugEnabled())
129 LOG.debug("Closing {}",endPoint);
130 endPoint.close();
131 }
132 }
133
134 private void failUpgrade(Throwable cause)
135 {
136 close();
137 connectPromise.failed(cause);
138 }
139
140 private void notifyConnect(ClientUpgradeResponse response)
141 {
142 connectPromise.setResponse(response);
143
144 UpgradeListener handshakeListener = connectPromise.getUpgradeListener();
145 if (handshakeListener != null)
146 {
147 handshakeListener.onHandshakeResponse(response);
148 }
149 }
150
151 @Override
152 public void onFillable()
153 {
154 if (LOG.isDebugEnabled())
155 {
156 LOG.debug("onFillable");
157 }
158 ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),false);
159 BufferUtil.clear(buffer);
160 boolean readMore = false;
161 try
162 {
163 readMore = read(buffer);
164 }
165 finally
166 {
167 bufferPool.release(buffer);
168 }
169
170 if (readMore)
171 {
172 fillInterested();
173 }
174 }
175
176 @Override
177 public void onOpen()
178 {
179 super.onOpen();
180 getExecutor().execute(new SendUpgradeRequest());
181 }
182
183 @Override
184 public void onClose()
185 {
186 if (LOG.isDebugEnabled())
187 {
188 LOG.warn("Closed connection {}",this);
189 }
190 super.onClose();
191 }
192
193 @Override
194 protected boolean onReadTimeout()
195 {
196 if (LOG.isDebugEnabled())
197 {
198 LOG.warn("Timeout on connection {}",this);
199 }
200
201 failUpgrade(new IOException("Timeout while performing WebSocket Upgrade"));
202
203 return super.onReadTimeout();
204 }
205
206
207
208
209
210
211
212
213 private boolean read(ByteBuffer buffer)
214 {
215 EndPoint endPoint = getEndPoint();
216 try
217 {
218 while (true)
219 {
220 int filled = endPoint.fill(buffer);
221 if (filled == 0)
222 {
223 return true;
224 }
225 else if (filled < 0)
226 {
227 LOG.warn("read - EOF Reached");
228 failUpgrade(new EOFException("Reading WebSocket Upgrade response"));
229 return false;
230 }
231 else
232 {
233 if (LOG.isDebugEnabled())
234 {
235 LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
236 }
237 ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer);
238 if (resp != null)
239 {
240
241 validateResponse(resp);
242 notifyConnect(resp);
243 upgradeConnection(resp);
244 if (buffer.hasRemaining())
245 {
246 LOG.debug("Has remaining client bytebuffer of {}",buffer.remaining());
247 }
248 return false;
249 }
250 }
251 }
252 }
253 catch (IOException | ParseException e)
254 {
255 UpgradeException ue = new UpgradeException(request.getRequestURI(),e);
256 connectPromise.failed(ue);
257 disconnect(false);
258 return false;
259 }
260 catch (UpgradeException e)
261 {
262 connectPromise.failed(e);
263 disconnect(false);
264 return false;
265 }
266 }
267
268 private void upgradeConnection(ClientUpgradeResponse response)
269 {
270 EndPoint endp = getEndPoint();
271 Executor executor = getExecutor();
272
273 EventDriver websocket = connectPromise.getDriver();
274 WebSocketPolicy policy = websocket.getPolicy();
275
276 WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy);
277
278 SessionFactory sessionFactory = connectPromise.getClient().getSessionFactory();
279 WebSocketSession session = sessionFactory.createSession(request.getRequestURI(),websocket,connection);
280 session.setPolicy(policy);
281 session.setUpgradeResponse(response);
282
283 connection.setSession(session);
284
285
286 ExtensionStack extensionStack = new ExtensionStack(connectPromise.getClient().getExtensionFactory());
287 extensionStack.negotiate(response.getExtensions());
288
289 extensionStack.configure(connection.getParser());
290 extensionStack.configure(connection.getGenerator());
291
292
293 connection.setNextIncomingFrames(extensionStack);
294 extensionStack.setNextIncoming(session);
295
296
297 session.setOutgoingHandler(extensionStack);
298 extensionStack.setNextOutgoing(connection);
299
300 session.addBean(extensionStack);
301 connectPromise.getClient().addManaged(session);
302
303
304
305 endp.setConnection(connection);
306 connection.onOpen();
307 }
308
309 private void validateResponse(ClientUpgradeResponse response)
310 {
311
312 if (response.getStatusCode() != SWITCHING_PROTOCOLS)
313 {
314 throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Didn't switch protocols");
315 }
316
317
318 String connection = response.getHeader("Connection");
319 if (!"upgrade".equalsIgnoreCase(connection))
320 {
321 throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Connection is " + connection + " (expected upgrade)");
322 }
323
324
325 String reqKey = request.getKey();
326 String expectedHash = AcceptHash.hashKey(reqKey);
327 String respHash = response.getHeader("Sec-WebSocket-Accept");
328
329 response.setSuccess(true);
330 if (expectedHash.equalsIgnoreCase(respHash) == false)
331 {
332 response.setSuccess(false);
333 throw new UpgradeException(request.getRequestURI(),response.getStatusCode(),"Invalid Sec-WebSocket-Accept hash");
334 }
335
336
337 List<ExtensionConfig> extensions = new ArrayList<>();
338 List<String> extValues = response.getHeaders("Sec-WebSocket-Extensions");
339 if (extValues != null)
340 {
341 for (String extVal : extValues)
342 {
343 QuotedStringTokenizer tok = new QuotedStringTokenizer(extVal,",");
344 while (tok.hasMoreTokens())
345 {
346 extensions.add(ExtensionConfig.parse(tok.nextToken()));
347 }
348 }
349 }
350 response.setExtensions(extensions);
351 }
352 }