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