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