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.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      * Harsh disconnect
109      */
110     @Override
111     public void disconnect()
112     {
113         connection.disconnect();
114 
115         // notify of harsh disconnect
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      * The idle timeout in milliseconds
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      * Incoming Errors from Parser
280      */
281     @Override
282     public void incomingError(Throwable t)
283     {
284         if (connection.getIOState().isInputAvailable())
285         {
286             // Forward Errors to User WebSocket Object
287             websocket.incomingError(t);
288         }
289     }
290 
291     /**
292      * Incoming Raw Frames from Parser
293      */
294     @Override
295     public void incomingFrame(Frame frame)
296     {
297         if (connection.getIOState().isInputAvailable())
298         {
299             // Forward Frames Through Extension List
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             // The session only cares about abnormal close, as we need to notify
344             // the endpoint of this close scenario.
345             if (ioState.wasAbnormalClose())
346             {
347                 CloseInfo close = ioState.getCloseInfo();
348                 LOG.debug("Detected abnormal close: {}",close);
349                 // notify local endpoint
350                 notifyClose(close.getStatusCode(),close.getReason());
351             }
352         }
353     }
354 
355     /**
356      * Open/Activate the session
357      */
358     public void open()
359     {
360         if (remote != null)
361         {
362             // already opened
363             return;
364         }
365 
366         // Upgrade success
367         connection.getIOState().onConnected();
368 
369         // Connect remote
370         remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
371 
372         // Open WebSocket
373         websocket.openSession(this);
374 
375         // Open connection
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      * Set the timeout in milliseconds
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 }