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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +1 -0
- data/example/config.ru +9 -0
- data/example/hello_world.rb +17 -0
- data/example/rack.rb +11 -0
- data/example/ring.rb +31 -0
- data/example/sinatra/app.rb +9 -0
- data/example/sinatra/config.ru +8 -0
- data/example/webmachine/Gemfile +4 -0
- data/example/webmachine/Gemfile.lock +16 -0
- data/example/webmachine/app.rb +37 -0
- data/jruby-http-kit.gemspec +25 -0
- data/lib/http_kit/rack_handler.rb +139 -0
- data/lib/http_kit/server.rb +62 -0
- data/lib/http_kit/version.rb +3 -0
- data/lib/http_kit.rb +6 -0
- data/lib/java/clojure-1.5.1.jar +0 -0
- data/lib/java/http-kit.jar +0 -0
- data/lib/rack/handler/http_kit.rb +3 -0
- data/lib/rack/http_kit.rb +3 -0
- data/lib/webmachine/adapters/ring.rb +135 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/test_resource.rb +75 -0
- data/spec/webmachine/ring_adapter_spec.rb +127 -0
- data/src/.classpath +7 -0
- data/src/.project +17 -0
- data/src/org/httpkit/BytesInputStream.class +0 -0
- data/src/org/httpkit/BytesInputStream.java +83 -0
- data/src/org/httpkit/DateFormatter.class +0 -0
- data/src/org/httpkit/DynamicBytes.class +0 -0
- data/src/org/httpkit/DynamicBytes.java +76 -0
- data/src/org/httpkit/HTTPException.class +0 -0
- data/src/org/httpkit/HTTPException.java +10 -0
- data/src/org/httpkit/HeaderMap.class +0 -0
- data/src/org/httpkit/HeaderMap.java +98 -0
- data/src/org/httpkit/HttpMethod.class +0 -0
- data/src/org/httpkit/HttpMethod.java +28 -0
- data/src/org/httpkit/HttpStatus.class +0 -0
- data/src/org/httpkit/HttpStatus.java +416 -0
- data/src/org/httpkit/HttpUtils.class +0 -0
- data/src/org/httpkit/HttpUtils.java +484 -0
- data/src/org/httpkit/HttpVersion.class +0 -0
- data/src/org/httpkit/HttpVersion.java +5 -0
- data/src/org/httpkit/LineReader.class +0 -0
- data/src/org/httpkit/LineReader.java +52 -0
- data/src/org/httpkit/LineTooLargeException.class +0 -0
- data/src/org/httpkit/LineTooLargeException.java +13 -0
- data/src/org/httpkit/PrefixThreadFactory.class +0 -0
- data/src/org/httpkit/PrefixThreadFactory.java +20 -0
- data/src/org/httpkit/PriorityQueue.class +0 -0
- data/src/org/httpkit/PriorityQueue.java +235 -0
- data/src/org/httpkit/ProtocolException.class +0 -0
- data/src/org/httpkit/ProtocolException.java +10 -0
- data/src/org/httpkit/RequestTooLargeException.class +0 -0
- data/src/org/httpkit/RequestTooLargeException.java +10 -0
- data/src/org/httpkit/client/AbortException.class +0 -0
- data/src/org/httpkit/client/AbortException.java +13 -0
- data/src/org/httpkit/client/Decoder.class +0 -0
- data/src/org/httpkit/client/Decoder.java +182 -0
- data/src/org/httpkit/client/Handler.class +0 -0
- data/src/org/httpkit/client/HttpClient.class +0 -0
- data/src/org/httpkit/client/HttpClient.java +393 -0
- data/src/org/httpkit/client/HttpsRequest.class +0 -0
- data/src/org/httpkit/client/HttpsRequest.java +141 -0
- data/src/org/httpkit/client/IFilter$1.class +0 -0
- data/src/org/httpkit/client/IFilter$MaxBodyFilter.class +0 -0
- data/src/org/httpkit/client/IFilter.class +0 -0
- data/src/org/httpkit/client/IFilter.java +53 -0
- data/src/org/httpkit/client/IRespListener.class +0 -0
- data/src/org/httpkit/client/IRespListener.java +30 -0
- data/src/org/httpkit/client/IResponseHandler.class +0 -0
- data/src/org/httpkit/client/IResponseHandler.java +18 -0
- data/src/org/httpkit/client/PersistentConn.class +0 -0
- data/src/org/httpkit/client/PersistentConn.java +33 -0
- data/src/org/httpkit/client/Request.class +0 -0
- data/src/org/httpkit/client/Request.java +74 -0
- data/src/org/httpkit/client/RequestConfig.class +0 -0
- data/src/org/httpkit/client/RequestConfig.java +28 -0
- data/src/org/httpkit/client/RespListener.class +0 -0
- data/src/org/httpkit/client/RespListener.java +160 -0
- data/src/org/httpkit/client/SslContextFactory.class +0 -0
- data/src/org/httpkit/client/SslContextFactory.java +79 -0
- data/src/org/httpkit/client/State.class +0 -0
- data/src/org/httpkit/client/TimeoutException.class +0 -0
- data/src/org/httpkit/client/TimeoutException.java +12 -0
- data/src/org/httpkit/client/TrustManagerFactory$1.class +0 -0
- data/src/org/httpkit/client/TrustManagerFactory.class +0 -0
- data/src/org/httpkit/server/AsyncChannel.class +0 -0
- data/src/org/httpkit/server/AsyncChannel.java +286 -0
- data/src/org/httpkit/server/ClojureRing.class +0 -0
- data/src/org/httpkit/server/Frame$BinaryFrame.class +0 -0
- data/src/org/httpkit/server/Frame$CloseFrame.class +0 -0
- data/src/org/httpkit/server/Frame$PingFrame.class +0 -0
- data/src/org/httpkit/server/Frame$TextFrame.class +0 -0
- data/src/org/httpkit/server/Frame.class +0 -0
- data/src/org/httpkit/server/Frame.java +73 -0
- data/src/org/httpkit/server/HttpAtta.class +0 -0
- data/src/org/httpkit/server/HttpAtta.java +10 -0
- data/src/org/httpkit/server/HttpDecoder$State.class +0 -0
- data/src/org/httpkit/server/HttpDecoder.class +0 -0
- data/src/org/httpkit/server/HttpDecoder.java +202 -0
- data/src/org/httpkit/server/HttpHandler.class +0 -0
- data/src/org/httpkit/server/HttpRequest.class +0 -0
- data/src/org/httpkit/server/HttpRequest.java +109 -0
- data/src/org/httpkit/server/HttpServer.class +0 -0
- data/src/org/httpkit/server/HttpServer.java +281 -0
- data/src/org/httpkit/server/IHandler.class +0 -0
- data/src/org/httpkit/server/IHandler.java +12 -0
- data/src/org/httpkit/server/LinkingRunnable.class +0 -0
- data/src/org/httpkit/server/Rack.class +0 -0
- data/src/org/httpkit/server/RackApplication.class +0 -0
- data/src/org/httpkit/server/RackApplication.java +10 -0
- data/src/org/httpkit/server/RackHandler$1.class +0 -0
- data/src/org/httpkit/server/RackHandler.class +0 -0
- data/src/org/httpkit/server/RackHandler.java +259 -0
- data/src/org/httpkit/server/RackHttpHandler.class +0 -0
- data/src/org/httpkit/server/RackHttpHandler.java +20 -0
- data/src/org/httpkit/server/RespCallback.class +0 -0
- data/src/org/httpkit/server/RespCallback.java +19 -0
- data/src/org/httpkit/server/RingHandler$1.class +0 -0
- data/src/org/httpkit/server/RingHandler.class +0 -0
- data/src/org/httpkit/server/RingHandler.java +222 -0
- data/src/org/httpkit/server/ServerAtta.class +0 -0
- data/src/org/httpkit/server/ServerAtta.java +22 -0
- data/src/org/httpkit/server/WSDecoder$State.class +0 -0
- data/src/org/httpkit/server/WSDecoder.class +0 -0
- data/src/org/httpkit/server/WSDecoder.java +181 -0
- data/src/org/httpkit/server/WSHandler.class +0 -0
- data/src/org/httpkit/server/WsAtta.class +0 -0
- data/src/org/httpkit/server/WsAtta.java +11 -0
- data/src/org/httpkit/timer/CancelableFutureTask.class +0 -0
- data/src/org/httpkit/timer/CancelableFutureTask.java +52 -0
- data/src/org/httpkit/timer/TimerService.class +0 -0
- data/src/org/httpkit/timer/TimerService.java +89 -0
- metadata +241 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package org.httpkit.client;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.PriorityQueue;
|
|
4
|
+
|
|
5
|
+
import javax.net.ssl.SSLException;
|
|
6
|
+
import java.net.InetSocketAddress;
|
|
7
|
+
import java.nio.ByteBuffer;
|
|
8
|
+
import java.nio.channels.SelectionKey;
|
|
9
|
+
|
|
10
|
+
public class Request implements Comparable<Request> {
|
|
11
|
+
|
|
12
|
+
final InetSocketAddress addr;
|
|
13
|
+
final Decoder decoder;
|
|
14
|
+
final ByteBuffer[] request; // HTTP request
|
|
15
|
+
final RequestConfig cfg;
|
|
16
|
+
private final PriorityQueue<Request> clients; // update timeout
|
|
17
|
+
|
|
18
|
+
// is modify from the loop thread. ensure only called once
|
|
19
|
+
private boolean isDone = false;
|
|
20
|
+
|
|
21
|
+
boolean isReuseConn = false; // a reused socket sent the request
|
|
22
|
+
boolean isConnected = false;
|
|
23
|
+
SelectionKey key; // for timeout, close connection
|
|
24
|
+
|
|
25
|
+
private long timeoutTs; // future time this request timeout, ms
|
|
26
|
+
|
|
27
|
+
public Request(InetSocketAddress addr, ByteBuffer[] request, IRespListener handler,
|
|
28
|
+
PriorityQueue<Request> clients, RequestConfig config) {
|
|
29
|
+
this.cfg = config;
|
|
30
|
+
this.decoder = new Decoder(handler, config.method);
|
|
31
|
+
this.request = request;
|
|
32
|
+
this.clients = clients;
|
|
33
|
+
this.addr = addr;
|
|
34
|
+
this.timeoutTs = config.timeout + System.currentTimeMillis();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public void onProgress(long now) {
|
|
38
|
+
if (cfg.timeout + now - timeoutTs > 800) {
|
|
39
|
+
// update time
|
|
40
|
+
clients.remove(this);
|
|
41
|
+
timeoutTs = cfg.timeout + now;
|
|
42
|
+
clients.offer(this);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public void finish() {
|
|
47
|
+
clients.remove(this);
|
|
48
|
+
if (isDone)
|
|
49
|
+
return;
|
|
50
|
+
isDone = true;
|
|
51
|
+
decoder.listener.onCompleted();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public boolean isTimeout(long now) {
|
|
55
|
+
return timeoutTs < now;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public void finish(Throwable t) {
|
|
59
|
+
clients.remove(this);
|
|
60
|
+
if (isDone)
|
|
61
|
+
return;
|
|
62
|
+
isDone = true;
|
|
63
|
+
decoder.listener.onThrowable(t);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public int compareTo(Request o) {
|
|
67
|
+
return (int) (timeoutTs - o.timeoutTs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public void recycle(Request old) throws SSLException {
|
|
71
|
+
this.key = old.key;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package org.httpkit.client;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.HttpMethod;
|
|
4
|
+
|
|
5
|
+
import java.util.Map;
|
|
6
|
+
|
|
7
|
+
public class RequestConfig {
|
|
8
|
+
public static String DEFAULT_USER_AGENT = "http-kit/2.0";
|
|
9
|
+
|
|
10
|
+
final int timeout;
|
|
11
|
+
final int keepAlive;
|
|
12
|
+
final Object body;
|
|
13
|
+
final Map<String, Object> headers;
|
|
14
|
+
final HttpMethod method;
|
|
15
|
+
|
|
16
|
+
public RequestConfig(HttpMethod method, Map<String, Object> headers, Object body,
|
|
17
|
+
int timeoutMs, int keepAliveMs) {
|
|
18
|
+
this.timeout = timeoutMs;
|
|
19
|
+
this.keepAlive = keepAliveMs;
|
|
20
|
+
this.headers = headers;
|
|
21
|
+
this.body = body;
|
|
22
|
+
this.method = method;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public RequestConfig() { // for easy test only
|
|
26
|
+
this(HttpMethod.GET, null, null, 40000, -1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
package org.httpkit.client;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.*;
|
|
4
|
+
|
|
5
|
+
import java.io.IOException;
|
|
6
|
+
import java.io.InputStream;
|
|
7
|
+
import java.nio.charset.Charset;
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
import java.util.TreeMap;
|
|
10
|
+
import java.util.concurrent.ExecutorService;
|
|
11
|
+
import java.util.zip.GZIPInputStream;
|
|
12
|
+
import java.util.zip.Inflater;
|
|
13
|
+
import java.util.zip.InflaterInputStream;
|
|
14
|
+
|
|
15
|
+
import static org.httpkit.HttpUtils.CONTENT_ENCODING;
|
|
16
|
+
import static org.httpkit.HttpUtils.CONTENT_TYPE;
|
|
17
|
+
|
|
18
|
+
class Handler implements Runnable {
|
|
19
|
+
|
|
20
|
+
private final int status;
|
|
21
|
+
private final Map<String, Object> headers;
|
|
22
|
+
private final Object body;
|
|
23
|
+
private final IResponseHandler handler;
|
|
24
|
+
|
|
25
|
+
public Handler(IResponseHandler handler, int status, Map<String, Object> headers,
|
|
26
|
+
Object body) {
|
|
27
|
+
this.handler = handler;
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.headers = headers;
|
|
30
|
+
this.body = body;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public Handler(IResponseHandler handler, Throwable e) {
|
|
34
|
+
this(handler, 0, null, e);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public void run() {
|
|
38
|
+
if (body instanceof Throwable) {
|
|
39
|
+
handler.onThrowable((Throwable) body);
|
|
40
|
+
} else {
|
|
41
|
+
handler.onSuccess(status, headers, body);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Accumulate all the response, call upper logic at once, for easy use
|
|
48
|
+
*/
|
|
49
|
+
public class RespListener implements IRespListener {
|
|
50
|
+
|
|
51
|
+
private boolean isText() {
|
|
52
|
+
if (status.getCode() == 200) {
|
|
53
|
+
String type = HttpUtils.getStringValue(headers, CONTENT_TYPE);
|
|
54
|
+
if (type != null) {
|
|
55
|
+
type = type.toLowerCase();
|
|
56
|
+
// TODO may miss something
|
|
57
|
+
return type.contains("text") || type.contains("json") || type.contains("xml");
|
|
58
|
+
} else {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
} else { // non 200: treat as text
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private DynamicBytes unzipBody() throws IOException {
|
|
67
|
+
String encoding = HttpUtils.getStringValue(headers, CONTENT_ENCODING);
|
|
68
|
+
if (encoding == null || body.length() == 0) {
|
|
69
|
+
return body;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
encoding = encoding.toLowerCase();
|
|
73
|
+
BytesInputStream bis = new BytesInputStream(body.get(), body.length());
|
|
74
|
+
InputStream is;
|
|
75
|
+
|
|
76
|
+
if ("gzip".equals(encoding) || "x-gzip".equals(encoding)) {
|
|
77
|
+
is = new GZIPInputStream(bis);
|
|
78
|
+
} else if ("deflate".equals(encoding) || "x-deflate".equals(encoding)) {
|
|
79
|
+
// http://stackoverflow.com/questions/3932117/handling-http-contentencoding-deflate
|
|
80
|
+
is = new InflaterInputStream(bis, new Inflater(true));
|
|
81
|
+
} else {
|
|
82
|
+
return body; // not compressed
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
DynamicBytes unzipped = new DynamicBytes(body.length() * 5);
|
|
86
|
+
byte[] buffer = new byte[4096];
|
|
87
|
+
int read;
|
|
88
|
+
while ((read = is.read(buffer)) != -1) {
|
|
89
|
+
unzipped.append(buffer, read);
|
|
90
|
+
}
|
|
91
|
+
is.close();
|
|
92
|
+
return unzipped;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private final DynamicBytes body;
|
|
96
|
+
|
|
97
|
+
// can be empty
|
|
98
|
+
private Map<String, Object> headers = new TreeMap<String, Object>();
|
|
99
|
+
private HttpStatus status;
|
|
100
|
+
private final IResponseHandler handler;
|
|
101
|
+
private final IFilter filter;
|
|
102
|
+
private final ExecutorService pool;
|
|
103
|
+
final int coercion;
|
|
104
|
+
|
|
105
|
+
public RespListener(IResponseHandler handler, IFilter filter, ExecutorService pool, int coercion) {
|
|
106
|
+
body = new DynamicBytes(1024 * 8);
|
|
107
|
+
this.filter = filter;
|
|
108
|
+
this.handler = handler;
|
|
109
|
+
this.coercion = coercion;
|
|
110
|
+
this.pool = pool;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public void onBodyReceived(byte[] buf, int length) throws AbortException {
|
|
114
|
+
body.append(buf, length);
|
|
115
|
+
if (filter != null && !filter.accept(body)) {
|
|
116
|
+
throw new AbortException("Rejected when reading body, length: " + body.length());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public void onCompleted() {
|
|
121
|
+
if (status == null) {
|
|
122
|
+
pool.submit(new Handler(handler, new ProtocolException("No status")));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
DynamicBytes bytes = unzipBody();
|
|
127
|
+
// 1=> auto, 2=>text, 3=>stream, 4=>byte-array
|
|
128
|
+
if (coercion == 2 || (coercion == 1 && isText())) {
|
|
129
|
+
Charset charset = HttpUtils.detectCharset(headers, bytes);
|
|
130
|
+
String html = new String(bytes.get(), 0, bytes.length(), charset);
|
|
131
|
+
pool.submit(new Handler(handler, status.getCode(), headers, html));
|
|
132
|
+
} else {
|
|
133
|
+
BytesInputStream is = new BytesInputStream(bytes.get(), bytes.length());
|
|
134
|
+
if (coercion == 4) { // byte-array
|
|
135
|
+
pool.submit(new Handler(handler, status.getCode(), headers, is.bytes()));
|
|
136
|
+
} else {
|
|
137
|
+
pool.submit(new Handler(handler, status.getCode(), headers, is));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (IOException e) {
|
|
141
|
+
handler.onThrowable(e);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public void onThrowable(Throwable t) {
|
|
146
|
+
pool.submit(new Handler(handler, t));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public void onHeadersReceived(Map<String, Object> headers) throws AbortException {
|
|
150
|
+
this.headers = headers;
|
|
151
|
+
if (filter != null && !filter.accept(headers)) {
|
|
152
|
+
throw new AbortException("Rejected when header received");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public void onInitialLineReceived(HttpVersion version, HttpStatus status)
|
|
157
|
+
throws AbortException {
|
|
158
|
+
this.status = status;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
package org.httpkit.client;
|
|
2
|
+
|
|
3
|
+
import javax.net.ssl.*;
|
|
4
|
+
import java.security.InvalidAlgorithmParameterException;
|
|
5
|
+
import java.security.KeyStore;
|
|
6
|
+
import java.security.KeyStoreException;
|
|
7
|
+
import java.security.cert.CertificateException;
|
|
8
|
+
import java.security.cert.X509Certificate;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
public class SslContextFactory {
|
|
12
|
+
|
|
13
|
+
private static final String PROTOCOL = "TLS";
|
|
14
|
+
private static final SSLContext CLIENT_CONTEXT;
|
|
15
|
+
|
|
16
|
+
static {
|
|
17
|
+
SSLContext clientContext = null;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
clientContext = SSLContext.getInstance(PROTOCOL);
|
|
21
|
+
clientContext.init(null, TrustManagerFactory.getTrustManagers(),
|
|
22
|
+
null);
|
|
23
|
+
} catch (Exception e) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"Failed to initialize the client-side SSLContext", e);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
CLIENT_CONTEXT = clientContext;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static SSLContext getClientContext() {
|
|
32
|
+
return CLIENT_CONTEXT;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static SSLEngine trustAnybody() {
|
|
36
|
+
return CLIENT_CONTEXT.createSSLEngine();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TrustManagerFactory extends TrustManagerFactorySpi {
|
|
42
|
+
|
|
43
|
+
private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() {
|
|
44
|
+
public X509Certificate[] getAcceptedIssuers() {
|
|
45
|
+
return new X509Certificate[0];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public void checkClientTrusted(X509Certificate[] chain,
|
|
49
|
+
String authType) throws CertificateException {
|
|
50
|
+
// Always trust
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public void checkServerTrusted(X509Certificate[] chain,
|
|
54
|
+
String authType) throws CertificateException {
|
|
55
|
+
// Always trust
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
public static TrustManager[] getTrustManagers() {
|
|
60
|
+
return new TrustManager[]{DUMMY_TRUST_MANAGER};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Override
|
|
64
|
+
protected TrustManager[] engineGetTrustManagers() {
|
|
65
|
+
return getTrustManagers();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Override
|
|
69
|
+
protected void engineInit(KeyStore keystore) throws KeyStoreException {
|
|
70
|
+
// Unused
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Override
|
|
74
|
+
protected void engineInit(
|
|
75
|
+
ManagerFactoryParameters managerFactoryParameters)
|
|
76
|
+
throws InvalidAlgorithmParameterException {
|
|
77
|
+
// Unused
|
|
78
|
+
}
|
|
79
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import clojure.lang.IFn;
|
|
4
|
+
import clojure.lang.Keyword;
|
|
5
|
+
import org.httpkit.DynamicBytes;
|
|
6
|
+
import org.httpkit.HeaderMap;
|
|
7
|
+
import org.httpkit.HttpVersion;
|
|
8
|
+
import sun.misc.Unsafe;
|
|
9
|
+
|
|
10
|
+
import java.io.IOException;
|
|
11
|
+
import java.io.InputStream;
|
|
12
|
+
import java.lang.reflect.Field;
|
|
13
|
+
import java.net.Socket;
|
|
14
|
+
import java.nio.ByteBuffer;
|
|
15
|
+
import java.nio.channels.SelectionKey;
|
|
16
|
+
import java.nio.channels.SocketChannel;
|
|
17
|
+
import java.util.Map;
|
|
18
|
+
|
|
19
|
+
import static org.httpkit.HttpUtils.*;
|
|
20
|
+
import static org.httpkit.server.ClojureRing.*;
|
|
21
|
+
import static org.httpkit.server.WSDecoder.*;
|
|
22
|
+
|
|
23
|
+
@SuppressWarnings({"unchecked"})
|
|
24
|
+
public class AsyncChannel {
|
|
25
|
+
static final Unsafe unsafe;
|
|
26
|
+
static final long closedRanOffset;
|
|
27
|
+
static final long closeHandlerOffset;
|
|
28
|
+
static final long receiveHandlerOffset;
|
|
29
|
+
static final long headerSentOffset;
|
|
30
|
+
|
|
31
|
+
private final SelectionKey key;
|
|
32
|
+
private final HttpServer server;
|
|
33
|
+
|
|
34
|
+
private HttpRequest request; // package private, for http 1.0 keep-alive
|
|
35
|
+
|
|
36
|
+
volatile int closedRan = 0; // 0 => false, 1 => run
|
|
37
|
+
// streaming
|
|
38
|
+
private volatile int isHeaderSent = 0;
|
|
39
|
+
|
|
40
|
+
private volatile IFn receiveHandler = null;
|
|
41
|
+
volatile IFn closeHandler = null;
|
|
42
|
+
|
|
43
|
+
static {
|
|
44
|
+
try {
|
|
45
|
+
// Unsafe instead of AtomicReference to save few bytes of RAM per connection
|
|
46
|
+
Field field = Unsafe.class.getDeclaredField("theUnsafe");
|
|
47
|
+
field.setAccessible(true);
|
|
48
|
+
unsafe = (Unsafe) field.get(null);
|
|
49
|
+
|
|
50
|
+
closedRanOffset = unsafe.objectFieldOffset(
|
|
51
|
+
AsyncChannel.class.getDeclaredField("closedRan"));
|
|
52
|
+
closeHandlerOffset = unsafe.objectFieldOffset(
|
|
53
|
+
AsyncChannel.class.getDeclaredField("closeHandler"));
|
|
54
|
+
receiveHandlerOffset = unsafe.objectFieldOffset(
|
|
55
|
+
AsyncChannel.class.getDeclaredField("receiveHandler"));
|
|
56
|
+
headerSentOffset = unsafe.objectFieldOffset(
|
|
57
|
+
AsyncChannel.class.getDeclaredField("isHeaderSent"));
|
|
58
|
+
} catch (Exception e) {
|
|
59
|
+
throw new RuntimeException(e);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// messages sent from a WebSocket client should be handled orderly by server
|
|
65
|
+
// Changed from a Single Thread(IO event thread), no volatile needed
|
|
66
|
+
LinkingRunnable serialTask;
|
|
67
|
+
|
|
68
|
+
public AsyncChannel(SelectionKey key, HttpServer server) {
|
|
69
|
+
this.key = key;
|
|
70
|
+
this.server = server;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public void reset(HttpRequest request) {
|
|
74
|
+
this.request = request;
|
|
75
|
+
serialTask = null;
|
|
76
|
+
unsafe.putOrderedInt(this, closedRanOffset, 0); // lazySet to false
|
|
77
|
+
unsafe.putOrderedInt(this, headerSentOffset, 0);
|
|
78
|
+
|
|
79
|
+
unsafe.putOrderedObject(this, closeHandlerOffset, null); // lazySet to null
|
|
80
|
+
unsafe.putOrderedObject(this, receiveHandlerOffset, null); // lazySet to null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private static final byte[] finalChunkBytes = "0\r\n\r\n".getBytes();
|
|
84
|
+
private static final byte[] newLineBytes = "\r\n".getBytes();
|
|
85
|
+
|
|
86
|
+
private static ByteBuffer chunkSize(int size) {
|
|
87
|
+
String s = Integer.toHexString(size) + "\r\n";
|
|
88
|
+
return ByteBuffer.wrap(s.getBytes());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private void firstWrite(Object data, boolean close) throws IOException {
|
|
92
|
+
ByteBuffer buffers[];
|
|
93
|
+
int status = 200;
|
|
94
|
+
Object body = data;
|
|
95
|
+
HeaderMap headers;
|
|
96
|
+
if (data instanceof Map) {
|
|
97
|
+
Map<Keyword, Object> resp = (Map<Keyword, Object>) data;
|
|
98
|
+
headers = HeaderMap.camelCase((Map) resp.get(HEADERS));
|
|
99
|
+
status = getStatus(resp);
|
|
100
|
+
body = resp.get(BODY);
|
|
101
|
+
} else {
|
|
102
|
+
headers = new HeaderMap();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (headers.isEmpty()) { // default 200 and text/html
|
|
106
|
+
headers.put("Content-Type", "text/html; charset=utf-8");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (request.isKeepAlive && request.version == HttpVersion.HTTP_1_0) {
|
|
110
|
+
headers.put("Connection", "Keep-Alive");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (close) { // normal response, Content-Length. Every http client understand it
|
|
114
|
+
buffers = HttpEncode(status, headers, body);
|
|
115
|
+
} else {
|
|
116
|
+
headers.put("Transfer-Encoding", "chunked"); // first chunk
|
|
117
|
+
ByteBuffer[] bb = HttpEncode(status, headers, body);
|
|
118
|
+
if (body == null) {
|
|
119
|
+
buffers = bb;
|
|
120
|
+
} else {
|
|
121
|
+
buffers = new ByteBuffer[]{bb[0], chunkSize(bb[1].remaining()), bb[1],
|
|
122
|
+
ByteBuffer.wrap(newLineBytes)};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (close) {
|
|
126
|
+
onClose(0);
|
|
127
|
+
}
|
|
128
|
+
server.tryWrite(key, buffers);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
private void writeChunk(Object body, boolean close) throws IOException {
|
|
133
|
+
if (body instanceof Map) { // only get body if a map
|
|
134
|
+
body = ((Map<Keyword, Object>) body).get(BODY);
|
|
135
|
+
}
|
|
136
|
+
if (body != null) { // null is ignored
|
|
137
|
+
ByteBuffer buffers[];
|
|
138
|
+
ByteBuffer t = bodyBuffer(body);
|
|
139
|
+
if (t.hasRemaining()) {
|
|
140
|
+
ByteBuffer size = chunkSize(t.remaining());
|
|
141
|
+
buffers = new ByteBuffer[]{size, t, ByteBuffer.wrap(newLineBytes)};
|
|
142
|
+
server.tryWrite(key, buffers);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (close) {
|
|
146
|
+
serverClose(0);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public void setReceiveHandler(IFn fn) {
|
|
151
|
+
if (!unsafe.compareAndSwapObject(this, receiveHandlerOffset, null, fn)) {
|
|
152
|
+
throw new IllegalStateException("receive handler exist: " + receiveHandler);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public void messageReceived(final Object mesg) {
|
|
157
|
+
IFn f = receiveHandler;
|
|
158
|
+
if (f != null) {
|
|
159
|
+
f.invoke(mesg); // byte[] or String
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public void sendHandshake(Map<String, Object> headers) {
|
|
164
|
+
HeaderMap map = HeaderMap.camelCase(headers);
|
|
165
|
+
server.tryWrite(key, HttpEncode(101, map, null));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public void setCloseHandler(IFn fn) {
|
|
169
|
+
if (!unsafe.compareAndSwapObject(this, closeHandlerOffset, null, fn)) { // only once
|
|
170
|
+
throw new IllegalStateException("close handler exist: " + closeHandler);
|
|
171
|
+
}
|
|
172
|
+
if (closedRan == 1) { // no handler, but already closed
|
|
173
|
+
fn.invoke(K_UNKNOWN);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public void onClose(int status) {
|
|
178
|
+
if (unsafe.compareAndSwapInt(this, closedRanOffset, 0, 1)) {
|
|
179
|
+
IFn f = closeHandler;
|
|
180
|
+
if (f != null) {
|
|
181
|
+
f.invoke(readable(status));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// also sent CloseFrame a final Chunk
|
|
187
|
+
public boolean serverClose(int status) {
|
|
188
|
+
if (!unsafe.compareAndSwapInt(this, closedRanOffset, 0, 1)) {
|
|
189
|
+
return false; // already closed
|
|
190
|
+
}
|
|
191
|
+
if (isWebSocket()) {
|
|
192
|
+
server.tryWrite(key, WsEncode(OPCODE_CLOSE, ByteBuffer.allocate(2)
|
|
193
|
+
.putShort((short) status).array()));
|
|
194
|
+
} else {
|
|
195
|
+
server.tryWrite(key, ByteBuffer.wrap(finalChunkBytes));
|
|
196
|
+
}
|
|
197
|
+
IFn f = closeHandler;
|
|
198
|
+
if (f != null) {
|
|
199
|
+
f.invoke(readable(0)); // server close is 0
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public boolean send(Object data, boolean close) throws IOException {
|
|
205
|
+
if (closedRan == 1) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (isWebSocket()) {
|
|
210
|
+
if (data instanceof Map) { // only get the :body if map
|
|
211
|
+
Object tmp = ((Map<Keyword, Object>) data).get(BODY);
|
|
212
|
+
if (tmp != null) { // save contains(BODY) && get(BODY)
|
|
213
|
+
data = tmp;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (data instanceof String) { // null is not allowed
|
|
218
|
+
server.tryWrite(key, WsEncode(OPCODE_TEXT, ((String) data).getBytes(UTF_8)));
|
|
219
|
+
} else if (data instanceof byte[]) {
|
|
220
|
+
server.tryWrite(key, WsEncode(OPCODE_BINARY, (byte[]) data));
|
|
221
|
+
} else if (data instanceof InputStream) {
|
|
222
|
+
DynamicBytes bytes = readAll((InputStream) data);
|
|
223
|
+
server.tryWrite(key, WsEncode(OPCODE_BINARY, bytes.get(), bytes.length()));
|
|
224
|
+
} else if (data != null) { // ignore null
|
|
225
|
+
String mesg = "send! called with data: " + data.toString() +
|
|
226
|
+
"(" + data.getClass() + "), but only string, byte[], InputStream expected";
|
|
227
|
+
throw new IllegalArgumentException(mesg);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (close) {
|
|
231
|
+
serverClose(1000);
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
if (isHeaderSent == 1) { // HTTP Streaming
|
|
235
|
+
writeChunk(data, close);
|
|
236
|
+
} else {
|
|
237
|
+
isHeaderSent = 1;
|
|
238
|
+
firstWrite(data, close);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public String toString() {
|
|
245
|
+
Socket s = ((SocketChannel) key.channel()).socket();
|
|
246
|
+
return s.getLocalSocketAddress() + "<->" + s.getRemoteSocketAddress();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public boolean isWebSocket() {
|
|
250
|
+
return key.attachment() instanceof WsAtta;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public boolean isClosed() {
|
|
254
|
+
return closedRan == 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static Keyword K_BY_SERVER = Keyword.intern("server-close");
|
|
258
|
+
static Keyword K_CLIENT_CLOSED = Keyword.intern("client-close");
|
|
259
|
+
|
|
260
|
+
// http://datatracker.ietf.org/doc/rfc6455/?include_text=1
|
|
261
|
+
// 7.4.1. Defined Status Codes
|
|
262
|
+
static Keyword K_WS_1000 = Keyword.intern("normal");
|
|
263
|
+
static Keyword K_WS_1001 = Keyword.intern("going-away");
|
|
264
|
+
static Keyword K_WS_1002 = Keyword.intern("protocol-error");
|
|
265
|
+
static Keyword K_WS_1003 = Keyword.intern("unsupported");
|
|
266
|
+
static Keyword K_UNKNOWN = Keyword.intern("unknown");
|
|
267
|
+
|
|
268
|
+
private static Keyword readable(int status) {
|
|
269
|
+
switch (status) {
|
|
270
|
+
case 0:
|
|
271
|
+
return K_BY_SERVER;
|
|
272
|
+
case -1:
|
|
273
|
+
return K_CLIENT_CLOSED;
|
|
274
|
+
case 1000:
|
|
275
|
+
return K_WS_1000;
|
|
276
|
+
case 1001:
|
|
277
|
+
return K_WS_1001;
|
|
278
|
+
case 1002:
|
|
279
|
+
return K_WS_1002;
|
|
280
|
+
case 1003:
|
|
281
|
+
return K_WS_1003;
|
|
282
|
+
default:
|
|
283
|
+
return K_UNKNOWN;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|