View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.spdy.client;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.net.SocketAddress;
24  import java.nio.channels.SelectionKey;
25  import java.nio.channels.SocketChannel;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Queue;
30  import java.util.concurrent.ConcurrentLinkedQueue;
31  import java.util.concurrent.Executor;
32  import java.util.concurrent.Future;
33  
34  import javax.net.ssl.SSLEngine;
35  
36  import org.eclipse.jetty.io.ByteBufferPool;
37  import org.eclipse.jetty.io.Connection;
38  import org.eclipse.jetty.io.EndPoint;
39  import org.eclipse.jetty.io.MappedByteBufferPool;
40  import org.eclipse.jetty.io.SelectChannelEndPoint;
41  import org.eclipse.jetty.io.SelectorManager;
42  import org.eclipse.jetty.io.ssl.SslConnection;
43  import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
44  import org.eclipse.jetty.spdy.FlowControlStrategy;
45  import org.eclipse.jetty.spdy.api.GoAwayInfo;
46  import org.eclipse.jetty.spdy.api.Session;
47  import org.eclipse.jetty.spdy.api.SessionFrameListener;
48  import org.eclipse.jetty.util.Callback;
49  import org.eclipse.jetty.util.FuturePromise;
50  import org.eclipse.jetty.util.component.ContainerLifeCycle;
51  import org.eclipse.jetty.util.ssl.SslContextFactory;
52  import org.eclipse.jetty.util.thread.QueuedThreadPool;
53  import org.eclipse.jetty.util.thread.Scheduler;
54  import org.eclipse.jetty.util.thread.TimerScheduler;
55  
56  public class SPDYClient
57  {
58      private final SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
59      final short version;
60      final Factory factory;
61      private volatile SocketAddress bindAddress;
62      private volatile long idleTimeout = -1;
63      private volatile int initialWindowSize;
64  
65      protected SPDYClient(short version, Factory factory)
66      {
67          this.version = version;
68          this.factory = factory;
69          setInitialWindowSize(65536);
70      }
71  
72      /**
73       * @return the address to bind the socket channel to
74       * @see #setBindAddress(SocketAddress)
75       */
76      public SocketAddress getBindAddress()
77      {
78          return bindAddress;
79      }
80  
81      /**
82       * @param bindAddress the address to bind the socket channel to
83       * @see #getBindAddress()
84       */
85      public void setBindAddress(SocketAddress bindAddress)
86      {
87          this.bindAddress = bindAddress;
88      }
89  
90      public Future<Session> connect(InetSocketAddress address, SessionFrameListener listener) throws IOException
91      {
92          if (!factory.isStarted())
93              throw new IllegalStateException(Factory.class.getSimpleName() + " is not started");
94  
95          SocketChannel channel = SocketChannel.open();
96          if (bindAddress != null)
97              channel.bind(bindAddress);
98          channel.socket().setTcpNoDelay(true);
99          channel.configureBlocking(false);
100 
101         SessionPromise result = new SessionPromise(channel, this, listener);
102 
103         channel.connect(address);
104         factory.selector.connect(channel, result);
105 
106         return result;
107     }
108 
109     public long getIdleTimeout()
110     {
111         return idleTimeout;
112     }
113 
114     public void setIdleTimeout(long idleTimeout)
115     {
116         this.idleTimeout = idleTimeout;
117     }
118 
119     public int getInitialWindowSize()
120     {
121         return initialWindowSize;
122     }
123 
124     public void setInitialWindowSize(int initialWindowSize)
125     {
126         this.initialWindowSize = initialWindowSize;
127     }
128 
129     protected String selectProtocol(List<String> serverProtocols)
130     {
131         String protocol = "spdy/" + version;
132         for (String serverProtocol : serverProtocols)
133         {
134             if (serverProtocol.equals(protocol))
135                 return protocol;
136         }
137         return null;
138     }
139 
140     public SPDYClientConnectionFactory getConnectionFactory()
141     {
142         return connectionFactory;
143     }
144 
145     protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
146     {
147         String peerHost = channel.socket().getInetAddress().getHostAddress();
148         int peerPort = channel.socket().getPort();
149         SSLEngine engine = sslContextFactory.newSSLEngine(peerHost, peerPort);
150         engine.setUseClientMode(true);
151         return engine;
152     }
153 
154     protected FlowControlStrategy newFlowControlStrategy()
155     {
156         return FlowControlStrategyFactory.newFlowControlStrategy(version);
157     }
158 
159     public static class Factory extends ContainerLifeCycle
160     {
161         private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
162         private final ByteBufferPool bufferPool = new MappedByteBufferPool();
163         private final Scheduler scheduler;
164         private final Executor executor;
165         private final SslContextFactory sslContextFactory;
166         private final SelectorManager selector;
167         private final long idleTimeout;
168         private long connectTimeout = 15000;
169 
170         public Factory()
171         {
172             this(null, null);
173         }
174 
175         public Factory(SslContextFactory sslContextFactory)
176         {
177             this(null, null, sslContextFactory);
178         }
179 
180         public Factory(Executor executor)
181         {
182             this(executor, null);
183         }
184 
185         public Factory(Executor executor, Scheduler scheduler)
186         {
187             this(executor, scheduler, null);
188         }
189 
190         public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory)
191         {
192             this(executor, scheduler, sslContextFactory, 30000);
193         }
194 
195         public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory, long idleTimeout)
196         {
197             this.idleTimeout = idleTimeout;
198 
199             if (executor == null)
200                 executor = new QueuedThreadPool();
201             this.executor = executor;
202             addBean(executor);
203 
204             if (scheduler == null)
205                 scheduler = new TimerScheduler();
206             this.scheduler = scheduler;
207             addBean(scheduler);
208 
209             this.sslContextFactory = sslContextFactory;
210             if (sslContextFactory != null)
211                 addBean(sslContextFactory);
212 
213             selector = new ClientSelectorManager(executor, scheduler);
214             selector.setConnectTimeout(getConnectTimeout());
215             addBean(selector);
216         }
217 
218         public ByteBufferPool getByteBufferPool()
219         {
220             return bufferPool;
221         }
222 
223         public Scheduler getScheduler()
224         {
225             return scheduler;
226         }
227 
228         public Executor getExecutor()
229         {
230             return executor;
231         }
232 
233         public long getConnectTimeout()
234         {
235             return connectTimeout;
236         }
237 
238         public void setConnectTimeout(long connectTimeout)
239         {
240             this.connectTimeout = connectTimeout;
241         }
242 
243         public SPDYClient newSPDYClient(short version)
244         {
245             return new SPDYClient(version, this);
246         }
247 
248         @Override
249         protected void doStop() throws Exception
250         {
251             closeConnections();
252             super.doStop();
253         }
254 
255         boolean sessionOpened(Session session)
256         {
257             // Add sessions only if the factory is not stopping
258             return isRunning() && sessions.offer(session);
259         }
260 
261         boolean sessionClosed(Session session)
262         {
263             // Remove sessions only if the factory is not stopping
264             // to avoid concurrent removes during iterations
265             return isRunning() && sessions.remove(session);
266         }
267 
268         private void closeConnections()
269         {
270             for (Session session : sessions)
271                 session.goAway(new GoAwayInfo(), new Callback.Adapter());
272             sessions.clear();
273         }
274 
275         public Collection<Session> getSessions()
276         {
277             return Collections.unmodifiableCollection(sessions);
278         }
279 
280         private class ClientSelectorManager extends SelectorManager
281         {
282             private ClientSelectorManager(Executor executor, Scheduler scheduler)
283             {
284                 super(executor, scheduler);
285             }
286 
287             @Override
288             protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
289             {
290                 SessionPromise attachment = (SessionPromise)key.attachment();
291 
292                 long clientIdleTimeout = attachment.client.getIdleTimeout();
293                 if (clientIdleTimeout < 0)
294                     clientIdleTimeout = idleTimeout;
295 
296                 return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
297             }
298 
299             @Override
300             public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment)
301             {
302                 SessionPromise sessionPromise = (SessionPromise)attachment;
303                 final SPDYClient client = sessionPromise.client;
304 
305                 try
306                 {
307                     if (sslContextFactory != null)
308                     {
309                         final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
310                         SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
311                         DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
312                         NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client);
313                         sslEndPoint.setConnection(connection);
314                         return sslConnection;
315                     }
316 
317                     SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
318                     return connectionFactory.newConnection(channel, endPoint, attachment);
319                 }
320                 catch (RuntimeException x)
321                 {
322                     sessionPromise.failed(x);
323                     throw x;
324                 }
325             }
326         }
327     }
328 
329     static class SessionPromise extends FuturePromise<Session>
330     {
331         private final SocketChannel channel;
332         final SPDYClient client;
333         final SessionFrameListener listener;
334 
335         private SessionPromise(SocketChannel channel, SPDYClient client, SessionFrameListener listener)
336         {
337             this.channel = channel;
338             this.client = client;
339             this.listener = listener;
340         }
341 
342         @Override
343         public boolean cancel(boolean mayInterruptIfRunning)
344         {
345             try
346             {
347                 super.cancel(mayInterruptIfRunning);
348                 channel.close();
349                 return true;
350             }
351             catch (IOException x)
352             {
353                 return true;
354             }
355         }
356     }
357 }