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
64 protected SPDYClient(short version, Factory factory)
65 {
66 this.version = version;
67 this.factory = factory;
68 setInitialWindowSize(65536);
69 }
70
71
72
73
74
75 public SocketAddress getBindAddress()
76 {
77 return bindAddress;
78 }
79
80
81
82
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
257 return isRunning() && sessions.offer(session);
258 }
259
260 boolean sessionClosed(Session session)
261 {
262
263
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 }