1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
74
75
76 public SocketAddress getBindAddress()
77 {
78 return bindAddress;
79 }
80
81
82
83
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
258 return isRunning() && sessions.offer(session);
259 }
260
261 boolean sessionClosed(Session session)
262 {
263
264
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 }