jruby-http-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +72 -0
  7. data/Rakefile +1 -0
  8. data/example/config.ru +9 -0
  9. data/example/hello_world.rb +17 -0
  10. data/example/rack.rb +11 -0
  11. data/example/ring.rb +31 -0
  12. data/example/sinatra/app.rb +9 -0
  13. data/example/sinatra/config.ru +8 -0
  14. data/example/webmachine/Gemfile +4 -0
  15. data/example/webmachine/Gemfile.lock +16 -0
  16. data/example/webmachine/app.rb +37 -0
  17. data/jruby-http-kit.gemspec +25 -0
  18. data/lib/http_kit/rack_handler.rb +139 -0
  19. data/lib/http_kit/server.rb +62 -0
  20. data/lib/http_kit/version.rb +3 -0
  21. data/lib/http_kit.rb +6 -0
  22. data/lib/java/clojure-1.5.1.jar +0 -0
  23. data/lib/java/http-kit.jar +0 -0
  24. data/lib/rack/handler/http_kit.rb +3 -0
  25. data/lib/rack/http_kit.rb +3 -0
  26. data/lib/webmachine/adapters/ring.rb +135 -0
  27. data/spec/spec_helper.rb +24 -0
  28. data/spec/support/test_resource.rb +75 -0
  29. data/spec/webmachine/ring_adapter_spec.rb +127 -0
  30. data/src/.classpath +7 -0
  31. data/src/.project +17 -0
  32. data/src/org/httpkit/BytesInputStream.class +0 -0
  33. data/src/org/httpkit/BytesInputStream.java +83 -0
  34. data/src/org/httpkit/DateFormatter.class +0 -0
  35. data/src/org/httpkit/DynamicBytes.class +0 -0
  36. data/src/org/httpkit/DynamicBytes.java +76 -0
  37. data/src/org/httpkit/HTTPException.class +0 -0
  38. data/src/org/httpkit/HTTPException.java +10 -0
  39. data/src/org/httpkit/HeaderMap.class +0 -0
  40. data/src/org/httpkit/HeaderMap.java +98 -0
  41. data/src/org/httpkit/HttpMethod.class +0 -0
  42. data/src/org/httpkit/HttpMethod.java +28 -0
  43. data/src/org/httpkit/HttpStatus.class +0 -0
  44. data/src/org/httpkit/HttpStatus.java +416 -0
  45. data/src/org/httpkit/HttpUtils.class +0 -0
  46. data/src/org/httpkit/HttpUtils.java +484 -0
  47. data/src/org/httpkit/HttpVersion.class +0 -0
  48. data/src/org/httpkit/HttpVersion.java +5 -0
  49. data/src/org/httpkit/LineReader.class +0 -0
  50. data/src/org/httpkit/LineReader.java +52 -0
  51. data/src/org/httpkit/LineTooLargeException.class +0 -0
  52. data/src/org/httpkit/LineTooLargeException.java +13 -0
  53. data/src/org/httpkit/PrefixThreadFactory.class +0 -0
  54. data/src/org/httpkit/PrefixThreadFactory.java +20 -0
  55. data/src/org/httpkit/PriorityQueue.class +0 -0
  56. data/src/org/httpkit/PriorityQueue.java +235 -0
  57. data/src/org/httpkit/ProtocolException.class +0 -0
  58. data/src/org/httpkit/ProtocolException.java +10 -0
  59. data/src/org/httpkit/RequestTooLargeException.class +0 -0
  60. data/src/org/httpkit/RequestTooLargeException.java +10 -0
  61. data/src/org/httpkit/client/AbortException.class +0 -0
  62. data/src/org/httpkit/client/AbortException.java +13 -0
  63. data/src/org/httpkit/client/Decoder.class +0 -0
  64. data/src/org/httpkit/client/Decoder.java +182 -0
  65. data/src/org/httpkit/client/Handler.class +0 -0
  66. data/src/org/httpkit/client/HttpClient.class +0 -0
  67. data/src/org/httpkit/client/HttpClient.java +393 -0
  68. data/src/org/httpkit/client/HttpsRequest.class +0 -0
  69. data/src/org/httpkit/client/HttpsRequest.java +141 -0
  70. data/src/org/httpkit/client/IFilter$1.class +0 -0
  71. data/src/org/httpkit/client/IFilter$MaxBodyFilter.class +0 -0
  72. data/src/org/httpkit/client/IFilter.class +0 -0
  73. data/src/org/httpkit/client/IFilter.java +53 -0
  74. data/src/org/httpkit/client/IRespListener.class +0 -0
  75. data/src/org/httpkit/client/IRespListener.java +30 -0
  76. data/src/org/httpkit/client/IResponseHandler.class +0 -0
  77. data/src/org/httpkit/client/IResponseHandler.java +18 -0
  78. data/src/org/httpkit/client/PersistentConn.class +0 -0
  79. data/src/org/httpkit/client/PersistentConn.java +33 -0
  80. data/src/org/httpkit/client/Request.class +0 -0
  81. data/src/org/httpkit/client/Request.java +74 -0
  82. data/src/org/httpkit/client/RequestConfig.class +0 -0
  83. data/src/org/httpkit/client/RequestConfig.java +28 -0
  84. data/src/org/httpkit/client/RespListener.class +0 -0
  85. data/src/org/httpkit/client/RespListener.java +160 -0
  86. data/src/org/httpkit/client/SslContextFactory.class +0 -0
  87. data/src/org/httpkit/client/SslContextFactory.java +79 -0
  88. data/src/org/httpkit/client/State.class +0 -0
  89. data/src/org/httpkit/client/TimeoutException.class +0 -0
  90. data/src/org/httpkit/client/TimeoutException.java +12 -0
  91. data/src/org/httpkit/client/TrustManagerFactory$1.class +0 -0
  92. data/src/org/httpkit/client/TrustManagerFactory.class +0 -0
  93. data/src/org/httpkit/server/AsyncChannel.class +0 -0
  94. data/src/org/httpkit/server/AsyncChannel.java +286 -0
  95. data/src/org/httpkit/server/ClojureRing.class +0 -0
  96. data/src/org/httpkit/server/Frame$BinaryFrame.class +0 -0
  97. data/src/org/httpkit/server/Frame$CloseFrame.class +0 -0
  98. data/src/org/httpkit/server/Frame$PingFrame.class +0 -0
  99. data/src/org/httpkit/server/Frame$TextFrame.class +0 -0
  100. data/src/org/httpkit/server/Frame.class +0 -0
  101. data/src/org/httpkit/server/Frame.java +73 -0
  102. data/src/org/httpkit/server/HttpAtta.class +0 -0
  103. data/src/org/httpkit/server/HttpAtta.java +10 -0
  104. data/src/org/httpkit/server/HttpDecoder$State.class +0 -0
  105. data/src/org/httpkit/server/HttpDecoder.class +0 -0
  106. data/src/org/httpkit/server/HttpDecoder.java +202 -0
  107. data/src/org/httpkit/server/HttpHandler.class +0 -0
  108. data/src/org/httpkit/server/HttpRequest.class +0 -0
  109. data/src/org/httpkit/server/HttpRequest.java +109 -0
  110. data/src/org/httpkit/server/HttpServer.class +0 -0
  111. data/src/org/httpkit/server/HttpServer.java +281 -0
  112. data/src/org/httpkit/server/IHandler.class +0 -0
  113. data/src/org/httpkit/server/IHandler.java +12 -0
  114. data/src/org/httpkit/server/LinkingRunnable.class +0 -0
  115. data/src/org/httpkit/server/Rack.class +0 -0
  116. data/src/org/httpkit/server/RackApplication.class +0 -0
  117. data/src/org/httpkit/server/RackApplication.java +10 -0
  118. data/src/org/httpkit/server/RackHandler$1.class +0 -0
  119. data/src/org/httpkit/server/RackHandler.class +0 -0
  120. data/src/org/httpkit/server/RackHandler.java +259 -0
  121. data/src/org/httpkit/server/RackHttpHandler.class +0 -0
  122. data/src/org/httpkit/server/RackHttpHandler.java +20 -0
  123. data/src/org/httpkit/server/RespCallback.class +0 -0
  124. data/src/org/httpkit/server/RespCallback.java +19 -0
  125. data/src/org/httpkit/server/RingHandler$1.class +0 -0
  126. data/src/org/httpkit/server/RingHandler.class +0 -0
  127. data/src/org/httpkit/server/RingHandler.java +222 -0
  128. data/src/org/httpkit/server/ServerAtta.class +0 -0
  129. data/src/org/httpkit/server/ServerAtta.java +22 -0
  130. data/src/org/httpkit/server/WSDecoder$State.class +0 -0
  131. data/src/org/httpkit/server/WSDecoder.class +0 -0
  132. data/src/org/httpkit/server/WSDecoder.java +181 -0
  133. data/src/org/httpkit/server/WSHandler.class +0 -0
  134. data/src/org/httpkit/server/WsAtta.class +0 -0
  135. data/src/org/httpkit/server/WsAtta.java +11 -0
  136. data/src/org/httpkit/timer/CancelableFutureTask.class +0 -0
  137. data/src/org/httpkit/timer/CancelableFutureTask.java +52 -0
  138. data/src/org/httpkit/timer/TimerService.class +0 -0
  139. data/src/org/httpkit/timer/TimerService.java +89 -0
  140. metadata +241 -0
