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,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