View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 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.BatchMode;
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.WebSocketBehavior;
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 final SessionListener[] sessionListeners;
63      private final Executor executor;
64      private ClassLoader classLoader;
65      private ExtensionFactory extensionFactory;
66      private String protocolVersion;
67      private Map<String, String[]> parameterMap = new HashMap<>();
68      private WebSocketRemoteEndpoint remote;
69      private IncomingFrames incomingHandler;
70      private OutgoingFrames outgoingHandler;
71      private WebSocketPolicy policy;
72      private UpgradeRequest upgradeRequest;
73      private UpgradeResponse upgradeResponse;
74  
75      public WebSocketSession(URI requestURI, EventDriver websocket, LogicalConnection connection, SessionListener... sessionListeners)
76      {
77          if (requestURI == null)
78          {
79              throw new RuntimeException("Request URI cannot be null");
80          }
81  
82          this.classLoader = Thread.currentThread().getContextClassLoader();
83          this.requestURI = requestURI;
84          this.websocket = websocket;
85          this.connection = connection;
86          this.sessionListeners = sessionListeners;
87          this.executor = connection.getExecutor();
88          this.outgoingHandler = connection;
89          this.incomingHandler = websocket;
90          this.connection.getIOState().addListener(this);
91      }
92  
93      @Override
94      public void close()
95      {
96          this.close(StatusCode.NORMAL,null);
97      }
98  
99      @Override
100     public void close(CloseStatus closeStatus)
101     {
102         this.close(closeStatus.getCode(),closeStatus.getPhrase());
103     }
104 
105     @Override
106     public void close(int statusCode, String reason)
107     {
108         connection.close(statusCode,reason);
109     }
110 
111     /**
112      * Harsh disconnect
113      */
114     @Override
115     public void disconnect()
116     {
117         connection.disconnect();
118 
119         // notify of harsh disconnect
120         notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
121     }
122 
123     public void dispatch(Runnable runnable)
124     {
125         executor.execute(runnable);
126     }
127 
128     @Override
129     public void dump(Appendable out, String indent) throws IOException
130     {
131         dumpThis(out);
132         out.append(indent).append(" +- incomingHandler : ");
133         if (incomingHandler instanceof Dumpable)
134         {
135             ((Dumpable)incomingHandler).dump(out,indent + "    ");
136         }
137         else
138         {
139             out.append(incomingHandler.toString()).append(System.lineSeparator());
140         }
141 
142         out.append(indent).append(" +- outgoingHandler : ");
143         if (outgoingHandler instanceof Dumpable)
144         {
145             ((Dumpable)outgoingHandler).dump(out,indent + "    ");
146         }
147         else
148         {
149             out.append(outgoingHandler.toString()).append(System.lineSeparator());
150         }
151     }
152 
153     @Override
154     public boolean equals(Object obj)
155     {
156         if (this == obj)
157         {
158             return true;
159         }
160         if (obj == null)
161         {
162             return false;
163         }
164         if (getClass() != obj.getClass())
165         {
166             return false;
167         }
168         WebSocketSession other = (WebSocketSession)obj;
169         if (connection == null)
170         {
171             if (other.connection != null)
172             {
173                 return false;
174             }
175         }
176         else if (!connection.equals(other.connection))
177         {
178             return false;
179         }
180         return true;
181     }
182 
183     public ByteBufferPool getBufferPool()
184     {
185         return this.connection.getBufferPool();
186     }
187     
188     public ClassLoader getClassLoader()
189     {
190         return this.getClass().getClassLoader();
191     }
192 
193     public LogicalConnection getConnection()
194     {
195         return connection;
196     }
197 
198     public ExtensionFactory getExtensionFactory()
199     {
200         return extensionFactory;
201     }
202 
203     /**
204      * The idle timeout in milliseconds
205      */
206     @Override
207     public long getIdleTimeout()
208     {
209         return connection.getMaxIdleTimeout();
210     }
211 
212     @ManagedAttribute(readonly = true)
213     public IncomingFrames getIncomingHandler()
214     {
215         return incomingHandler;
216     }
217 
218     @Override
219     public InetSocketAddress getLocalAddress()
220     {
221         return connection.getLocalAddress();
222     }
223 
224     @ManagedAttribute(readonly = true)
225     public OutgoingFrames getOutgoingHandler()
226     {
227         return outgoingHandler;
228     }
229 
230     @Override
231     public WebSocketPolicy getPolicy()
232     {
233         return policy;
234     }
235 
236     @Override
237     public String getProtocolVersion()
238     {
239         return protocolVersion;
240     }
241 
242     @Override
243     public RemoteEndpoint getRemote()
244     {
245         ConnectionState state = connection.getIOState().getConnectionState();
246 
247         if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
248         {
249             return remote;
250         }
251 
252         throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
253     }
254 
255     @Override
256     public InetSocketAddress getRemoteAddress()
257     {
258         return remote.getInetSocketAddress();
259     }
260 
261     public URI getRequestURI()
262     {
263         return requestURI;
264     }
265 
266     @Override
267     public UpgradeRequest getUpgradeRequest()
268     {
269         return this.upgradeRequest;
270     }
271 
272     @Override
273     public UpgradeResponse getUpgradeResponse()
274     {
275         return this.upgradeResponse;
276     }
277 
278     @Override
279     public int hashCode()
280     {
281         final int prime = 31;
282         int result = 1;
283         result = (prime * result) + ((connection == null)?0:connection.hashCode());
284         return result;
285     }
286 
287     /**
288      * Incoming Errors from Parser
289      */
290     @Override
291     public void incomingError(Throwable t)
292     {
293         if (connection.getIOState().isInputAvailable())
294         {
295             // Forward Errors to User WebSocket Object
296             websocket.incomingError(t);
297         }
298     }
299 
300     /**
301      * Incoming Raw Frames from Parser
302      */
303     @Override
304     public void incomingFrame(Frame frame)
305     {
306         if (connection.getIOState().isInputAvailable())
307         {
308             // Forward Frames Through Extension List
309             incomingHandler.incomingFrame(frame);
310         }
311     }
312 
313     @Override
314     public boolean isOpen()
315     {
316         if (this.connection == null)
317         {
318             return false;
319         }
320         return this.connection.isOpen();
321     }
322 
323     @Override
324     public boolean isSecure()
325     {
326         if (upgradeRequest == null)
327         {
328             throw new IllegalStateException("No valid UpgradeRequest yet");
329         }
330 
331         URI requestURI = upgradeRequest.getRequestURI();
332 
333         return "wss".equalsIgnoreCase(requestURI.getScheme());
334     }
335 
336     public void notifyClose(int statusCode, String reason)
337     {
338         if (LOG.isDebugEnabled())
339         {
340             LOG.debug("notifyClose({},{})",statusCode,reason);
341         }
342         websocket.onClose(new CloseInfo(statusCode,reason));
343     }
344 
345     public void notifyError(Throwable cause)
346     {
347         incomingError(cause);
348     }
349 
350     @SuppressWarnings("incomplete-switch")
351     @Override
352     public void onConnectionStateChange(ConnectionState state)
353     {
354         switch (state)
355         {
356             case CLOSED:
357                 // notify session listeners
358                 for (SessionListener listener : sessionListeners)
359                 {
360                     try
361                     {
362                         if (LOG.isDebugEnabled())
363                             LOG.debug("{}.onSessionClosed()",listener.getClass().getSimpleName());
364                         listener.onSessionClosed(this);
365                     }
366                     catch (Throwable t)
367                     {
368                         LOG.ignore(t);
369                     }
370                 }
371                 IOState ioState = this.connection.getIOState();
372                 CloseInfo close = ioState.getCloseInfo();
373                 // confirmed close of local endpoint
374                 notifyClose(close.getStatusCode(),close.getReason());
375                 break;
376             case OPEN:
377                 // notify session listeners
378                 for (SessionListener listener : sessionListeners)
379                 {
380                     try
381                     {
382                         listener.onSessionOpened(this);
383                     }
384                     catch (Throwable t)
385                     {
386                         LOG.ignore(t);
387                     }
388                 }
389                 break;
390         }
391     }
392 
393     /**
394      * Open/Activate the session
395      */
396     public void open()
397     {
398         if (remote != null)
399         {
400             // already opened
401             return;
402         }
403         
404         ClassLoader old = Thread.currentThread().getContextClassLoader();
405         try {
406             Thread.currentThread().setContextClassLoader(classLoader);
407 
408             // Upgrade success
409             connection.getIOState().onConnected();
410     
411             // Connect remote
412             remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
413 
414             // Open WebSocket
415             websocket.openSession(this);
416 
417             // Open connection
418             connection.getIOState().onOpened();
419 
420             if (LOG.isDebugEnabled())
421             {
422                 LOG.debug("open -> {}",dump());
423             }
424         }
425         catch (Throwable t)
426         {
427             // Exception on end-user WS-Endpoint.
428             // Fast-fail & close connection with reason.
429             int statusCode = StatusCode.SERVER_ERROR;
430             if(policy.getBehavior() == WebSocketBehavior.CLIENT)
431             {
432                 statusCode = StatusCode.POLICY_VIOLATION;
433             }
434             
435             close(statusCode,t.getMessage());
436         }
437         finally
438         {
439             Thread.currentThread().setContextClassLoader(old);
440         }
441     }
442 
443     public void setExtensionFactory(ExtensionFactory extensionFactory)
444     {
445         this.extensionFactory = extensionFactory;
446     }
447 
448     /**
449      * Set the timeout in milliseconds
450      */
451     @Override
452     public void setIdleTimeout(long ms)
453     {
454         connection.setMaxIdleTimeout(ms);
455     }
456 
457     public void setOutgoingHandler(OutgoingFrames outgoing)
458     {
459         this.outgoingHandler = outgoing;
460     }
461 
462     public void setPolicy(WebSocketPolicy policy)
463     {
464         this.policy = policy;
465     }
466 
467     public void setUpgradeRequest(UpgradeRequest request)
468     {
469         this.upgradeRequest = request;
470         this.protocolVersion = request.getProtocolVersion();
471         this.parameterMap.clear();
472         if (request.getParameterMap() != null)
473         {
474             for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
475             {
476                 List<String> values = entry.getValue();
477                 if (values != null)
478                 {
479                     this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
480                 }
481                 else
482                 {
483                     this.parameterMap.put(entry.getKey(),new String[0]);
484                 }
485             }
486         }
487     }
488 
489     public void setUpgradeResponse(UpgradeResponse response)
490     {
491         this.upgradeResponse = response;
492     }
493 
494     @Override
495     public SuspendToken suspend()
496     {
497         return connection.suspend();
498     }
499 
500     /**
501      * @return the default (initial) value for the batching mode.
502      */
503     public BatchMode getBatchMode()
504     {
505         return BatchMode.AUTO;
506     }
507 
508     @Override
509     public String toString()
510     {
511         StringBuilder builder = new StringBuilder();
512         builder.append("WebSocketSession[");
513         builder.append("websocket=").append(websocket);
514         builder.append(",behavior=").append(policy.getBehavior());
515         builder.append(",connection=").append(connection);
516         builder.append(",remote=").append(remote);
517         builder.append(",incoming=").append(incomingHandler);
518         builder.append(",outgoing=").append(outgoingHandler);
519         builder.append("]");
520         return builder.toString();
521     }
522 
523 }