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,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
@@ -0,0 +1,10 @@
1
+ package org.httpkit.server;
2
+
3
+ import java.util.Map;
4
+
5
+ public interface RackApplication {
6
+ void destroy();
7
+
8
+ /** Make a request into the Rack-based Ruby web application. */
9
+ public Object[] call(Map<Object, Object> env);
10
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }