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,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
+
@@ -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
+ }
@@ -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
+ }
@@ -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
@@ -0,0 +1,12 @@
1
+ package org.httpkit.client;
2
+
3
+ import org.httpkit.HTTPException;
4
+
5
+ public class TimeoutException extends HTTPException {
6
+
7
+ private static final long serialVersionUID = 1L;
8
+
9
+ public TimeoutException(String msg) {
10
+ super(msg);
11
+ }
12
+ }
@@ -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