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