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      private volatile boolean executeOnFillable;
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     public boolean isExecuteOnFillable()
130     {
131         return executeOnFillable;
132     }
133 
134     public void setExecuteOnFillable(boolean executeOnFillable)
135     {
136         this.executeOnFillable = executeOnFillable;
137     }
138 
139     protected String selectProtocol(List<String> serverProtocols)
140     {
141         String protocol = "spdy/" + version;
142         for (String serverProtocol : serverProtocols)
143         {
144             if (serverProtocol.equals(protocol))
145                 return protocol;
146         }
147         return null;
148     }
149 
150     public SPDYClientConnectionFactory getConnectionFactory()
151     {
152         return connectionFactory;
153     }
154 
155     protected SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
156     {
157         String peerHost = channel.socket().getInetAddress().getHostName();
158         int peerPort = channel.socket().getPort();
159         SSLEngine engine = sslContextFactory.newSSLEngine(peerHost, peerPort);
160         engine.setUseClientMode(true);
161         return engine;
162     }
163 
164     protected FlowControlStrategy newFlowControlStrategy()
165     {
166         return FlowControlStrategyFactory.newFlowControlStrategy(version);
167     }
168 
169     public static class Factory extends ContainerLifeCycle
170     {
171         private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
172         private final ByteBufferPool bufferPool = new MappedByteBufferPool();
173         private final Scheduler scheduler;
174         private final Executor executor;
175         private final SslContextFactory sslContextFactory;
176         private final SelectorManager selector;
177         private final long idleTimeout;
178         private long connectTimeout = 15000;
179 
180         public Factory()
181         {
182             this(null, null);
183         }
184 
185         public Factory(SslContextFactory sslContextFactory)
186         {
187             this(null, null, sslContextFactory);
188         }
189 
190         public Factory(Executor executor)
191         {
192             this(executor, null);
193         }
194 
195         public Factory(Executor executor, Scheduler scheduler)
196         {
197             this(executor, scheduler, null);
198         }
199 
200         public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory)
201         {
202             this(executor, scheduler, sslContextFactory, 30000);
203         }
204 
205         public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory, long idleTimeout)
206         {
207             this.idleTimeout = idleTimeout;
208 
209             if (executor == null)
210                 executor = new QueuedThreadPool();
211             this.executor = executor;
212             addBean(executor);
213 
214             if (scheduler == null)
215                 scheduler = new ScheduledExecutorScheduler();
216             this.scheduler = scheduler;
217             addBean(scheduler);
218 
219             this.sslContextFactory = sslContextFactory;
220             if (sslContextFactory != null)
221                 addBean(sslContextFactory);
222 
223             selector = new ClientSelectorManager(executor, scheduler);
224             selector.setConnectTimeout(getConnectTimeout());
225             addBean(selector);
226         }
227 
228         public ByteBufferPool getByteBufferPool()
229         {
230             return bufferPool;
231         }
232 
233         public Scheduler getScheduler()
234         {
235             return scheduler;
236         }
237 
238         public Executor getExecutor()
239         {
240             return executor;
241         }
242 
243         public long getConnectTimeout()
244         {
245             return connectTimeout;
246         }
247 
248         public void setConnectTimeout(long connectTimeout)
249         {
250             this.connectTimeout = connectTimeout;
251         }
252 
253         public SPDYClient newSPDYClient(short version)
254         {
255             return new SPDYClient(version, this);
256         }
257 
258         @Override
259         protected void doStop() throws Exception
260         {
261             closeConnections();
262             super.doStop();
263         }
264 
265         boolean sessionOpened(Session session)
266         {
267             // Add sessions only if the factory is not stopping
268             return isRunning() && sessions.offer(session);
269         }
270 
271         boolean sessionClosed(Session session)
272         {
273             // Remove sessions only if the factory is not stopping
274             // to avoid concurrent removes during iterations
275             return isRunning() && sessions.remove(session);
276         }
277 
278         private void closeConnections()
279         {
280             for (Session session : sessions)
281                 session.goAway(new GoAwayInfo(), new Callback.Adapter());
282             sessions.clear();
283         }
284 
285         public Collection<Session> getSessions()
286         {
287             return Collections.unmodifiableCollection(sessions);
288         }
289 
290         private class ClientSelectorManager extends SelectorManager
291         {
292             private ClientSelectorManager(Executor executor, Scheduler scheduler)
293             {
294                 super(executor, scheduler);
295             }
296 
297             @Override
298             protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
299             {
300                 SessionPromise attachment = (SessionPromise)key.attachment();
301 
302                 long clientIdleTimeout = attachment.client.getIdleTimeout();
303                 if (clientIdleTimeout < 0)
304                     clientIdleTimeout = idleTimeout;
305 
306                 return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
307             }
308 
309             @Override
310             public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment)
311             {
312                 SessionPromise sessionPromise = (SessionPromise)attachment;
313                 final SPDYClient client = sessionPromise.client;
314 
315                 try
316                 {
317                     if (sslContextFactory != null)
318                     {
319                         final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
320                         SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
321                         sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
322                         DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
323                         NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client);
324                         sslEndPoint.setConnection(connection);
325                         return sslConnection;
326                     }
327 
328                     SPDYClientConnectionFactory connectionFactory = new SPDYClientConnectionFactory();
329                     return connectionFactory.newConnection(channel, endPoint, attachment);
330                 }
331                 catch (RuntimeException x)
332                 {
333                     sessionPromise.failed(x);
334                     throw x;
335                 }
336             }
337         }
338     }
339 
340     static class SessionPromise extends FuturePromise<Session>
341     {
342         private final SocketChannel channel;
343         final SPDYClient client;
344         final SessionFrameListener listener;
345 
346         private SessionPromise(SocketChannel channel, SPDYClient client, SessionFrameListener listener)
347         {
348             this.channel = channel;
349             this.client = client;
350             this.listener = listener;
351         }
352 
353         @Override
354         public boolean cancel(boolean mayInterruptIfRunning)
355         {
356             try
357             {
358                 super.cancel(mayInterruptIfRunning);
359                 channel.close();
360                 return true;
361             }
362             catch (IOException x)
363             {
364                 return true;
365             }
366         }
367     }
368 }