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