View Javadoc
1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Objects;
29  import java.util.ServiceLoader;
30  import java.util.concurrent.CompletableFuture;
31  import java.util.concurrent.Executor;
32  
33  import org.eclipse.jetty.io.ByteBufferPool;
34  import org.eclipse.jetty.io.Connection;
35  import org.eclipse.jetty.util.annotation.ManagedAttribute;
36  import org.eclipse.jetty.util.annotation.ManagedObject;
37  import org.eclipse.jetty.util.component.ContainerLifeCycle;
38  import org.eclipse.jetty.util.component.Dumpable;
39  import org.eclipse.jetty.util.log.Log;
40  import org.eclipse.jetty.util.log.Logger;
41  import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
42  import org.eclipse.jetty.websocket.api.BatchMode;
43  import org.eclipse.jetty.websocket.api.CloseException;
44  import org.eclipse.jetty.websocket.api.CloseStatus;
45  import org.eclipse.jetty.websocket.api.RemoteEndpoint;
46  import org.eclipse.jetty.websocket.api.Session;
47  import org.eclipse.jetty.websocket.api.StatusCode;
48  import org.eclipse.jetty.websocket.api.SuspendToken;
49  import org.eclipse.jetty.websocket.api.UpgradeRequest;
50  import org.eclipse.jetty.websocket.api.UpgradeResponse;
51  import org.eclipse.jetty.websocket.api.WebSocketBehavior;
52  import org.eclipse.jetty.websocket.api.WebSocketException;
53  import org.eclipse.jetty.websocket.api.WebSocketPolicy;
54  import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
55  import org.eclipse.jetty.websocket.api.extensions.Frame;
56  import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
57  import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
58  import org.eclipse.jetty.websocket.common.events.EventDriver;
59  import org.eclipse.jetty.websocket.common.io.IOState;
60  import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
61  import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
62  import org.eclipse.jetty.websocket.common.scopes.WebSocketSessionScope;
63  
64  @ManagedObject("A Jetty WebSocket Session")
65  public class WebSocketSession extends ContainerLifeCycle implements Session, RemoteEndpointFactory, WebSocketSessionScope, IncomingFrames, Connection.Listener, ConnectionStateListener
66  {
67      private static final Logger LOG = Log.getLogger(WebSocketSession.class);
68      private static final Logger LOG_OPEN = Log.getLogger(WebSocketSession.class.getName() + "_OPEN");
69      private final WebSocketContainerScope containerScope;
70      private final URI requestURI;
71      private final LogicalConnection connection;
72      private final EventDriver websocket;
73      private final Executor executor;
74      private final WebSocketPolicy policy;
75      private ClassLoader classLoader;
76      private ExtensionFactory extensionFactory;
77      private RemoteEndpointFactory remoteEndpointFactory;
78      private String protocolVersion;
79      private Map<String, String[]> parameterMap = new HashMap<>();
80      private RemoteEndpoint remote;
81      private IncomingFrames incomingHandler;
82      private OutgoingFrames outgoingHandler;
83      private UpgradeRequest upgradeRequest;
84      private UpgradeResponse upgradeResponse;
85      private CompletableFuture<Session> openFuture;
86  
87      public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, EventDriver websocket, LogicalConnection connection)
88      {
89          Objects.requireNonNull(containerScope,"Container Scope cannot be null");
90          Objects.requireNonNull(requestURI,"Request URI cannot be null");
91  
92          this.classLoader = Thread.currentThread().getContextClassLoader();
93          this.containerScope = containerScope;
94          this.requestURI = requestURI;
95          this.websocket = websocket;
96          this.connection = connection;
97          this.executor = connection.getExecutor();
98          this.outgoingHandler = connection;
99          this.incomingHandler = websocket;
100         this.connection.getIOState().addListener(this);
101         this.policy = websocket.getPolicy();
102 
103         addBean(this.connection);
104         addBean(this.websocket);
105     }
106 
107     @Override
108     public void close()
109     {
110         /* This is assumed to always be a NORMAL closure, no reason phrase */
111         close(StatusCode.NORMAL, null);
112     }
113 
114     @Override
115     public void close(CloseStatus closeStatus)
116     {
117         close(closeStatus.getCode(),closeStatus.getPhrase());
118     }
119 
120     @Override
121     public void close(int statusCode, String reason)
122     {
123         connection.close(statusCode,reason);
124     }
125 
126     /**
127      * Harsh disconnect
128      */
129     @Override
130     public void disconnect()
131     {
132         connection.disconnect();
133 
134         // notify of harsh disconnect
135         notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
136     }
137 
138     public void dispatch(Runnable runnable)
139     {
140         executor.execute(runnable);
141     }
142 
143     @Override
144     protected void doStart() throws Exception
145     {
146         if(LOG.isDebugEnabled())
147             LOG.debug("starting - {}",this);
148 
149         Iterator<RemoteEndpointFactory> iter = ServiceLoader.load(RemoteEndpointFactory.class).iterator();
150         if (iter.hasNext())
151             remoteEndpointFactory = iter.next();
152 
153         if (remoteEndpointFactory == null)
154             remoteEndpointFactory = this;
155 
156         if (LOG.isDebugEnabled())
157             LOG.debug("Using RemoteEndpointFactory: {}", remoteEndpointFactory);
158 
159         super.doStart();
160     }
161 
162     @Override
163     protected void doStop() throws Exception
164     {
165         if(LOG.isDebugEnabled())
166             LOG.debug("stopping - {}",this);
167         try
168         {
169             close(StatusCode.SHUTDOWN,"Shutdown");
170         }
171         catch (Throwable t)
172         {
173             LOG.debug("During Connection Shutdown",t);
174         }
175         super.doStop();
176     }
177 
178     @Override
179     public void dump(Appendable out, String indent) throws IOException
180     {
181         dumpThis(out);
182         out.append(indent).append(" +- incomingHandler : ");
183         if (incomingHandler instanceof Dumpable)
184         {
185             ((Dumpable)incomingHandler).dump(out,indent + "    ");
186         }
187         else
188         {
189             out.append(incomingHandler.toString()).append(System.lineSeparator());
190         }
191 
192         out.append(indent).append(" +- outgoingHandler : ");
193         if (outgoingHandler instanceof Dumpable)
194         {
195             ((Dumpable)outgoingHandler).dump(out,indent + "    ");
196         }
197         else
198         {
199             out.append(outgoingHandler.toString()).append(System.lineSeparator());
200         }
201     }
202 
203     @Override
204     public boolean equals(Object obj)
205     {
206         if (this == obj)
207         {
208             return true;
209         }
210         if (obj == null)
211         {
212             return false;
213         }
214         if (getClass() != obj.getClass())
215         {
216             return false;
217         }
218         WebSocketSession other = (WebSocketSession)obj;
219         if (connection == null)
220         {
221             if (other.connection != null)
222             {
223                 return false;
224             }
225         }
226         else if (!connection.equals(other.connection))
227         {
228             return false;
229         }
230         return true;
231     }
232 
233     public ByteBufferPool getBufferPool()
234     {
235         return this.connection.getBufferPool();
236     }
237 
238     public ClassLoader getClassLoader()
239     {
240         return this.getClass().getClassLoader();
241     }
242 
243     public LogicalConnection getConnection()
244     {
245         return connection;
246     }
247 
248     @Override
249     public WebSocketContainerScope getContainerScope()
250     {
251         return this.containerScope;
252     }
253 
254     public ExtensionFactory getExtensionFactory()
255     {
256         return extensionFactory;
257     }
258 
259     /**
260      * The idle timeout in milliseconds
261      */
262     @Override
263     public long getIdleTimeout()
264     {
265         return connection.getMaxIdleTimeout();
266     }
267 
268     @ManagedAttribute(readonly = true)
269     public IncomingFrames getIncomingHandler()
270     {
271         return incomingHandler;
272     }
273 
274     @Override
275     public InetSocketAddress getLocalAddress()
276     {
277         return connection.getLocalAddress();
278     }
279 
280     @ManagedAttribute(readonly = true)
281     public OutgoingFrames getOutgoingHandler()
282     {
283         return outgoingHandler;
284     }
285 
286     @Override
287     public WebSocketPolicy getPolicy()
288     {
289         return policy;
290     }
291 
292     @Override
293     public String getProtocolVersion()
294     {
295         return protocolVersion;
296     }
297 
298     @Override
299     public RemoteEndpoint getRemote()
300     {
301         if(LOG_OPEN.isDebugEnabled())
302             LOG_OPEN.debug("[{}] {}.getRemote()",policy.getBehavior(),this.getClass().getSimpleName());
303         ConnectionState state = connection.getIOState().getConnectionState();
304 
305         if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
306         {
307             return remote;
308         }
309 
310         throw new WebSocketException("RemoteEndpoint unavailable, current state [" + state + "], expecting [OPEN or CONNECTED]");
311     }
312 
313     @Override
314     public InetSocketAddress getRemoteAddress()
315     {
316         return remote.getInetSocketAddress();
317     }
318 
319     public URI getRequestURI()
320     {
321         return requestURI;
322     }
323 
324     @Override
325     public UpgradeRequest getUpgradeRequest()
326     {
327         return this.upgradeRequest;
328     }
329 
330     @Override
331     public UpgradeResponse getUpgradeResponse()
332     {
333         return this.upgradeResponse;
334     }
335 
336 
337     @Override
338     public WebSocketSession getWebSocketSession()
339     {
340         return this;
341     }
342 
343     @Override
344     public int hashCode()
345     {
346         final int prime = 31;
347         int result = 1;
348         result = (prime * result) + ((connection == null)?0:connection.hashCode());
349         return result;
350     }
351 
352     /**
353      * Incoming Errors
354      */
355     @Override
356     public void incomingError(Throwable t)
357     {
358         // Forward Errors to User WebSocket Object
359         websocket.incomingError(t);
360     }
361 
362     /**
363      * Incoming Raw Frames from Parser
364      */
365     @Override
366     public void incomingFrame(Frame frame)
367     {
368         ClassLoader old = Thread.currentThread().getContextClassLoader();
369         try
370         {
371             Thread.currentThread().setContextClassLoader(classLoader);
372             if (connection.getIOState().isInputAvailable())
373             {
374                 // Forward Frames Through Extension List
375                 incomingHandler.incomingFrame(frame);
376             }
377         }
378         finally
379         {
380             Thread.currentThread().setContextClassLoader(old);
381         }
382     }
383 
384     @Override
385     public boolean isOpen()
386     {
387         if (this.connection == null)
388         {
389             return false;
390         }
391         return this.connection.isOpen();
392     }
393 
394     @Override
395     public boolean isSecure()
396     {
397         if (upgradeRequest == null)
398         {
399             throw new IllegalStateException("No valid UpgradeRequest yet");
400         }
401 
402         URI requestURI = upgradeRequest.getRequestURI();
403 
404         return "wss".equalsIgnoreCase(requestURI.getScheme());
405     }
406 
407     public void notifyClose(int statusCode, String reason)
408     {
409         if (LOG.isDebugEnabled())
410         {
411             LOG.debug("notifyClose({},{})",statusCode,reason);
412         }
413         websocket.onClose(new CloseInfo(statusCode,reason));
414     }
415 
416     public void notifyError(Throwable cause)
417     {
418         if (openFuture != null && !openFuture.isDone())
419             openFuture.completeExceptionally(cause);
420         incomingError(cause);
421     }
422 
423     @Override
424     public void onClosed(Connection connection)
425     {
426     }
427 
428     @Override
429     public void onOpened(Connection connection)
430     {
431         if(LOG_OPEN.isDebugEnabled())
432             LOG_OPEN.debug("[{}] {}.onOpened()",policy.getBehavior(),this.getClass().getSimpleName());
433         open();
434     }
435 
436     @SuppressWarnings("incomplete-switch")
437     @Override
438     public void onConnectionStateChange(ConnectionState state)
439     {
440         switch (state)
441         {
442             case CLOSED:
443                 IOState ioState = this.connection.getIOState();
444                 CloseInfo close = ioState.getCloseInfo();
445                 // confirmed close of local endpoint
446                 notifyClose(close.getStatusCode(),close.getReason());
447                 try
448                 {
449                     if (LOG.isDebugEnabled())
450                         LOG.debug("{}.onSessionClosed()",containerScope.getClass().getSimpleName());
451                     containerScope.onSessionClosed(this);
452                 }
453                 catch (Throwable t)
454                 {
455                     LOG.ignore(t);
456                 }
457                 break;
458             case CONNECTED:
459                 // notify session listeners
460                 try
461                 {
462                     if (LOG.isDebugEnabled())
463                         LOG.debug("{}.onSessionOpened()",containerScope.getClass().getSimpleName());
464                     containerScope.onSessionOpened(this);
465                 }
466                 catch (Throwable t)
467                 {
468                     LOG.ignore(t);
469                 }
470                 break;
471         }
472     }
473 
474     public WebSocketRemoteEndpoint newRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoingFrames, BatchMode batchMode)
475     {
476         return new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode());
477     }
478 
479     /**
480      * Open/Activate the session
481      */
482     public void open()
483     {
484         if(LOG_OPEN.isDebugEnabled())
485             LOG_OPEN.debug("[{}] {}.open()",policy.getBehavior(),this.getClass().getSimpleName());
486 
487         if (remote != null)
488         {
489             // already opened
490             return;
491         }
492 
493         try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader))
494         {
495             // Upgrade success
496             connection.getIOState().onConnected();
497 
498             // Connect remote
499             remote = remoteEndpointFactory.newRemoteEndpoint(connection,outgoingHandler,getBatchMode());
500             if(LOG_OPEN.isDebugEnabled())
501                 LOG_OPEN.debug("[{}] {}.open() remote={}",policy.getBehavior(),this.getClass().getSimpleName(),remote);
502 
503             // Open WebSocket
504             websocket.openSession(this);
505 
506             // Open connection
507             connection.getIOState().onOpened();
508 
509             if (LOG.isDebugEnabled())
510             {
511                 LOG.debug("open -> {}",dump());
512             }
513             
514             if(openFuture != null)
515             {
516                 openFuture.complete(this);
517             }
518         }
519         catch (CloseException ce)
520         {
521             LOG.warn(ce);
522             close(ce.getStatusCode(),ce.getMessage());
523         }
524         catch (Throwable t)
525         {
526             LOG.warn(t);
527             // Exception on end-user WS-Endpoint.
528             // Fast-fail & close connection with reason.
529             int statusCode = StatusCode.SERVER_ERROR;
530             if(policy.getBehavior() == WebSocketBehavior.CLIENT)
531             {
532                 statusCode = StatusCode.POLICY_VIOLATION;
533             }
534             close(statusCode,t.getMessage());
535         }
536     }
537 
538     public void setExtensionFactory(ExtensionFactory extensionFactory)
539     {
540         this.extensionFactory = extensionFactory;
541     }
542 
543     public void setFuture(CompletableFuture<Session> fut)
544     {
545         this.openFuture = fut;
546     }
547 
548     /**
549      * Set the timeout in milliseconds
550      */
551     @Override
552     public void setIdleTimeout(long ms)
553     {
554         connection.setMaxIdleTimeout(ms);
555     }
556 
557     public void setOutgoingHandler(OutgoingFrames outgoing)
558     {
559         this.outgoingHandler = outgoing;
560     }
561 
562     @Deprecated
563     public void setPolicy(WebSocketPolicy policy)
564     {
565         // do nothing
566     }
567 
568     public void setUpgradeRequest(UpgradeRequest request)
569     {
570         this.upgradeRequest = request;
571         this.protocolVersion = request.getProtocolVersion();
572         this.parameterMap.clear();
573         if (request.getParameterMap() != null)
574         {
575             for (Map.Entry<String, List<String>> entry : request.getParameterMap().entrySet())
576             {
577                 List<String> values = entry.getValue();
578                 if (values != null)
579                 {
580                     this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
581                 }
582                 else
583                 {
584                     this.parameterMap.put(entry.getKey(),new String[0]);
585                 }
586             }
587         }
588     }
589 
590     public void setUpgradeResponse(UpgradeResponse response)
591     {
592         this.upgradeResponse = response;
593     }
594 
595     @Override
596     public SuspendToken suspend()
597     {
598         return connection.suspend();
599     }
600 
601     /**
602      * @return the default (initial) value for the batching mode.
603      */
604     public BatchMode getBatchMode()
605     {
606         return BatchMode.AUTO;
607     }
608 
609     @Override
610     public String toString()
611     {
612         StringBuilder builder = new StringBuilder();
613         builder.append("WebSocketSession[");
614         builder.append("websocket=").append(websocket);
615         builder.append(",behavior=").append(policy.getBehavior());
616         builder.append(",connection=").append(connection);
617         builder.append(",remote=").append(remote);
618         builder.append(",incoming=").append(incomingHandler);
619         builder.append(",outgoing=").append(outgoingHandler);
620         builder.append("]");
621         return builder.toString();
622     }
623 
624     public static interface Listener
625     {
626         void onOpened(WebSocketSession session);
627 
628         void onClosed(WebSocketSession session);
629     }
630 }