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 ExtensionFactory extensionFactory;
65 private String protocolVersion;
66 private Map<String, String[]> parameterMap = new HashMap<>();
67 private WebSocketRemoteEndpoint remote;
68 private IncomingFrames incomingHandler;
69 private OutgoingFrames outgoingHandler;
70 private WebSocketPolicy policy;
71 private UpgradeRequest upgradeRequest;
72 private UpgradeResponse upgradeResponse;
73
74 public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener... sessionListeners)
75 {
76 if (requestURI == null)
77 {
78 throw new RuntimeException("Request URI cannot be null");
79 }
80
81 this.requestURI = requestURI;
82 this.websocket = websocket;
83 this.connection = connection;
84 this.sessionListeners = sessionListeners;
85 this.executor = connection.getExecutor();
86 this.outgoingHandler = connection;
87 this.incomingHandler = websocket;
88 this.connection.getIOState().addListener(this);
89 }
90
91 @Override
92 public void close()
93 {
94 this.close(StatusCode.NORMAL,null);
95 }
96
97 @Override
98 public void close(CloseStatus closeStatus)
99 {
100 this.close(closeStatus.getCode(),closeStatus.getPhrase());
101 }
102
103 @Override
104 public void close(int statusCode, String reason)
105 {
106 connection.close(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 if (LOG.isDebugEnabled())
332 {
333 LOG.debug("notifyClose({},{})",statusCode,reason);
334 }
335 websocket.onClose(new CloseInfo(statusCode,reason));
336 }
337
338 public void notifyError(Throwable cause)
339 {
340 incomingError(cause);
341 }
342
343 @SuppressWarnings("incomplete-switch")
344 @Override
345 public void onConnectionStateChange(ConnectionState state)
346 {
347 switch (state)
348 {
349 case CLOSED:
350
351 for (SessionListener listener : sessionListeners)
352 {
353 try
354 {
355 LOG.debug("{}.onSessionClosed()",listener.getClass().getSimpleName());
356 listener.onSessionClosed(this);
357 }
358 catch (Throwable t)
359 {
360 LOG.ignore(t);
361 }
362 }
363 IOState ioState = this.connection.getIOState();
364 CloseInfo close = ioState.getCloseInfo();
365
366 notifyClose(close.getStatusCode(),close.getReason());
367 break;
368 case OPEN:
369
370 for (SessionListener listener : sessionListeners)
371 {
372 try
373 {
374 listener.onSessionOpened(this);
375 }
376 catch (Throwable t)
377 {
378 LOG.ignore(t);
379 }
380 }
381 break;
382 }
383 }
384
385
386
387
388 public void open()
389 {
390 if (remote != null)
391 {
392
393 return;
394 }
395
396
397 connection.getIOState().onConnected();
398
399
400 remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
401
402 try
403 {
404
405 websocket.openSession(this);
406
407
408 connection.getIOState().onOpened();
409
410 if (LOG.isDebugEnabled())
411 {
412 LOG.debug("open -> {}",dump());
413 }
414 }
415 catch (Throwable t)
416 {
417
418
419 int statusCode = StatusCode.SERVER_ERROR;
420 if(policy.getBehavior() == WebSocketBehavior.CLIENT)
421 {
422 statusCode = StatusCode.POLICY_VIOLATION;
423 }
424
425 close(statusCode,t.getMessage());
426 }
427 }
428
429 public void setExtensionFactory(ExtensionFactory extensionFactory)
430 {
431 this.extensionFactory = extensionFactory;
432 }
433
434
435
436
437 @Override
438 public void setIdleTimeout(long ms)
439 {
440 connection.setMaxIdleTimeout(ms);
441 }
442
443 public void setOutgoingHandler(OutgoingFrames outgoing)
444 {
445 this.outgoingHandler = outgoing;
446 }
447
448 public void setPolicy(WebSocketPolicy policy)
449 {
450 this.policy = policy;
451 }
452
453 public void setUpgradeRequest(UpgradeRequest request)
454 {
455 this.upgradeRequest = request;
456 this.protocolVersion = request.getProtocolVersion();
457 this.parameterMap.clear();
458 if (request.getParameterMap() != null)
459 {
460 for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
461 {
462 List<String> values = entry.getValue();
463 if (values != null)
464 {
465 this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
466 }
467 else
468 {
469 this.parameterMap.put(entry.getKey(),new String[0]);
470 }
471 }
472 }
473 }
474
475 public void setUpgradeResponse(UpgradeResponse response)
476 {
477 this.upgradeResponse = response;
478 }
479
480 @Override
481 public SuspendToken suspend()
482 {
483 return connection.suspend();
484 }
485
486
487
488
489 public BatchMode getBatchMode()
490 {
491 return BatchMode.AUTO;
492 }
493
494 @Override
495 public String toString()
496 {
497 StringBuilder builder = new StringBuilder();
498 builder.append("WebSocketSession[");
499 builder.append("websocket=").append(websocket);
500 builder.append(",behavior=").append(policy.getBehavior());
501 builder.append(",connection=").append(connection);
502 builder.append(",remote=").append(remote);
503 builder.append(",incoming=").append(incomingHandler);
504 builder.append(",outgoing=").append(outgoingHandler);
505 builder.append("]");
506 return builder.toString();
507 }
508 }