View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.client.io;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.net.SocketAddress;
24  import java.net.URI;
25  import java.nio.channels.SocketChannel;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.Locale;
29  import java.util.Queue;
30  import java.util.concurrent.ConcurrentLinkedQueue;
31  
32  import org.eclipse.jetty.util.component.ContainerLifeCycle;
33  import org.eclipse.jetty.util.log.Log;
34  import org.eclipse.jetty.util.log.Logger;
35  import org.eclipse.jetty.websocket.api.StatusCode;
36  import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
37  import org.eclipse.jetty.websocket.client.WebSocketClient;
38  import org.eclipse.jetty.websocket.common.WebSocketSession;
39  import org.eclipse.jetty.websocket.common.events.EventDriver;
40  
41  /**
42   * Internal Connection/Client Manager used to track active clients, their physical vs virtual connection information, and provide some means to create new
43   * physical or virtual connections.
44   */
45  public class ConnectionManager extends ContainerLifeCycle
46  {
47      private class PhysicalConnect extends ConnectPromise
48      {
49          private SocketAddress bindAddress;
50  
51          public PhysicalConnect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
52          {
53              super(client,driver,request);
54              this.bindAddress = client.getBindAddress();
55          }
56  
57          @Override
58          public void run()
59          {
60              SocketChannel channel = null;
61              try
62              {
63                  channel = SocketChannel.open();
64                  if (bindAddress != null)
65                  {
66                      channel.bind(bindAddress);
67                  }
68  
69                  URI wsUri = getRequest().getRequestURI();
70  
71                  channel.socket().setTcpNoDelay(true); // disable nagle
72                  channel.configureBlocking(false); // async always
73  
74                  InetSocketAddress address = toSocketAddress(wsUri);
75  
76                  if (channel.connect(address))
77                  {
78                      getSelector().accept(channel, this);
79                  }
80                  else
81                  {
82                      getSelector().connect(channel, this);
83                  }
84              }
85              catch (Throwable t)
86              {
87                  // close the socket channel
88                  if (channel != null)
89                  {
90                      try
91                      {
92                          channel.close();
93                      }
94                      catch (IOException ignore)
95                      {
96                          LOG.ignore(ignore);
97                      }
98                  }
99                  
100                 // notify the future
101                 failed(t);
102             }
103         }
104     }
105 
106     private static final Logger LOG = Log.getLogger(ConnectionManager.class);
107 
108     public static InetSocketAddress toSocketAddress(URI uri)
109     {
110         if (!uri.isAbsolute())
111         {
112             throw new IllegalArgumentException("Cannot get InetSocketAddress of non-absolute URIs");
113         }
114 
115         int port = uri.getPort();
116         String scheme = uri.getScheme().toLowerCase(Locale.ENGLISH);
117         if ("ws".equals(scheme))
118         {
119             if (port == (-1))
120             {
121                 port = 80;
122             }
123         }
124         else if ("wss".equals(scheme))
125         {
126             if (port == (-1))
127             {
128                 port = 443;
129             }
130         }
131         else
132         {
133             throw new IllegalArgumentException("Only support ws:// and wss:// URIs");
134         }
135 
136         return new InetSocketAddress(uri.getHost(),port);
137     }
138 
139     private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
140     private final WebSocketClient client;
141     private WebSocketClientSelectorManager selector;
142 
143     public ConnectionManager(WebSocketClient client)
144     {
145         this.client = client;
146     }
147 
148     public void addSession(WebSocketSession session)
149     {
150         sessions.add(session);
151     }
152 
153     private void shutdownAllConnections()
154     {
155         for (WebSocketSession session : sessions)
156         {
157             if (session.getConnection() != null)
158             {
159                 try
160                 {
161                     session.getConnection().close(
162                             StatusCode.SHUTDOWN,
163                             "Shutdown");
164                 }
165                 catch (Throwable t)
166                 {
167                     LOG.debug("During Shutdown All Connections",t);
168                 }
169             }
170         }
171     }
172 
173     public ConnectPromise connect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request)
174     {
175         return new PhysicalConnect(client,driver,request);
176     }
177 
178     @Override
179     protected void doStart() throws Exception
180     {
181         selector = newWebSocketClientSelectorManager(client);
182         selector.setSslContextFactory(client.getSslContextFactory());
183         selector.setConnectTimeout(client.getConnectTimeout());
184         addBean(selector);
185 
186         super.doStart();
187     }
188 
189     @Override
190     protected void doStop() throws Exception
191     {
192         shutdownAllConnections();
193         sessions.clear();
194         super.doStop();
195         removeBean(selector);
196     }
197 
198     public WebSocketClientSelectorManager getSelector()
199     {
200         return selector;
201     }
202 
203     public Collection<WebSocketSession> getSessions()
204     {
205         return Collections.unmodifiableCollection(sessions);
206     }
207 
208     /**
209      * Factory method for new WebSocketClientSelectorManager (used by other projects like cometd)
210      * 
211      * @param client
212      *            the client used to create the WebSocketClientSelectorManager
213      * @return the new WebSocketClientSelectorManager
214      */
215     protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client)
216     {
217         return new WebSocketClientSelectorManager(client);
218     }
219 
220     public void removeSession(WebSocketSession session)
221     {
222         sessions.remove(session);
223     }
224 }