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,12 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
public interface IHandler {
|
|
4
|
+
void handle(HttpRequest request, RespCallback callback);
|
|
5
|
+
|
|
6
|
+
void handle(AsyncChannel channel, Frame frame);
|
|
7
|
+
|
|
8
|
+
public void clientClose(AsyncChannel channel, int status);
|
|
9
|
+
|
|
10
|
+
// close any resource with this handler
|
|
11
|
+
void close();
|
|
12
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import clojure.lang.*;
|
|
4
|
+
import org.httpkit.HeaderMap;
|
|
5
|
+
import org.httpkit.HttpUtils;
|
|
6
|
+
import org.httpkit.PrefixThreadFactory;
|
|
7
|
+
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
import java.util.Arrays;
|
|
10
|
+
import java.util.TreeMap;
|
|
11
|
+
import java.util.concurrent.*;
|
|
12
|
+
import java.util.concurrent.atomic.AtomicReference;
|
|
13
|
+
|
|
14
|
+
import static clojure.lang.Keyword.intern;
|
|
15
|
+
import static org.httpkit.HttpUtils.HttpEncode;
|
|
16
|
+
import static org.httpkit.HttpVersion.HTTP_1_0;
|
|
17
|
+
import static org.httpkit.server.Rack.*;
|
|
18
|
+
import org.httpkit.server.RackHttpHandler;
|
|
19
|
+
import org.httpkit.server.RackApplication;
|
|
20
|
+
import static org.httpkit.server.Frame.TextFrame;
|
|
21
|
+
|
|
22
|
+
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
23
|
+
class Rack {
|
|
24
|
+
|
|
25
|
+
static final Keyword SERVER_PORT = intern("server-port");
|
|
26
|
+
static final Keyword SERVER_NAME = intern("server-name");
|
|
27
|
+
static final Keyword REMOTE_ADDR = intern("remote-addr");
|
|
28
|
+
static final Keyword URI = intern("uri");
|
|
29
|
+
static final Keyword QUERY_STRING = intern("query-string");
|
|
30
|
+
static final Keyword SCHEME = intern("scheme");
|
|
31
|
+
static final Keyword REQUEST_METHOD = intern("request-method");
|
|
32
|
+
static final Keyword HEADERS = intern("headers");
|
|
33
|
+
static final Keyword CONTENT_TYPE = intern("content-type");
|
|
34
|
+
static final Keyword CONTENT_LENGTH = intern("content-length");
|
|
35
|
+
static final Keyword CHARACTER_ENCODING = intern("character-encoding");
|
|
36
|
+
static final Keyword BODY = intern("body");
|
|
37
|
+
static final Keyword WEBSOCKET = intern("websocket?");
|
|
38
|
+
static final Keyword ASYC_CHANNEL = intern("async-channel");
|
|
39
|
+
|
|
40
|
+
static final Keyword HTTP = intern("http");
|
|
41
|
+
|
|
42
|
+
static final Keyword STATUS = intern("status");
|
|
43
|
+
|
|
44
|
+
public static int getStatus(Map<Keyword, Object> resp) {
|
|
45
|
+
int status = 200;
|
|
46
|
+
Object s = resp.get(STATUS);
|
|
47
|
+
if (s instanceof Long) {
|
|
48
|
+
status = ((Long) s).intValue();
|
|
49
|
+
} else if (s instanceof Integer) {
|
|
50
|
+
status = (Integer) s;
|
|
51
|
+
}
|
|
52
|
+
return status;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static Map buildRequestMap(HttpRequest req) {
|
|
56
|
+
Map<String, Object> m = new TreeMap<String, Object>();
|
|
57
|
+
m.put("SERVER_SOFTWARE" , "HTTP Kit");
|
|
58
|
+
m.put("SERVER_NAME" , req.serverName);
|
|
59
|
+
m.put("rack.input" , "#<StringIO:0x007fa1bce039f8>");
|
|
60
|
+
// m.put("rack.version" , [1, 0]);
|
|
61
|
+
m.put("rack.errors" , System.err);
|
|
62
|
+
m.put("rack.multithread" , true);
|
|
63
|
+
m.put("rack.multiprocess" , false);
|
|
64
|
+
m.put("rack.run_once" , false);
|
|
65
|
+
m.put("REQUEST_METHOD" , req.method.KEY);
|
|
66
|
+
m.put("REQUEST_PATH" , "/favicon.ico");
|
|
67
|
+
m.put("PATH_INFO" , "/favicon.ico");
|
|
68
|
+
m.put("REQUEST_URI" , req.uri);
|
|
69
|
+
m.put("HTTP_VERSION" , "HTTP/1.1");
|
|
70
|
+
m.put("HTTP_HOST" , "localhost:8080");
|
|
71
|
+
m.put("HTTP_CONNECTION" , "keep-alive");
|
|
72
|
+
m.put("HTTP_ACCEPT" , "*/*");
|
|
73
|
+
m.put("HTTP_USER_AGENT", "Mozilla/ , 0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11");
|
|
74
|
+
m.put("HTTP_ACCEPT_ENCODING" , "gzip,deflate,sdch");
|
|
75
|
+
m.put("HTTP_ACCEPT_LANGUAGE" , "en-US,en;q=0.8");
|
|
76
|
+
m.put("HTTP_ACCEPT_CHARSET" , "ISO-8859-1,utf-8;q=0.7,*;q=0.3");
|
|
77
|
+
m.put("HTTP_COOKIE" , "_gauges_unique_year=1; _gauges_unique_month=1");
|
|
78
|
+
m.put("GATEWAY_INTERFACE" , "CGI/1.2");
|
|
79
|
+
m.put("SERVER_PORT" , req.serverPort);
|
|
80
|
+
m.put("QUERY_STRING" , req.queryString);
|
|
81
|
+
m.put("SERVER_PROTOCOL" , "HTTP/1.1");
|
|
82
|
+
m.put("rack.url_scheme" , "http"); // only http is supported
|
|
83
|
+
m.put("SCRIPT_NAME" , "");
|
|
84
|
+
m.put("REMOTE_ADDR" , req.getRemoteAddr());
|
|
85
|
+
// m.put("async.callback", "#<Method: Thin::Connection#post_process>");
|
|
86
|
+
// m.put("async.close", "#<EventMachine::DefaultDeferrable:0x007fa1bce35b8");
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
// m.put(URI, req.uri);
|
|
92
|
+
// m.put(ASYC_CHANNEL, req.channel);
|
|
93
|
+
// m.put(WEBSOCKET, req.isWebSocket);
|
|
94
|
+
|
|
95
|
+
// // key is already lower cased, required by ring spec
|
|
96
|
+
// m.put(HEADERS, PersistentArrayMap.create(req.headers));
|
|
97
|
+
// m.put(CONTENT_TYPE, req.contentType);
|
|
98
|
+
// m.put(CONTENT_LENGTH, req.contentLength);
|
|
99
|
+
// m.put(CHARACTER_ENCODING, req.charset);
|
|
100
|
+
// m.put(BODY, req.getBody());
|
|
101
|
+
return m;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// RackEvn
|
|
106
|
+
// {
|
|
107
|
+
// "SERVER_SOFTWARE"=>"thin 1.4.1 codename Chromeo",
|
|
108
|
+
// "SERVER_NAME"=>"localhost",
|
|
109
|
+
// "rack.input"=>#<StringIO:0x007fa1bce039f8>,
|
|
110
|
+
// "rack.version"=>[1, 0],
|
|
111
|
+
// "rack.errors"=>#<IO:<STDERR>>,
|
|
112
|
+
// "rack.multithread"=>false,
|
|
113
|
+
// "rack.multiprocess"=>false,
|
|
114
|
+
// "rack.run_once"=>false,
|
|
115
|
+
// "REQUEST_METHOD"=>"GET",
|
|
116
|
+
// "REQUEST_PATH"=>"/favicon.ico",
|
|
117
|
+
// "PATH_INFO"=>"/favicon.ico",
|
|
118
|
+
// "REQUEST_URI"=>"/favicon.ico",
|
|
119
|
+
// "HTTP_VERSION"=>"HTTP/1.1",
|
|
120
|
+
// "HTTP_HOST"=>"localhost:8080",
|
|
121
|
+
// "HTTP_CONNECTION"=>"keep-alive",
|
|
122
|
+
// "HTTP_ACCEPT"=>"*/*",
|
|
123
|
+
// "HTTP_USER_AGENT"=>
|
|
124
|
+
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11",
|
|
125
|
+
// "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch",
|
|
126
|
+
// "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8",
|
|
127
|
+
// "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.3",
|
|
128
|
+
// "HTTP_COOKIE"=> "_gauges_unique_year=1; _gauges_unique_month=1",
|
|
129
|
+
// "GATEWAY_INTERFACE"=>"CGI/1.2",
|
|
130
|
+
// "SERVER_PORT"=>"8080",
|
|
131
|
+
// "QUERY_STRING"=>"",
|
|
132
|
+
// "SERVER_PROTOCOL"=>"HTTP/1.1",
|
|
133
|
+
// "rack.url_scheme"=>"http",
|
|
134
|
+
// "SCRIPT_NAME"=>"",
|
|
135
|
+
// "REMOTE_ADDR"=>"127.0.0.1",
|
|
136
|
+
// "async.callback"=>#<Method: Thin::Connection#post_process>,
|
|
137
|
+
// "async.close"=>#<EventMachine::DefaultDeferrable:0x007fa1bce35b88
|
|
138
|
+
// }
|
|
139
|
+
|
|
140
|
+
// @SuppressWarnings({"rawtypes", "unchecked"})
|
|
141
|
+
// public class RackHttpHandler implements Runnable {
|
|
142
|
+
|
|
143
|
+
// final HttpRequest req;
|
|
144
|
+
// final RespCallback cb;
|
|
145
|
+
// final RackApplication handler;
|
|
146
|
+
|
|
147
|
+
// public RackHttpHandler(HttpRequest req, RespCallback cb, RackApplication handler) {
|
|
148
|
+
// this.req = req;
|
|
149
|
+
// this.cb = cb;
|
|
150
|
+
// this.handler = handler;
|
|
151
|
+
// }
|
|
152
|
+
|
|
153
|
+
// public void run() {
|
|
154
|
+
// System.out.println("RackHttpHandler run");
|
|
155
|
+
|
|
156
|
+
// try {
|
|
157
|
+
// Object resp_arr[] = handler.call(buildRequestMap(req));
|
|
158
|
+
|
|
159
|
+
// if (resp_arr == null) { // handler return null
|
|
160
|
+
// cb.run(HttpEncode(404, new HeaderMap(), null));
|
|
161
|
+
// } else {
|
|
162
|
+
// Map<Keyword, Object> resp = new TreeMap<Keyword, Object>();
|
|
163
|
+
|
|
164
|
+
// resp.put(STATUS , resp_arr[0]);
|
|
165
|
+
// resp.put(HEADERS , PersistentArrayMap.create((Map) resp_arr[1]));
|
|
166
|
+
// resp.put(BODY , resp_arr[2]);
|
|
167
|
+
|
|
168
|
+
// System.out.println( resp.get(STATUS) );
|
|
169
|
+
|
|
170
|
+
// Object body = resp.get(BODY);
|
|
171
|
+
|
|
172
|
+
// if (!(body instanceof AsyncChannel)) { // hijacked
|
|
173
|
+
// HeaderMap headers = HeaderMap.camelCase((Map) resp.get(HEADERS));
|
|
174
|
+
// if (req.version == HTTP_1_0 && req.isKeepAlive) {
|
|
175
|
+
// headers.put("Connection", "Keep-Alive");
|
|
176
|
+
// }
|
|
177
|
+
// cb.run(HttpEncode(getStatus(resp), headers, body));
|
|
178
|
+
// }
|
|
179
|
+
// }
|
|
180
|
+
// } catch (Throwable e) {
|
|
181
|
+
// cb.run(HttpEncode(500, new HeaderMap(), e.getMessage()));
|
|
182
|
+
// HttpUtils.printError(req.method + " " + req.uri, e);
|
|
183
|
+
// }
|
|
184
|
+
// }
|
|
185
|
+
// }
|
|
186
|
+
|
|
187
|
+
public class RackHandler implements IHandler {
|
|
188
|
+
final ExecutorService execs;
|
|
189
|
+
final RackApplication handler;
|
|
190
|
+
|
|
191
|
+
public RackHandler(int thread, RackApplication handler, String prefix, int queueSize) {
|
|
192
|
+
PrefixThreadFactory factory = new PrefixThreadFactory(prefix);
|
|
193
|
+
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(queueSize);
|
|
194
|
+
execs = new ThreadPoolExecutor(thread, thread, 0, TimeUnit.MILLISECONDS, queue, factory);
|
|
195
|
+
this.handler = handler;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public void handle(HttpRequest req, RespCallback cb) {
|
|
199
|
+
try {
|
|
200
|
+
execs.submit(new RackHttpHandler(req, cb, handler));
|
|
201
|
+
} catch (RejectedExecutionException e) {
|
|
202
|
+
HttpUtils.printError("increase :queue-size if this happens often", e);
|
|
203
|
+
cb.run(HttpEncode(503, new HeaderMap(), "Server is overloaded, please try later"));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public void close() {
|
|
208
|
+
execs.shutdownNow();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public void handle(AsyncChannel channel, Frame frame) {
|
|
212
|
+
WSHandler task = new WSHandler(channel, frame);
|
|
213
|
+
|
|
214
|
+
// messages from the same client are handled orderly
|
|
215
|
+
LinkingRunnable job = new LinkingRunnable(task);
|
|
216
|
+
LinkingRunnable old = channel.serialTask;
|
|
217
|
+
channel.serialTask = job;
|
|
218
|
+
try {
|
|
219
|
+
if (old == null) { // No previous job
|
|
220
|
+
execs.submit(job);
|
|
221
|
+
} else {
|
|
222
|
+
if (!old.next.compareAndSet(null, job)) { // successfully append to previous task
|
|
223
|
+
// previous message is handled, order is guaranteed.
|
|
224
|
+
execs.submit(job);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (RejectedExecutionException e) {
|
|
228
|
+
// TODO notify client if server is overloaded
|
|
229
|
+
HttpUtils.printError("increase :queue-size if this happens often", e);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public void clientClose(final AsyncChannel channel, final int status) {
|
|
234
|
+
if (channel.closedRan == 0) { // server did not close it first
|
|
235
|
+
// has close handler, execute it in another thread
|
|
236
|
+
if (channel.closeHandler != null) {
|
|
237
|
+
try {
|
|
238
|
+
// no need to maintain order
|
|
239
|
+
execs.submit(new Runnable() {
|
|
240
|
+
public void run() {
|
|
241
|
+
try {
|
|
242
|
+
channel.onClose(status);
|
|
243
|
+
} catch (Exception e) {
|
|
244
|
+
HttpUtils.printError("on close handler", e);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
} catch (RejectedExecutionException e) {
|
|
249
|
+
HttpUtils.printError("increase :queue-size if this happens often", e);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
// no close handler, mark the connection as closed
|
|
253
|
+
// channel.closedRan = 1;
|
|
254
|
+
// lazySet
|
|
255
|
+
AsyncChannel.unsafe.putOrderedInt(channel, AsyncChannel.closedRanOffset, 1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import java.util.Map;
|
|
4
|
+
|
|
5
|
+
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
6
|
+
public class RackHttpHandler implements Runnable {
|
|
7
|
+
|
|
8
|
+
final HttpRequest req;
|
|
9
|
+
final RespCallback cb;
|
|
10
|
+
final Object handler;
|
|
11
|
+
|
|
12
|
+
public RackHttpHandler(HttpRequest req, RespCallback cb, Object handler) {
|
|
13
|
+
this.req = req;
|
|
14
|
+
this.cb = cb;
|
|
15
|
+
this.handler = handler;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public void run() {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import java.nio.ByteBuffer;
|
|
4
|
+
import java.nio.channels.SelectionKey;
|
|
5
|
+
|
|
6
|
+
public class RespCallback {
|
|
7
|
+
private final SelectionKey key;
|
|
8
|
+
private final HttpServer server;
|
|
9
|
+
|
|
10
|
+
public RespCallback(SelectionKey key, HttpServer server) {
|
|
11
|
+
this.key = key;
|
|
12
|
+
this.server = server;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// maybe in another thread :worker thread
|
|
16
|
+
public void run(ByteBuffer... buffers) {
|
|
17
|
+
server.tryWrite(key, buffers);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import clojure.lang.*;
|
|
4
|
+
import org.httpkit.HeaderMap;
|
|
5
|
+
import org.httpkit.HttpUtils;
|
|
6
|
+
import org.httpkit.PrefixThreadFactory;
|
|
7
|
+
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
import java.util.TreeMap;
|
|
10
|
+
import java.util.concurrent.*;
|
|
11
|
+
import java.util.concurrent.atomic.AtomicReference;
|
|
12
|
+
|
|
13
|
+
import static clojure.lang.Keyword.intern;
|
|
14
|
+
import static org.httpkit.HttpUtils.HttpEncode;
|
|
15
|
+
import static org.httpkit.HttpVersion.HTTP_1_0;
|
|
16
|
+
import static org.httpkit.server.ClojureRing.*;
|
|
17
|
+
import static org.httpkit.server.Frame.TextFrame;
|
|
18
|
+
|
|
19
|
+
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
20
|
+
class ClojureRing {
|
|
21
|
+
|
|
22
|
+
static final Keyword SERVER_PORT = intern("server-port");
|
|
23
|
+
static final Keyword SERVER_NAME = intern("server-name");
|
|
24
|
+
static final Keyword REMOTE_ADDR = intern("remote-addr");
|
|
25
|
+
static final Keyword URI = intern("uri");
|
|
26
|
+
static final Keyword QUERY_STRING = intern("query-string");
|
|
27
|
+
static final Keyword SCHEME = intern("scheme");
|
|
28
|
+
static final Keyword REQUEST_METHOD = intern("request-method");
|
|
29
|
+
static final Keyword HEADERS = intern("headers");
|
|
30
|
+
static final Keyword CONTENT_TYPE = intern("content-type");
|
|
31
|
+
static final Keyword CONTENT_LENGTH = intern("content-length");
|
|
32
|
+
static final Keyword CHARACTER_ENCODING = intern("character-encoding");
|
|
33
|
+
static final Keyword BODY = intern("body");
|
|
34
|
+
static final Keyword WEBSOCKET = intern("websocket?");
|
|
35
|
+
static final Keyword ASYC_CHANNEL = intern("async-channel");
|
|
36
|
+
|
|
37
|
+
static final Keyword HTTP = intern("http");
|
|
38
|
+
|
|
39
|
+
static final Keyword STATUS = intern("status");
|
|
40
|
+
|
|
41
|
+
public static int getStatus(Map<Keyword, Object> resp) {
|
|
42
|
+
int status = 200;
|
|
43
|
+
Object s = resp.get(STATUS);
|
|
44
|
+
if (s instanceof Long) {
|
|
45
|
+
status = ((Long) s).intValue();
|
|
46
|
+
} else if (s instanceof Integer) {
|
|
47
|
+
status = (Integer) s;
|
|
48
|
+
}
|
|
49
|
+
return status;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static IPersistentMap buildRequestMap(HttpRequest req) {
|
|
53
|
+
// ring spec
|
|
54
|
+
Map<Object, Object> m = new TreeMap<Object, Object>();
|
|
55
|
+
m.put(SERVER_PORT, req.serverPort);
|
|
56
|
+
m.put(SERVER_NAME, req.serverName);
|
|
57
|
+
m.put(REMOTE_ADDR, req.getRemoteAddr());
|
|
58
|
+
m.put(URI, req.uri);
|
|
59
|
+
m.put(QUERY_STRING, req.queryString);
|
|
60
|
+
m.put(SCHEME, HTTP); // only http is supported
|
|
61
|
+
m.put(ASYC_CHANNEL, req.channel);
|
|
62
|
+
m.put(WEBSOCKET, req.isWebSocket);
|
|
63
|
+
m.put(REQUEST_METHOD, req.method.KEY);
|
|
64
|
+
|
|
65
|
+
// key is already lower cased, required by ring spec
|
|
66
|
+
m.put(HEADERS, PersistentArrayMap.create(req.headers));
|
|
67
|
+
m.put(CONTENT_TYPE, req.contentType);
|
|
68
|
+
m.put(CONTENT_LENGTH, req.contentLength);
|
|
69
|
+
m.put(CHARACTER_ENCODING, req.charset);
|
|
70
|
+
m.put(BODY, req.getBody());
|
|
71
|
+
return PersistentArrayMap.create(m);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
77
|
+
class HttpHandler implements Runnable {
|
|
78
|
+
|
|
79
|
+
final HttpRequest req;
|
|
80
|
+
final RespCallback cb;
|
|
81
|
+
final IFn handler;
|
|
82
|
+
|
|
83
|
+
public HttpHandler(HttpRequest req, RespCallback cb, IFn handler) {
|
|
84
|
+
this.req = req;
|
|
85
|
+
this.cb = cb;
|
|
86
|
+
this.handler = handler;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public void run() {
|
|
90
|
+
try {
|
|
91
|
+
Map resp = (Map) handler.invoke(buildRequestMap(req));
|
|
92
|
+
if (resp == null) { // handler return null
|
|
93
|
+
cb.run(HttpEncode(404, new HeaderMap(), null));
|
|
94
|
+
} else {
|
|
95
|
+
Object body = resp.get(BODY);
|
|
96
|
+
if (!(body instanceof AsyncChannel)) { // hijacked
|
|
97
|
+
HeaderMap headers = HeaderMap.camelCase((Map) resp.get(HEADERS));
|
|
98
|
+
if (req.version == HTTP_1_0 && req.isKeepAlive) {
|
|
99
|
+
headers.put("Connection", "Keep-Alive");
|
|
100
|
+
}
|
|
101
|
+
cb.run(HttpEncode(getStatus(resp), headers, body));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (Throwable e) {
|
|
105
|
+
cb.run(HttpEncode(500, new HeaderMap(), e.getMessage()));
|
|
106
|
+
HttpUtils.printError(req.method + " " + req.uri, e);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class LinkingRunnable implements Runnable {
|
|
112
|
+
private final Runnable impl;
|
|
113
|
+
AtomicReference<LinkingRunnable> next = new AtomicReference<LinkingRunnable>(null);
|
|
114
|
+
|
|
115
|
+
public LinkingRunnable(Runnable r) {
|
|
116
|
+
this.impl = r;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public void run() {
|
|
120
|
+
impl.run();
|
|
121
|
+
if (!next.compareAndSet(null, this)) { // has more job to run
|
|
122
|
+
next.get().run();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
class WSHandler implements Runnable {
|
|
128
|
+
private Frame frame;
|
|
129
|
+
private AsyncChannel channel;
|
|
130
|
+
|
|
131
|
+
protected WSHandler(AsyncChannel channel, Frame frame) {
|
|
132
|
+
this.channel = channel;
|
|
133
|
+
this.frame = frame;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Override
|
|
137
|
+
public void run() {
|
|
138
|
+
try {
|
|
139
|
+
if (frame instanceof TextFrame) {
|
|
140
|
+
channel.messageReceived(((TextFrame) frame).getText());
|
|
141
|
+
} else {
|
|
142
|
+
channel.messageReceived(frame.data);
|
|
143
|
+
}
|
|
144
|
+
} catch (Throwable e) {
|
|
145
|
+
HttpUtils.printError("handle websocket frame " + frame, e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public class RingHandler implements IHandler {
|
|
151
|
+
final ExecutorService execs;
|
|
152
|
+
final IFn handler;
|
|
153
|
+
|
|
154
|
+
public RingHandler(int thread, IFn handler, String prefix, int queueSize) {
|
|
155
|
+
PrefixThreadFactory factory = new PrefixThreadFactory(prefix);
|
|
156
|
+
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(queueSize);
|
|
157
|
+
execs = new ThreadPoolExecutor(thread, thread, 0, TimeUnit.MILLISECONDS, queue, factory);
|
|
158
|
+
this.handler = handler;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public void handle(HttpRequest req, RespCallback cb) {
|
|
162
|
+
try {
|
|
163
|
+
execs.submit(new HttpHandler(req, cb, handler));
|
|
164
|
+
} catch (RejectedExecutionException e) {
|
|
165
|
+
HttpUtils.printError("increase :queue-size if this happens often", e);
|
|
166
|
+
cb.run(HttpEncode(503, new HeaderMap(), "Server is overloaded, please try later"));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public void close() {
|
|
171
|
+
execs.shutdownNow();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public void handle(AsyncChannel channel, Frame frame) {
|
|
175
|
+
WSHandler task = new WSHandler(channel, frame);
|
|
176
|
+
|
|
177
|
+
// messages from the same client are handled orderly
|
|
178
|
+
LinkingRunnable job = new LinkingRunnable(task);
|
|
179
|
+
LinkingRunnable old = channel.serialTask;
|
|
180
|
+
channel.serialTask = job;
|
|
181
|
+
try {
|
|
182
|
+
if (old == null) { // No previous job
|
|
183
|
+
execs.submit(job);
|
|
184
|
+
} else {
|
|
185
|
+
if (!old.next.compareAndSet(null, job)) { // successfully append to previous task
|
|
186
|
+
// previous message is handled, order is guaranteed.
|
|
187
|
+
execs.submit(job);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} catch (RejectedExecutionException e) {
|
|
191
|
+
// TODO notify client if server is overloaded
|
|
192
|
+
HttpUtils.printError("increase :queue-size if this happens often", e);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public void clientClose(final AsyncChannel channel, final int status) {
|
|
197
|
+
if (channel.closedRan == 0) { // server did not close it first
|
|
198
|
+
// has close handler, execute it in another thread
|
|
199
|
+
if (channel.closeHandler != null) {
|
|
200
|
+
try {
|
|
201
|
+
// no need to maintain order
|
|
202
|
+
execs.submit(new Runnable() {
|
|
203
|
+
public void run() {
|
|
204
|
+
try {
|
|
205
|
+
channel.onClose(status);
|
|
206
|
+
} catch (Exception e) {
|
|
207
|
+
HttpUtils.printError("on close handler", e);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} catch (RejectedExecutionException e) {
|
|
212
|
+
HttpUtils.printError("increase :queue-size if this happens often", e);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// no close handler, mark the connection as closed
|
|
216
|
+
// channel.closedRan = 1;
|
|
217
|
+
// lazySet
|
|
218
|
+
AsyncChannel.unsafe.putOrderedInt(channel, AsyncChannel.closedRanOffset, 1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import java.nio.ByteBuffer;
|
|
4
|
+
import java.util.LinkedList;
|
|
5
|
+
|
|
6
|
+
public abstract class ServerAtta {
|
|
7
|
+
final LinkedList<ByteBuffer> toWrites = new LinkedList<ByteBuffer>();
|
|
8
|
+
|
|
9
|
+
protected AsyncChannel channel;
|
|
10
|
+
|
|
11
|
+
// close the connection after write?
|
|
12
|
+
|
|
13
|
+
/* HTTP: greedy, if client support it( HTTP/1.1 without keep-alive: close),
|
|
14
|
+
http-kit only close the socket after client first close it
|
|
15
|
+
WebSocket: When a close frame is received, the socket get closed after the response close frame is sent
|
|
16
|
+
*/
|
|
17
|
+
protected boolean keepalive = true;
|
|
18
|
+
|
|
19
|
+
public boolean isKeepAlive() {
|
|
20
|
+
return keepalive;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
Binary file
|
|
Binary file
|