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