@@ -0,0 +1,393 @@
1
+ package org.httpkit.client;
2
+
3
+ import org.httpkit.*;
4
+ import org.httpkit.ProtocolException;
5
+
6
+ import javax.net.ssl.SSLContext;
7
+ import javax.net.ssl.SSLEngine;
8
+ import javax.net.ssl.SSLException;
9
+ import java.io.IOException;
10
+ import java.net.*;
11
+ import java.nio.ByteBuffer;
12
+ import java.nio.channels.SelectionKey;
13
+ import java.nio.channels.Selector;
14
+ import java.nio.channels.SocketChannel;
15
+ import java.security.NoSuchAlgorithmException;
16
+ import java.util.Iterator;
17
+ import java.util.Queue;
18
+ import java.util.Set;
19
+ import java.util.concurrent.ConcurrentLinkedQueue;
20
+ import java.util.concurrent.atomic.AtomicInteger;
21
+
22
+ import static java.lang.System.currentTimeMillis;
23
+ import static java.nio.channels.SelectionKey.*;
24
+ import static org.httpkit.HttpUtils.SP;
25
+ import static org.httpkit.HttpUtils.getServerAddr;
26
+ import static org.httpkit.client.State.ALL_READ;
27
+ import static org.httpkit.client.State.READ_INITIAL;
28
+
29
+ public final class HttpClient implements Runnable {
30
+ private static final AtomicInteger ID = new AtomicInteger(0);
31
+
32
+ public static final SSLContext DEFAULT_CONTEXT;
33
+
34
+ static {
35
+ try {
36
+ DEFAULT_CONTEXT = SSLContext.getDefault();
37
+ } catch (NoSuchAlgorithmException e) {
38
+ throw new Error("Failed to initialize SSLContext", e);
39
+ }
40
+ }
41
+
42
+ private final Queue<Request> pending = new ConcurrentLinkedQueue<Request>();
43
+ private final PriorityQueue<Request> requests = new PriorityQueue<Request>();
44
+ private final PriorityQueue<PersistentConn> keepalives = new PriorityQueue<PersistentConn>();
45
+
46
+ private volatile boolean running = true;
47
+
48
+ // shared, single thread
49
+ private final ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 64);
50
+ private final Selector selector;
51
+
52
+ public HttpClient() throws IOException {
53
+ int id = ID.incrementAndGet();
54
+ String name = "client-loop";
55
+ if (id > 1) {
56
+ name = name + "#" + id;
57
+ }
58
+ selector = Selector.open();
59
+ Thread t = new Thread(this, name);
60
+ t.setDaemon(true);
61
+ t.start();
62
+ }
63
+
64
+ private void clearTimeout(long now) {
65
+ Request r;
66
+ while ((r = requests.peek()) != null) {
67
+ if (r.isTimeout(now)) {
68
+ String msg = "connect timeout: ";
69
+ if (r.isConnected) {
70
+ msg = "read timeout: ";
71
+ }
72
+ // will remove it from queue
73
+ r.finish(new TimeoutException(msg + r.cfg.timeout + "ms"));
74
+ if (r.key != null) {
75
+ closeQuietly(r.key);
76
+ }
77
+ } else {
78
+ break;
79
+ }
80
+ }
81
+
82
+ PersistentConn pc;
83
+ while ((pc = keepalives.peek()) != null) {
84
+ if (pc.isTimeout(now)) {
85
+ closeQuietly(pc.key);
86
+ keepalives.poll();
87
+ } else {
88
+ break;
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * http-kit think all connections are keep-alived (since some say it is, but
95
+ * actually is not). but, some are not, http-kit pick them out after the fact
96
+ * <ol>
97
+ * <li>The connection is reused</li>
98
+ * <li>No data received</li>
99
+ * </ol>
100
+ */
101
+ private boolean cleanAndRetryIfBroken(SelectionKey key, Request req) {
102
+ closeQuietly(key);
103
+ keepalives.remove(key);
104
+ // keep-alived connection, remote server close it without sending byte
105
+ if (req.isReuseConn && req.decoder.state == READ_INITIAL) {
106
+ for (ByteBuffer b : req.request) {
107
+ b.position(0); // reset for retry
108
+ }
109
+ req.isReuseConn = false;
110
+ requests.remove(req); // remove from timeout queue
111
+ pending.offer(req); // queue for retry
112
+ selector.wakeup();
113
+ return true; // retry: re-open a connection to server, sent the request again
114
+ }
115
+ return false;
116
+ }
117
+
118
+ private void doRead(SelectionKey key, long now) {
119
+ Request req = (Request) key.attachment();
120
+ SocketChannel ch = (SocketChannel) key.channel();
121
+ int read = 0;
122
+ try {
123
+ buffer.clear();
124
+ if (req instanceof HttpsRequest) {
125
+ HttpsRequest httpsReq = (HttpsRequest) req;
126
+ if (httpsReq.handshaken) {
127
+ // SSLEngine closed => fine, will return -1 in the next run
128
+ read = httpsReq.unwrapRead(buffer);
129
+ } else {
130
+ read = httpsReq.doHandshake(buffer);
131
+ }
132
+ } else {
133
+ read = ch.read(buffer);
134
+ }
135
+ } catch (IOException e) { // The remote forcibly closed the connection
136
+ if (!cleanAndRetryIfBroken(key, req)) {
137
+ req.finish(e); // os X get Connection reset by peer error,
138
+ }
139
+ // java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 1024 (inclusive)
140
+ // java.lang.RuntimeException: Could not generate DH keypair
141
+ } catch (Exception e) {
142
+ req.finish(e);
143
+ }
144
+
145
+ if (read == -1) { // read all, remote closed it cleanly
146
+ if (!cleanAndRetryIfBroken(key, req)) {
147
+ req.finish();
148
+ }
149
+ } else if (read > 0) {
150
+ req.onProgress(now);
151
+ buffer.flip();
152
+ try {
153
+ if (req.decoder.decode(buffer) == ALL_READ) {
154
+ req.finish();
155
+ if (req.cfg.keepAlive > 0) {
156
+ keepalives.offer(new PersistentConn(now + req.cfg.keepAlive, req.addr, key));
157
+ } else {
158
+ closeQuietly(key);
159
+ }
160
+ }
161
+ } catch (HTTPException e) {
162
+ closeQuietly(key);
163
+ req.finish(e);
164
+ } catch (Exception e) {
165
+ closeQuietly(key);
166
+ req.finish(e);
167
+ HttpUtils.printError("should not happen", e); // decoding
168
+ }
169
+ }
170
+ }
171
+
172
+ private void closeQuietly(SelectionKey key) {
173
+ try {
174
+ // TODO engine.closeInbound
175
+ key.channel().close();
176
+ } catch (Exception ignore) {
177
+ }
178
+ }
179
+
180
+ private void doWrite(SelectionKey key) {
181
+ Request req = (Request) key.attachment();
182
+ SocketChannel ch = (SocketChannel) key.channel();
183
+ try {
184
+ if (req instanceof HttpsRequest) {
185
+ HttpsRequest httpsReq = (HttpsRequest) req;
186
+ if (httpsReq.handshaken) {
187
+ // will flip to OP_READ
188
+ httpsReq.writeWrappedRequest();
189
+ } else {
190
+ buffer.clear();
191
+ if (httpsReq.doHandshake(buffer) < 0) {
192
+ req.finish(); // will be a No status exception
193
+ }
194
+ }
195
+ } else {
196
+ ByteBuffer[] buffers = req.request;
197
+ ch.write(buffers);
198
+ if (!buffers[buffers.length - 1].hasRemaining()) {
199
+ key.interestOps(OP_READ);
200
+ }
201
+ }
202
+ } catch (IOException e) {
203
+ if (!cleanAndRetryIfBroken(key, req)) {
204
+ req.finish(e);
205
+ }
206
+ } catch (Exception e) { // rarely happen
207
+ req.finish(e);
208
+ }
209
+ }
210
+
211
+ public void exec(String url, RequestConfig cfg, SSLEngine engine, IRespListener cb) {
212
+ URI uri;
213
+ try {
214
+ uri = new URI(url);
215
+ } catch (URISyntaxException e) {
216
+ cb.onThrowable(e);
217
+ return;
218
+ }
219
+
220
+ if (uri.getHost() == null) {
221
+ cb.onThrowable(new IllegalArgumentException("host is null: " + url));
222
+ return;
223
+ }
224
+
225
+ String scheme = uri.getScheme();
226
+ if (!"http".equals(scheme) && !"https".equals(scheme)) {
227
+ String message = (scheme == null) ? "No protocol specified" : scheme + " is not supported";
228
+ cb.onThrowable(new ProtocolException(message));
229
+ return;
230
+ }
231
+
232
+ InetSocketAddress addr;
233
+ try {
234
+ addr = getServerAddr(uri);
235
+ } catch (UnknownHostException e) {
236
+ cb.onThrowable(e);
237
+ return;
238
+ }
239
+
240
+ // copy to modify, normalize header
241
+ HeaderMap headers = HeaderMap.camelCase(cfg.headers);
242
+ headers.put("Host", HttpUtils.getHost(uri));
243
+
244
+ if (!headers.containsKey("Accept")) // allow override
245
+ headers.put("Accept", "*/*");
246
+ if (!headers.containsKey("User-Agent")) // allow override
247
+ headers.put("User-Agent", RequestConfig.DEFAULT_USER_AGENT); // default
248
+ if (!headers.containsKey("Accept-Encoding"))
249
+ headers.put("Accept-Encoding", "gzip, deflate"); // compression is good
250
+
251
+ ByteBuffer request[];
252
+ try {
253
+ request = encode(cfg.method, headers, cfg.body, uri);
254
+ } catch (IOException e) {
255
+ cb.onThrowable(e);
256
+ return;
257
+ }
258
+ if ("https".equals(scheme)) {
259
+ if (engine == null) {
260
+ engine = DEFAULT_CONTEXT.createSSLEngine();
261
+ }
262
+ engine.setUseClientMode(true);
263
+ pending.offer(new HttpsRequest(addr, request, cb, requests, cfg, engine));
264
+ } else {
265
+ pending.offer(new Request(addr, request, cb, requests, cfg));
266
+ }
267
+
268
+ // pending.offer(new Request(addr, request, cb, requests, cfg));
269
+ selector.wakeup();
270
+ }
271
+
272
+ private ByteBuffer[] encode(HttpMethod method, HeaderMap headers, Object body,
273
+ URI uri) throws IOException {
274
+ ByteBuffer bodyBuffer = HttpUtils.bodyBuffer(body);
275
+
276
+ if (body != null) {
277
+ headers.put("Content-Length", Integer.toString(bodyBuffer.remaining()));
278
+ } else {
279
+ headers.put("Content-Length", "0");
280
+ }
281
+ DynamicBytes bytes = new DynamicBytes(196);
282
+ bytes.append(method.toString()).append(SP).append(HttpUtils.getPath(uri));
283
+ bytes.append(" HTTP/1.1\r\n");
284
+ headers.encodeHeaders(bytes);
285
+ ByteBuffer headBuffer = ByteBuffer.wrap(bytes.get(), 0, bytes.length());
286
+
287
+ if (bodyBuffer == null) {
288
+ return new ByteBuffer[]{headBuffer};
289
+ } else {
290
+ return new ByteBuffer[]{headBuffer, bodyBuffer};
291
+ }
292
+ }
293
+
294
+ private void finishConnect(SelectionKey key, long now) {
295
+ SocketChannel ch = (SocketChannel) key.channel();
296
+ Request req = (Request) key.attachment();
297
+ try {
298
+ if (ch.finishConnect()) {
299
+ req.isConnected = true;
300
+ req.onProgress(now);
301
+ key.interestOps(OP_WRITE);
302
+ if (req instanceof HttpsRequest) {
303
+ ((HttpsRequest) req).engine.beginHandshake();
304
+ }
305
+ }
306
+ } catch (IOException e) {
307
+ closeQuietly(key); // not added to kee-alive yet;
308
+ req.finish(e);
309
+ }
310
+ }
311
+
312
+ private void processPending() {
313
+ Request job = null;
314
+ while ((job = pending.poll()) != null) {
315
+ if (job.cfg.keepAlive > 0) {
316
+ PersistentConn con = keepalives.remove(job.addr);
317
+ if (con != null) { // keep alive
318
+ SelectionKey key = con.key;
319
+ if (key.isValid()) {
320
+ job.isReuseConn = true;
321
+ // reuse key, engine
322
+ try {
323
+ job.recycle((Request) key.attachment());
324
+ key.attach(job);
325
+ key.interestOps(OP_WRITE);
326
+ requests.offer(job);
327
+ continue;
328
+ } catch (SSLException e) {
329
+ closeQuietly(key); // https wrap SSLException, start from fresh
330
+ }
331
+ } else {
332
+ // this should not happen often
333
+ closeQuietly(key);
334
+ }
335
+ }
336
+ }
337
+ try {
338
+ SocketChannel ch = SocketChannel.open();
339
+ ch.configureBlocking(false);
340
+ // saved for timeout
341
+ job.key = ch.register(selector, OP_CONNECT, job);
342
+ ch.connect(job.addr);
343
+ requests.offer(job);
344
+ } catch (IOException e) {
345
+ job.finish(e);
346
+ HttpUtils.printError("Try to connect " + job.addr, e);
347
+ }
348
+ }
349
+ }
350
+
351
+ public void run() {
352
+ while (running) {
353
+ try {
354
+ long now = currentTimeMillis();
355
+ int select = selector.select(2000);
356
+ if (select > 0) {
357
+ Set<SelectionKey> selectedKeys = selector.selectedKeys();
358
+ Iterator<SelectionKey> ite = selectedKeys.iterator();
359
+ while (ite.hasNext()) {
360
+ SelectionKey key = ite.next();
361
+ if (!key.isValid()) {
362
+ continue;
363
+ }
364
+ if (key.isConnectable()) {
365
+ finishConnect(key, now);
366
+ } else if (key.isReadable()) {
367
+ doRead(key, now);
368
+ } else if (key.isWritable()) {
369
+ doWrite(key);
370
+ }
371
+ ite.remove();
372
+ }
373
+ }
374
+ clearTimeout(now);
375
+ processPending();
376
+ } catch (Throwable e) { // catch any exception (including OOM), print it: do not exits the loop
377
+ HttpUtils.printError("select exception, should not happen", e);
378
+ }
379
+ }
380
+ }
381
+
382
+ public void stop() throws IOException {
383
+ running = false;
384
+ if (selector != null) {
385
+ selector.close();
386
+ }
387
+ }
388
+
389
+ @Override
390
+ public String toString() {
391
+ return this.getClass().getCanonicalName();
392
+ }
393
+ }
@@ -0,0 +1,141 @@
1
+ package org.httpkit.client;
2
+
3
+ import org.httpkit.PriorityQueue;
4
+
5
+ import javax.net.ssl.SSLEngine;
6
+ import javax.net.ssl.SSLEngineResult;
7
+ import javax.net.ssl.SSLEngineResult.Status;
8
+ import javax.net.ssl.SSLException;
9
+ import java.io.IOException;
10
+ import java.net.InetSocketAddress;
11
+ import java.nio.ByteBuffer;
12
+ import java.nio.channels.SelectionKey;
13
+ import java.nio.channels.SocketChannel;
14
+
15
+ public class HttpsRequest extends Request {
16
+ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
17
+
18
+ public HttpsRequest(InetSocketAddress addr, ByteBuffer[] request, IRespListener handler,
19
+ PriorityQueue<Request> clients, RequestConfig config, SSLEngine engine) {
20
+ super(addr, request, handler, clients, config);
21
+ this.engine = engine;
22
+ }
23
+
24
+ SSLEngine engine; // package private
25
+ private ByteBuffer myNetData = ByteBuffer.allocate(40 * 1024);
26
+ private ByteBuffer peerNetData = ByteBuffer.allocate(40 * 1024);
27
+ boolean handshaken = false;
28
+
29
+ final int unwrapRead(ByteBuffer peerAppData) throws IOException {
30
+ // TODO, make sure peerNetData has remaining place
31
+ int read = ((SocketChannel) key.channel()).read(peerNetData), unwrapped = 0;
32
+ if (read > 0) {
33
+ peerNetData.flip();
34
+ SSLEngineResult res;
35
+ while ((res = engine.unwrap(peerNetData, peerAppData)).getStatus() == Status.OK) {
36
+ unwrapped += res.bytesProduced();
37
+ if (!peerNetData.hasRemaining())
38
+ break;
39
+ }
40
+ peerNetData.compact();
41
+ switch (res.getStatus()) {
42
+ case OK:
43
+ case BUFFER_UNDERFLOW: // need more data
44
+ return unwrapped;
45
+ case CLOSED:
46
+ return unwrapped > 0 ? unwrapped : -1;
47
+ case BUFFER_OVERFLOW:
48
+ return -1; // can't => peerAppData is 64k
49
+ }
50
+ return unwrapped;
51
+ } else {
52
+ return read;
53
+ }
54
+ }
55
+
56
+ private void wrapRequest() throws SSLException {
57
+ myNetData.clear();
58
+ SSLEngineResult res = engine.wrap(request, myNetData);
59
+ if (res.getStatus() != Status.OK) {
60
+ // TODO larger buffer, uberflow?
61
+ }
62
+ myNetData.flip();
63
+ }
64
+
65
+ final void writeWrappedRequest() throws IOException {
66
+ if (myNetData.hasRemaining()) {
67
+ ((SocketChannel) key.channel()).write(myNetData);
68
+ } else if (request[request.length - 1].hasRemaining()) {
69
+ wrapRequest();
70
+ ((SocketChannel) key.channel()).write(myNetData);
71
+ }
72
+ if (myNetData.hasRemaining() || request[request.length - 1].hasRemaining()) {
73
+ // need more write
74
+ if ((key.interestOps() & SelectionKey.OP_WRITE) == 0)
75
+ key.interestOps(SelectionKey.OP_WRITE);
76
+ } else {
77
+ // OK, request sent
78
+ key.interestOps(SelectionKey.OP_READ);
79
+ }
80
+ }
81
+
82
+ final int doHandshake(ByteBuffer peerAppData) throws IOException {
83
+ SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
84
+ while (!handshaken) {
85
+ switch (hs) {
86
+ case NEED_TASK:
87
+ Runnable runnable;
88
+ while ((runnable = engine.getDelegatedTask()) != null) {
89
+ runnable.run();
90
+ }
91
+ break;
92
+ case NEED_UNWRAP:
93
+ int read = ((SocketChannel) key.channel()).read(peerNetData);
94
+ if (read < 0) {
95
+ return -1;
96
+ } else {
97
+ peerNetData.flip();
98
+ SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
99
+ peerNetData.compact();
100
+ switch (res.getStatus()) {
101
+ case BUFFER_OVERFLOW: // Not possible, peerAppData is 64k
102
+ break;
103
+ case CLOSED:
104
+ return -1;
105
+ case BUFFER_UNDERFLOW: // need more data from peer
106
+ return 0;
107
+ }
108
+ // do not flip to write here, since TCP buffer is writable
109
+ }
110
+ break;
111
+ case NEED_WRAP:
112
+ SSLEngineResult res = engine.wrap(EMPTY_BUFFER, myNetData);
113
+ myNetData.flip();
114
+ ((SocketChannel) key.channel()).write(myNetData);
115
+ if (myNetData.hasRemaining()) {
116
+ // TODO, make sure data get written
117
+ } else {
118
+ myNetData.clear();
119
+ if (res.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NEED_WRAP)
120
+ key.interestOps(SelectionKey.OP_READ);
121
+ }
122
+ break;
123
+ }
124
+ hs = engine.getHandshakeStatus();
125
+ handshaken = hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING
126
+ || hs == SSLEngineResult.HandshakeStatus.FINISHED;
127
+ if (handshaken) {
128
+ wrapRequest();
129
+ writeWrappedRequest(); // TCP buffer maybe empty this time
130
+ }
131
+ }
132
+ return 0;
133
+ }
134
+
135
+ public void recycle(Request old) throws SSLException {
136
+ super.recycle(old);
137
+ this.engine = ((HttpsRequest) old).engine;
138
+ this.handshaken = true;
139
+ wrapRequest(); // prepare for write
140
+ }
141
+ }
@@ -0,0 +1,53 @@
1
+ package org.httpkit.client;
2
+
3
+ import org.httpkit.DynamicBytes;
4
+
5
+ import java.util.Map;
6
+
7
+ /**
8
+ * allow to abort the connection. for example, a crawler may abort the
9
+ * connection if not text
10
+ *
11
+ * @author feng
12
+ */
13
+ public interface IFilter {
14
+ public final static IFilter ACCEPT_ALL = new IFilter() {
15
+ public boolean accept(DynamicBytes partialBody) {
16
+ return true;
17
+ }
18
+
19
+ public boolean accept(Map<String, Object> headers) {
20
+ return true;
21
+ }
22
+
23
+ public String toString() {
24
+ return "Response Filter: ACCEPT all response";
25
+ }
26
+ };
27
+
28
+ // if the response is too large, protect OOM
29
+ // For example, HTML expected, but a big mp4 file is returned
30
+ public static class MaxBodyFilter implements IFilter {
31
+ private final int length;
32
+
33
+ public MaxBodyFilter(int maxLength) {
34
+ this.length = maxLength;
35
+ }
36
+
37
+ public boolean accept(Map<String, Object> headers) {
38
+ return true;
39
+ }
40
+
41
+ public String toString() {
42
+ return "Response Filter: ACCEPT when body's length <= " + length;
43
+ }
44
+
45
+ public boolean accept(DynamicBytes partialBody) {
46
+ return partialBody.length() <= length;
47
+ }
48
+ }
49
+
50
+ public boolean accept(Map<String, Object> headers);
51
+
52
+ public boolean accept(DynamicBytes partialBody);
53
+ }
@@ -0,0 +1,30 @@
1
+ package org.httpkit.client;
2
+
3
+ import org.httpkit.HttpStatus;
4
+ import org.httpkit.HttpVersion;
5
+
6
+ import java.util.Map;
7
+
8
+ /**
9
+ * Interface for response received from server
10
+ * <p/>
11
+ * A low level interface, can be used for very large file download
12
+ *
13
+ * @author feng
14
+ */
15
+ public interface IRespListener {
16
+
17
+ public void onBodyReceived(byte[] buf, int length) throws AbortException;
18
+
19
+ public void onCompleted();
20
+
21
+ public void onHeadersReceived(Map<String, Object> headers) throws AbortException;
22
+
23
+ public void onInitialLineReceived(HttpVersion version, HttpStatus status)
24
+ throws AbortException;
25
+
26
+ /**
27
+ * protocol error
28
+ */
29
+ public void onThrowable(Throwable t);
30
+ }
@@ -0,0 +1,18 @@
1
+ package org.httpkit.client;
2
+
3
+ import java.util.Map;
4
+
5
+ public interface IResponseHandler {
6
+
7
+ /**
8
+ * get called when all the response are fully received from server
9
+ *
10
+ * @param status HTTP status code, like 200
11
+ * @param headers Response HTTP headers
12
+ * @param body Response body, for text is a String, for binary is a
13
+ * InputStream
14
+ */
15
+ void onSuccess(int status, Map<String, Object> headers, Object body);
16
+
17
+ void onThrowable(Throwable t);
18
+ }
@@ -0,0 +1,33 @@
1
+ package org.httpkit.client;
2
+
3
+ import java.net.InetSocketAddress;
4
+ import java.nio.channels.SelectionKey;
5
+
6
+ public class PersistentConn implements Comparable<PersistentConn> {
7
+ private final long timeoutTs;
8
+ public final InetSocketAddress addr;
9
+ public final SelectionKey key;
10
+
11
+ public PersistentConn(long timeoutTs, InetSocketAddress addr, SelectionKey key) {
12
+ this.timeoutTs = timeoutTs;
13
+ this.addr = addr;
14
+ this.key = key;
15
+ }
16
+
17
+ public boolean equals(Object obj) {
18
+ // for PriorityQueue to remove by key and by addr
19
+ return addr.equals(obj) || key.equals(obj);
20
+ }
21
+
22
+ public int compareTo(PersistentConn o) {
23
+ return (int) (timeoutTs - o.timeoutTs);
24
+ }
25
+
26
+ public boolean isTimeout(long now) {
27
+ return timeoutTs < now;
28
+ }
29
+
30
+ public String toString() {
31
+ return addr + "; timeout=" + (timeoutTs - System.currentTimeMillis()) + "ms";
32
+ }
33
+ }