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