jruby-http-kit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }