1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.common;
20
21 import java.io.IOException;
22 import java.net.InetSocketAddress;
23 import java.net.URI;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.Executor;
28
29 import org.eclipse.jetty.io.ByteBufferPool;
30 import org.eclipse.jetty.util.annotation.ManagedAttribute;
31 import org.eclipse.jetty.util.annotation.ManagedObject;
32 import org.eclipse.jetty.util.component.ContainerLifeCycle;
33 import org.eclipse.jetty.util.component.Dumpable;
34 import org.eclipse.jetty.util.log.Log;
35 import org.eclipse.jetty.util.log.Logger;
36 import org.eclipse.jetty.websocket.api.BatchMode;
37 import org.eclipse.jetty.websocket.api.CloseStatus;
38 import org.eclipse.jetty.websocket.api.RemoteEndpoint;
39 import org.eclipse.jetty.websocket.api.Session;
40 import org.eclipse.jetty.websocket.api.StatusCode;
41 import org.eclipse.jetty.websocket.api.SuspendToken;
42 import org.eclipse.jetty.websocket.api.UpgradeRequest;
43 import org.eclipse.jetty.websocket.api.UpgradeResponse;
44 import org.eclipse.jetty.websocket.api.WebSocketBehavior;
45 import org.eclipse.jetty.websocket.api.WebSocketException;
46 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
47 import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
48 import org.eclipse.jetty.websocket.api.extensions.Frame;
49 import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
50 import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
51 import org.eclipse.jetty.websocket.common.events.EventDriver;
52 import org.eclipse.jetty.websocket.common.io.IOState;
53 import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
54
55 @ManagedObject("A Jetty WebSocket Session")
56 public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener
57 {
58 private static final Logger LOG = Log.getLogger(WebSocketSession.class);
59 private final URI requestURI;
60 private final EventDriver websocket;
61 private final LogicalConnection connection;
62 private final SessionListener[] sessionListeners;
63 private final Executor executor;
64 private ClassLoader classLoader;
65 private ExtensionFactory extensionFactory;
66 private String protocolVersion;
67 private Map<String, String[]> parameterMap = new HashMap<>();
68 private WebSocketRemoteEndpoint remote;
69 private IncomingFrames incomingHandler;
70 private OutgoingFrames outgoingHandler;
71 private WebSocketPolicy policy;
72 private UpgradeRequest upgradeRequest;
73 private UpgradeResponse upgradeResponse;
74
75 public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener... sessionListeners)
76 {
77 if (requestURI == null)
78 {
79 throw new RuntimeException("Request URI cannot be null");
80 }
81
82 this.classLoader = Thread.currentThread().getContextClassLoader();
83 this.requestURI = requestURI;
84 this.websocket = websocket;
85 this.connection = connection;
86 this.sessionListeners = sessionListeners;
87 this.executor = connection.getExecutor();
88 this.outgoingHandler = connection;
89 this.incomingHandler = websocket;
90 this.connection.getIOState().addListener(this);
91 }
92
93 @Override
94 public void close()
95 {
96 this.close(StatusCode.NORMAL,null);
97 }
98
99 @Override
100 public void close(CloseStatus closeStatus)
101 {
102 this.close(closeStatus.getCode(),closeStatus.getPhrase());
103 }
104
105 @Override
106 public void close(int statusCode, String reason)
107 {
108 connection.close(statusCode,reason);
109 }
110
111
112
113
114 @Override
115 public void disconnect()
116 {
117 connection.disconnect();
118
119
120 notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
121 }
122
123 public void dispatch(Runnable runnable)
124 {
125 executor.execute(runnable);
126 }
127
128 @Override
129 public void dump(Appendable out, String indent) throws IOException
130 {
131 dumpThis(out);
132 out.append(indent).append(" +- incomingHandler : ");
133 if (incomingHandler instanceof Dumpable)
134 {
135 ((Dumpable)incomingHandler).dump(out,indent + " ");
136 }
137 else
138 {
139 out.append(incomingHandler.toString()).append(System.lineSeparator());
140 }
141
142 out.append(indent).append(" +- outgoingHandler : ");
143 if (outgoingHandler instanceof Dumpable)
144 {
145 ((Dumpable)outgoingHandler).dump(out,indent + " ");
146 }
147 else
148 {
149 out.append(outgoingHandler.toString()).append(System.lineSeparator());
150 }
151 }
152
153 @Override
154 public boolean equals(Object obj)
155 {
156 if (this == obj)
157 {
158 return true;
159 }
160 if (obj == null)
161 {
162 return false;
163 }
164 if (getClass() != obj.getClass())
165 {
166 return false;
167 }
168 WebSocketSession other = (WebSocketSession)obj;
169 if (connection == null)
170 {
171 if (other.connection != null)
172 {
173 return false;
174 }
175 }
176 else if (!connection.equals(other.connection))
177 {
178 return false;
179 }
180 return true;
181 }
182
183 public ByteBufferPool getBufferPool()
184 {
185 return this.connection.getBufferPool();
186 }
187
188 public ClassLoader getClassLoader()
189 {
190 return this.getClass().getClassLoader();
191 }
192
193 public LogicalConnection getConnection()
194 {
195 return connection;
196 }
197
198 public ExtensionFactory getExtensionFactory()
199 {
200 return extensionFactory;
201 }
202
203
204
205
206 @Override
207 public long getIdleTimeout()
208 {
209 return connection.getMaxIdleTimeout();
210 }
211
212 @ManagedAttribute(readonly = true)
213 public IncomingFrames getIncomingHandler()
214 {
215 return incomingHandler;
216 }
217
218 @Override
219 public InetSocketAddress getLocalAddress()
220 {
221 return connection.getLocalAddress();
222 }
223
224 @ManagedAttribute(readonly = true)
225 public OutgoingFrames getOutgoingHandler()
226 {
227 return outgoingHandler;
228 }
229
230 @Override
231 public WebSocketPolicy getPolicy()
232 {
233 return policy;
234 }
235
236 @Override
237 public String getProtocolVersion()
238 {
239 return protocolVersion;
240 }
241
242 @Override
243 public RemoteEndpoint getRemote()
244 {
245 ConnectionState state = connection.getIOState().getConnectionState();
246
247 if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
248 {
249 return remote;
250 }
251
252 throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
253 }
254
255 @Override
256 public InetSocketAddress getRemoteAddress()
257 {
258 return remote.getInetSocketAddress();
259 }
260
261 public URI getRequestURI()
262 {
263 return requestURI;
264 }
265
266 @Override
267 public UpgradeRequest getUpgradeRequest()
268 {
269 return this.upgradeRequest;
270 }
271
272 @Override
273 public UpgradeResponse getUpgradeResponse()
274 {
275 return this.upgradeResponse;
276 }
277
278 @Override
279 public int hashCode()
280 {
281 final int prime = 31;
282 int result = 1;
283 result = (prime * result) + ((connection == null)?0:connection.hashCode());
284 return result;
285 }
286
287
288
289
290 @Override
291 public void incomingError(Throwable t)
292 {
293 if (connection.getIOState().isInputAvailable())
294 {
295
296 websocket.incomingError(t);
297 }
298 }
299
300
301
302
303 @Override
304 public void incomingFrame(Frame frame)
305 {
306 if (connection.getIOState().isInputAvailable())
307 {
308
309 incomingHandler.incomingFrame(frame);
310 }
311 }
312
313 @Override
314 public boolean isOpen()
315 {
316 if (this.connection == null)
317 {
318 return false;
319 }
320 return this.connection.isOpen();
321 }
322
323 @Override
324 public boolean isSecure()
325 {
326 if (upgradeRequest == null)
327 {
328 throw new IllegalStateException("No valid UpgradeRequest yet");
329 }
330
331 URI requestURI = upgradeRequest.getRequestURI();
332
333 return "wss".equalsIgnoreCase(requestURI.getScheme());
334 }
335
336 public void notifyClose(int statusCode, String reason)
337 {
338 if (LOG.isDebugEnabled())
339 {
340 LOG.debug("notifyClose({},{})",statusCode,reason);
341 }
342 websocket.onClose(new CloseInfo(statusCode,reason));
343 }
344
345 public void notifyError(Throwable cause)
346 {
347 incomingError(cause);
348 }
349
350 @SuppressWarnings("incomplete-switch")
351 @Override
352 public void onConnectionStateChange(ConnectionState state)
353 {
354 switch (state)
355 {
356 case CLOSED:
357
358 for (SessionListener listener : sessionListeners)
359 {
360 try
361 {
362 if (LOG.isDebugEnabled())
363 LOG.debug("{}.onSessionClosed()",listener.getClass().getSimpleName());
364 listener.onSessionClosed(this);
365 }
366 catch (Throwable t)
367 {
368 LOG.ignore(t);
369 }
370 }
371 IOState ioState = this.connection.getIOState();
372 CloseInfo close = ioState.getCloseInfo();
373
374 notifyClose(close.getStatusCode(),close.getReason());
375 break;
376 case OPEN:
377
378 for (SessionListener listener : sessionListeners)
379 {
380 try
381 {
382 listener.onSessionOpened(this);
383 }
384 catch (Throwable t)
385 {
386 LOG.ignore(t);
387 }
388 }
389 break;
390 }
391 }
392
393
394
395
396 public void open()
397 {
398 if (remote != null)
399 {
400
401 return;
402 }
403
404 ClassLoader old = Thread.currentThread().getContextClassLoader();
405 try {
406 Thread.currentThread().setContextClassLoader(classLoader);
407
408
409 connection.getIOState().onConnected();
410
411
412 remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
413
414
415 websocket.openSession(this);
416
417
418 connection.getIOState().onOpened();
419
420 if (LOG.isDebugEnabled())
421 {
422 LOG.debug("open -> {}",dump());
423 }
424 }
425 catch (Throwable t)
426 {
427
428
429 int statusCode = StatusCode.SERVER_ERROR;
430 if(policy.getBehavior() == WebSocketBehavior.CLIENT)
431 {
432 statusCode = StatusCode.POLICY_VIOLATION;
433 }
434
435 close(statusCode,t.getMessage());
436 }
437 finally
438 {
439 Thread.currentThread().setContextClassLoader(old);
440 }
441 }
442
443 public void setExtensionFactory(ExtensionFactory extensionFactory)
444 {
445 this.extensionFactory = extensionFactory;
446 }
447
448
449
450
451 @Override
452 public void setIdleTimeout(long ms)
453 {
454 connection.setMaxIdleTimeout(ms);
455 }
456
457 public void setOutgoingHandler(OutgoingFrames outgoing)
458 {
459 this.outgoingHandler = outgoing;
460 }
461
462 public void setPolicy(WebSocketPolicy policy)
463 {
464 this.policy = policy;
465 }
466
467 public void setUpgradeRequest(UpgradeRequest request)
468 {
469 this.upgradeRequest = request;
470 this.protocolVersion = request.getProtocolVersion();
471 this.parameterMap.clear();
472 if (request.getParameterMap() != null)
473 {
474 for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
475 {
476 List<String> values = entry.getValue();
477 if (values != null)
478 {
479 this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
480 }
481 else
482 {
483 this.parameterMap.put(entry.getKey(),new String[0]);
484 }
485 }
486 }
487 }
488
489 public void setUpgradeResponse(UpgradeResponse response)
490 {
491 this.upgradeResponse = response;
492 }
493
494 @Override
495 public SuspendToken suspend()
496 {
497 return connection.suspend();
498 }
499
500
501
502
503 public BatchMode getBatchMode()
504 {
505 return BatchMode.AUTO;
506 }
507
508 @Override
509 public String toString()
510 {
511 StringBuilder builder = new StringBuilder();
512 builder.append("WebSocketSession[");
513 builder.append("websocket=").append(websocket);
514 builder.append(",behavior=").append(policy.getBehavior());
515 builder.append(",connection=").append(connection);
516 builder.append(",remote=").append(remote);
517 builder.append(",incoming=").append(incomingHandler);
518 builder.append(",outgoing=").append(outgoingHandler);
519 builder.append("]");
520 return builder.toString();
521 }
522
523 }