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 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
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 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
268 return isRunning() && sessions.offer(session);
269 }
270
271 boolean sessionClosed(Session session)
272 {
273
274
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 }