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