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.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.ServiceLoader;
30 import java.util.concurrent.CompletableFuture;
31 import java.util.concurrent.Executor;
32
33 import org.eclipse.jetty.io.ByteBufferPool;
34 import org.eclipse.jetty.io.Connection;
35 import org.eclipse.jetty.util.annotation.ManagedAttribute;
36 import org.eclipse.jetty.util.annotation.ManagedObject;
37 import org.eclipse.jetty.util.component.ContainerLifeCycle;
38 import org.eclipse.jetty.util.component.Dumpable;
39 import org.eclipse.jetty.util.log.Log;
40 import org.eclipse.jetty.util.log.Logger;
41 import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
42 import org.eclipse.jetty.websocket.api.BatchMode;
43 import org.eclipse.jetty.websocket.api.CloseException;
44 import org.eclipse.jetty.websocket.api.CloseStatus;
45 import org.eclipse.jetty.websocket.api.RemoteEndpoint;
46 import org.eclipse.jetty.websocket.api.Session;
47 import org.eclipse.jetty.websocket.api.StatusCode;
48 import org.eclipse.jetty.websocket.api.SuspendToken;
49 import org.eclipse.jetty.websocket.api.UpgradeRequest;
50 import org.eclipse.jetty.websocket.api.UpgradeResponse;
51 import org.eclipse.jetty.websocket.api.WebSocketBehavior;
52 import org.eclipse.jetty.websocket.api.WebSocketException;
53 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
54 import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
55 import org.eclipse.jetty.websocket.api.extensions.Frame;
56 import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
57 import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
58 import org.eclipse.jetty.websocket.common.events.EventDriver;
59 import org.eclipse.jetty.websocket.common.io.IOState;
60 import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
61 import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
62 import org.eclipse.jetty.websocket.common.scopes.WebSocketSessionScope;
63
64 @ManagedObject("A Jetty WebSocket Session")
65 public class WebSocketSession extends ContainerLifeCycle implements Session, RemoteEndpointFactory, WebSocketSessionScope, IncomingFrames, Connection.Listener, ConnectionStateListener
66 {
67 private static final Logger LOG = Log.getLogger(WebSocketSession.class);
68 private static final Logger LOG_OPEN = Log.getLogger(WebSocketSession.class.getName() + "_OPEN");
69 private final WebSocketContainerScope containerScope;
70 private final URI requestURI;
71 private final LogicalConnection connection;
72 private final EventDriver websocket;
73 private final Executor executor;
74 private final WebSocketPolicy policy;
75 private ClassLoader classLoader;
76 private ExtensionFactory extensionFactory;
77 private RemoteEndpointFactory remoteEndpointFactory;
78 private String protocolVersion;
79 private Map<String, String[]> parameterMap = new HashMap<>();
80 private RemoteEndpoint remote;
81 private IncomingFrames incomingHandler;
82 private OutgoingFrames outgoingHandler;
83 private UpgradeRequest upgradeRequest;
84 private UpgradeResponse upgradeResponse;
85 private CompletableFuture<Session> openFuture;
86
87 public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, EventDriver websocket, LogicalConnection connection)
88 {
89 Objects.requireNonNull(containerScope,"Container Scope cannot be null");
90 Objects.requireNonNull(requestURI,"Request URI cannot be null");
91
92 this.classLoader = Thread.currentThread().getContextClassLoader();
93 this.containerScope = containerScope;
94 this.requestURI = requestURI;
95 this.websocket = websocket;
96 this.connection = connection;
97 this.executor = connection.getExecutor();
98 this.outgoingHandler = connection;
99 this.incomingHandler = websocket;
100 this.connection.getIOState().addListener(this);
101 this.policy = websocket.getPolicy();
102
103 addBean(this.connection);
104 addBean(this.websocket);
105 }
106
107 @Override
108 public void close()
109 {
110
111 close(StatusCode.NORMAL, null);
112 }
113
114 @Override
115 public void close(CloseStatus closeStatus)
116 {
117 close(closeStatus.getCode(),closeStatus.getPhrase());
118 }
119
120 @Override
121 public void close(int statusCode, String reason)
122 {
123 connection.close(statusCode,reason);
124 }
125
126
127
128
129 @Override
130 public void disconnect()
131 {
132 connection.disconnect();
133
134
135 notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
136 }
137
138 public void dispatch(Runnable runnable)
139 {
140 executor.execute(runnable);
141 }
142
143 @Override
144 protected void doStart() throws Exception
145 {
146 if(LOG.isDebugEnabled())
147 LOG.debug("starting - {}",this);
148
149 Iterator<RemoteEndpointFactory> iter = ServiceLoader.load(RemoteEndpointFactory.class).iterator();
150 if (iter.hasNext())
151 remoteEndpointFactory = iter.next();
152
153 if (remoteEndpointFactory == null)
154 remoteEndpointFactory = this;
155
156 if (LOG.isDebugEnabled())
157 LOG.debug("Using RemoteEndpointFactory: {}", remoteEndpointFactory);
158
159 super.doStart();
160 }
161
162 @Override
163 protected void doStop() throws Exception
164 {
165 if(LOG.isDebugEnabled())
166 LOG.debug("stopping - {}",this);
167 try
168 {
169 close(StatusCode.SHUTDOWN,"Shutdown");
170 }
171 catch (Throwable t)
172 {
173 LOG.debug("During Connection Shutdown",t);
174 }
175 super.doStop();
176 }
177
178 @Override
179 public void dump(Appendable out, String indent) throws IOException
180 {
181 dumpThis(out);
182 out.append(indent).append(" +- incomingHandler : ");
183 if (incomingHandler instanceof Dumpable)
184 {
185 ((Dumpable)incomingHandler).dump(out,indent + " ");
186 }
187 else
188 {
189 out.append(incomingHandler.toString()).append(System.lineSeparator());
190 }
191
192 out.append(indent).append(" +- outgoingHandler : ");
193 if (outgoingHandler instanceof Dumpable)
194 {
195 ((Dumpable)outgoingHandler).dump(out,indent + " ");
196 }
197 else
198 {
199 out.append(outgoingHandler.toString()).append(System.lineSeparator());
200 }
201 }
202
203 @Override
204 public boolean equals(Object obj)
205 {
206 if (this == obj)
207 {
208 return true;
209 }
210 if (obj == null)
211 {
212 return false;
213 }
214 if (getClass() != obj.getClass())
215 {
216 return false;
217 }
218 WebSocketSession other = (WebSocketSession)obj;
219 if (connection == null)
220 {
221 if (other.connection != null)
222 {
223 return false;
224 }
225 }
226 else if (!connection.equals(other.connection))
227 {
228 return false;
229 }
230 return true;
231 }
232
233 public ByteBufferPool getBufferPool()
234 {
235 return this.connection.getBufferPool();
236 }
237
238 public ClassLoader getClassLoader()
239 {
240 return this.getClass().getClassLoader();
241 }
242
243 public LogicalConnection getConnection()
244 {
245 return connection;
246 }
247
248 @Override
249 public WebSocketContainerScope getContainerScope()
250 {
251 return this.containerScope;
252 }
253
254 public ExtensionFactory getExtensionFactory()
255 {
256 return extensionFactory;
257 }
258
259
260
261
262 @Override
263 public long getIdleTimeout()
264 {
265 return connection.getMaxIdleTimeout();
266 }
267
268 @ManagedAttribute(readonly = true)
269 public IncomingFrames getIncomingHandler()
270 {
271 return incomingHandler;
272 }
273
274 @Override
275 public InetSocketAddress getLocalAddress()
276 {
277 return connection.getLocalAddress();
278 }
279
280 @ManagedAttribute(readonly = true)
281 public OutgoingFrames getOutgoingHandler()
282 {
283 return outgoingHandler;
284 }
285
286 @Override
287 public WebSocketPolicy getPolicy()
288 {
289 return policy;
290 }
291
292 @Override
293 public String getProtocolVersion()
294 {
295 return protocolVersion;
296 }
297
298 @Override
299 public RemoteEndpoint getRemote()
300 {
301 if(LOG_OPEN.isDebugEnabled())
302 LOG_OPEN.debug("[{}] {}.getRemote()",policy.getBehavior(),this.getClass().getSimpleName());
303 ConnectionState state = connection.getIOState().getConnectionState();
304
305 if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
306 {
307 return remote;
308 }
309
310 throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
311 }
312
313 @Override
314 public InetSocketAddress getRemoteAddress()
315 {
316 return remote.getInetSocketAddress();
317 }
318
319 public URI getRequestURI()
320 {
321 return requestURI;
322 }
323
324 @Override
325 public UpgradeRequest getUpgradeRequest()
326 {
327 return this.upgradeRequest;
328 }
329
330 @Override
331 public UpgradeResponse getUpgradeResponse()
332 {
333 return this.upgradeResponse;
334 }
335
336
337 @Override
338 public WebSocketSession getWebSocketSession()
339 {
340 return this;
341 }
342
343 @Override
344 public int hashCode()
345 {
346 final int prime = 31;
347 int result = 1;
348 result = (prime * result) + ((connection == null)?0:connection.hashCode());
349 return result;
350 }
351
352
353
354
355 @Override
356 public void incomingError(Throwable t)
357 {
358
359 websocket.incomingError(t);
360 }
361
362
363
364
365 @Override
366 public void incomingFrame(Frame frame)
367 {
368 ClassLoader old = Thread.currentThread().getContextClassLoader();
369 try
370 {
371 Thread.currentThread().setContextClassLoader(classLoader);
372 if (connection.getIOState().isInputAvailable())
373 {
374
375 incomingHandler.incomingFrame(frame);
376 }
377 }
378 finally
379 {
380 Thread.currentThread().setContextClassLoader(old);
381 }
382 }
383
384 @Override
385 public boolean isOpen()
386 {
387 if (this.connection == null)
388 {
389 return false;
390 }
391 return this.connection.isOpen();
392 }
393
394 @Override
395 public boolean isSecure()
396 {
397 if (upgradeRequest == null)
398 {
399 throw new IllegalStateException("No valid UpgradeRequest yet");
400 }
401
402 URI requestURI = upgradeRequest.getRequestURI();
403
404 return "wss".equalsIgnoreCase(requestURI.getScheme());
405 }
406
407 public void notifyClose(int statusCode, String reason)
408 {
409 if (LOG.isDebugEnabled())
410 {
411 LOG.debug("notifyClose({},{})",statusCode,reason);
412 }
413 websocket.onClose(new CloseInfo(statusCode,reason));
414 }
415
416 public void notifyError(Throwable cause)
417 {
418 if (openFuture != null && !openFuture.isDone())
419 openFuture.completeExceptionally(cause);
420 incomingError(cause);
421 }
422
423 @Override
424 public void onClosed(Connection connection)
425 {
426 }
427
428 @Override
429 public void onOpened(Connection connection)
430 {
431 if(LOG_OPEN.isDebugEnabled())
432 LOG_OPEN.debug("[{}] {}.onOpened()",policy.getBehavior(),this.getClass().getSimpleName());
433 open();
434 }
435
436 @SuppressWarnings("incomplete-switch")
437 @Override
438 public void onConnectionStateChange(ConnectionState state)
439 {
440 switch (state)
441 {
442 case CLOSED:
443 IOState ioState = this.connection.getIOState();
444 CloseInfo close = ioState.getCloseInfo();
445
446 notifyClose(close.getStatusCode(),close.getReason());
447 try
448 {
449 if (LOG.isDebugEnabled())
450 LOG.debug("{}.onSessionClosed()",containerScope.getClass().getSimpleName());
451 containerScope.onSessionClosed(this);
452 }
453 catch (Throwable t)
454 {
455 LOG.ignore(t);
456 }
457 break;
458 case CONNECTED:
459
460 try
461 {
462 if (LOG.isDebugEnabled())
463 LOG.debug("{}.onSessionOpened()",containerScope.getClass().getSimpleName());
464 containerScope.onSessionOpened(this);
465 }
466 catch (Throwable t)
467 {
468 LOG.ignore(t);
469 }
470 break;
471 }
472 }
473
474 public WebSocketRemoteEndpoint newRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoingFrames, BatchMode batchMode)
475 {
476 return new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
477 }
478
479
480
481
482 public void open()
483 {
484 if(LOG_OPEN.isDebugEnabled())
485 LOG_OPEN.debug("[{}] {}.open()",policy.getBehavior(),this.getClass().getSimpleName());
486
487 if (remote != null)
488 {
489
490 return;
491 }
492
493 try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader))
494 {
495
496 connection.getIOState().onConnected();
497
498
499 remote = remoteEndpointFactory.newRemoteEndpoint(connection,outgoingHandler,getBatchMode());
500 if(LOG_OPEN.isDebugEnabled())
501 LOG_OPEN.debug("[{}] {}.open() remote={}",policy.getBehavior(),this.getClass().getSimpleName(),remote);
502
503
504 websocket.openSession(this);
505
506
507 connection.getIOState().onOpened();
508
509 if (LOG.isDebugEnabled())
510 {
511 LOG.debug("open -> {}",dump());
512 }
513
514 if(openFuture != null)
515 {
516 openFuture.complete(this);
517 }
518 }
519 catch (CloseException ce)
520 {
521 LOG.warn(ce);
522 close(ce.getStatusCode(),ce.getMessage());
523 }
524 catch (Throwable t)
525 {
526 LOG.warn(t);
527
528
529 int statusCode = StatusCode.SERVER_ERROR;
530 if(policy.getBehavior() == WebSocketBehavior.CLIENT)
531 {
532 statusCode = StatusCode.POLICY_VIOLATION;
533 }
534 close(statusCode,t.getMessage());
535 }
536 }
537
538 public void setExtensionFactory(ExtensionFactory extensionFactory)
539 {
540 this.extensionFactory = extensionFactory;
541 }
542
543 public void setFuture(CompletableFuture<Session> fut)
544 {
545 this.openFuture = fut;
546 }
547
548
549
550
551 @Override
552 public void setIdleTimeout(long ms)
553 {
554 connection.setMaxIdleTimeout(ms);
555 }
556
557 public void setOutgoingHandler(OutgoingFrames outgoing)
558 {
559 this.outgoingHandler = outgoing;
560 }
561
562 @Deprecated
563 public void setPolicy(WebSocketPolicy policy)
564 {
565
566 }
567
568 public void setUpgradeRequest(UpgradeRequest request)
569 {
570 this.upgradeRequest = request;
571 this.protocolVersion = request.getProtocolVersion();
572 this.parameterMap.clear();
573 if (request.getParameterMap() != null)
574 {
575 for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
576 {
577 List<String> values = entry.getValue();
578 if (values != null)
579 {
580 this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
581 }
582 else
583 {
584 this.parameterMap.put(entry.getKey(),new String[0]);
585 }
586 }
587 }
588 }
589
590 public void setUpgradeResponse(UpgradeResponse response)
591 {
592 this.upgradeResponse = response;
593 }
594
595 @Override
596 public SuspendToken suspend()
597 {
598 return connection.suspend();
599 }
600
601
602
603
604 public BatchMode getBatchMode()
605 {
606 return BatchMode.AUTO;
607 }
608
609 @Override
610 public String toString()
611 {
612 StringBuilder builder = new StringBuilder();
613 builder.append("WebSocketSession[");
614 builder.append("websocket=").append(websocket);
615 builder.append(",behavior=").append(policy.getBehavior());
616 builder.append(",connection=").append(connection);
617 builder.append(",remote=").append(remote);
618 builder.append(",incoming=").append(incomingHandler);
619 builder.append(",outgoing=").append(outgoingHandler);
620 builder.append("]");
621 return builder.toString();
622 }
623
624 public static interface Listener
625 {
626 void onOpened(WebSocketSession session);
627
628 void onClosed(WebSocketSession session);
629 }
630 }