View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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          // Get the parameter map (use the jetty MultiMap to do this right)
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      * Harsh disconnect
126      */
127     @Override
128     public void disconnect()
129     {
130         connection.disconnect();
131 
132         // notify of harsh disconnect
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      * The idle timeout in milliseconds
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      * Incoming Errors from Parser
293      */
294     @Override
295     public void incomingError(WebSocketException e)
296     {
297         if (connection.getIOState().isInputAvailable())
298         {
299             // Forward Errors to User WebSocket Object
300             websocket.incomingError(e);
301         }
302     }
303 
304     /**
305      * Incoming Raw Frames from Parser
306      */
307     @Override
308     public void incomingFrame(Frame frame)
309     {
310         if (connection.getIOState().isInputAvailable())
311         {
312             // Forward Frames Through Extension List
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             // The session only cares about abnormal close, as we need to notify
352             // the endpoint of this close scenario.
353             if (ioState.wasAbnormalClose())
354             {
355                 CloseInfo close = ioState.getCloseInfo();
356                 LOG.debug("Detected abnormal close: {}",close);
357                 // notify local endpoint
358                 notifyClose(close.getStatusCode(),close.getReason());
359             }
360         }
361     }
362 
363     /**
364      * Open/Activate the session
365      */
366     public void open()
367     {
368         if (remote != null)
369         {
370             // already opened
371             return;
372         }
373 
374         // Upgrade success
375         connection.getIOState().onConnected();
376 
377         // Connect remote
378         remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
379 
380         // Open WebSocket
381         websocket.openSession(this);
382 
383         // Open connection
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      * Set the timeout in milliseconds
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         // TODO Auto-generated method stub
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 }