jruby-http-kit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +1 -0
- data/example/config.ru +9 -0
- data/example/hello_world.rb +17 -0
- data/example/rack.rb +11 -0
- data/example/ring.rb +31 -0
- data/example/sinatra/app.rb +9 -0
- data/example/sinatra/config.ru +8 -0
- data/example/webmachine/Gemfile +4 -0
- data/example/webmachine/Gemfile.lock +16 -0
- data/example/webmachine/app.rb +37 -0
- data/jruby-http-kit.gemspec +25 -0
- data/lib/http_kit/rack_handler.rb +139 -0
- data/lib/http_kit/server.rb +62 -0
- data/lib/http_kit/version.rb +3 -0
- data/lib/http_kit.rb +6 -0
- data/lib/java/clojure-1.5.1.jar +0 -0
- data/lib/java/http-kit.jar +0 -0
- data/lib/rack/handler/http_kit.rb +3 -0
- data/lib/rack/http_kit.rb +3 -0
- data/lib/webmachine/adapters/ring.rb +135 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/test_resource.rb +75 -0
- data/spec/webmachine/ring_adapter_spec.rb +127 -0
- data/src/.classpath +7 -0
- data/src/.project +17 -0
- data/src/org/httpkit/BytesInputStream.class +0 -0
- data/src/org/httpkit/BytesInputStream.java +83 -0
- data/src/org/httpkit/DateFormatter.class +0 -0
- data/src/org/httpkit/DynamicBytes.class +0 -0
- data/src/org/httpkit/DynamicBytes.java +76 -0
- data/src/org/httpkit/HTTPException.class +0 -0
- data/src/org/httpkit/HTTPException.java +10 -0
- data/src/org/httpkit/HeaderMap.class +0 -0
- data/src/org/httpkit/HeaderMap.java +98 -0
- data/src/org/httpkit/HttpMethod.class +0 -0
- data/src/org/httpkit/HttpMethod.java +28 -0
- data/src/org/httpkit/HttpStatus.class +0 -0
- data/src/org/httpkit/HttpStatus.java +416 -0
- data/src/org/httpkit/HttpUtils.class +0 -0
- data/src/org/httpkit/HttpUtils.java +484 -0
- data/src/org/httpkit/HttpVersion.class +0 -0
- data/src/org/httpkit/HttpVersion.java +5 -0
- data/src/org/httpkit/LineReader.class +0 -0
- data/src/org/httpkit/LineReader.java +52 -0
- data/src/org/httpkit/LineTooLargeException.class +0 -0
- data/src/org/httpkit/LineTooLargeException.java +13 -0
- data/src/org/httpkit/PrefixThreadFactory.class +0 -0
- data/src/org/httpkit/PrefixThreadFactory.java +20 -0
- data/src/org/httpkit/PriorityQueue.class +0 -0
- data/src/org/httpkit/PriorityQueue.java +235 -0
- data/src/org/httpkit/ProtocolException.class +0 -0
- data/src/org/httpkit/ProtocolException.java +10 -0
- data/src/org/httpkit/RequestTooLargeException.class +0 -0
- data/src/org/httpkit/RequestTooLargeException.java +10 -0
- data/src/org/httpkit/client/AbortException.class +0 -0
- data/src/org/httpkit/client/AbortException.java +13 -0
- data/src/org/httpkit/client/Decoder.class +0 -0
- data/src/org/httpkit/client/Decoder.java +182 -0
- data/src/org/httpkit/client/Handler.class +0 -0
- data/src/org/httpkit/client/HttpClient.class +0 -0
- data/src/org/httpkit/client/HttpClient.java +393 -0
- data/src/org/httpkit/client/HttpsRequest.class +0 -0
- data/src/org/httpkit/client/HttpsRequest.java +141 -0
- data/src/org/httpkit/client/IFilter$1.class +0 -0
- data/src/org/httpkit/client/IFilter$MaxBodyFilter.class +0 -0
- data/src/org/httpkit/client/IFilter.class +0 -0
- data/src/org/httpkit/client/IFilter.java +53 -0
- data/src/org/httpkit/client/IRespListener.class +0 -0
- data/src/org/httpkit/client/IRespListener.java +30 -0
- data/src/org/httpkit/client/IResponseHandler.class +0 -0
- data/src/org/httpkit/client/IResponseHandler.java +18 -0
- data/src/org/httpkit/client/PersistentConn.class +0 -0
- data/src/org/httpkit/client/PersistentConn.java +33 -0
- data/src/org/httpkit/client/Request.class +0 -0
- data/src/org/httpkit/client/Request.java +74 -0
- data/src/org/httpkit/client/RequestConfig.class +0 -0
- data/src/org/httpkit/client/RequestConfig.java +28 -0
- data/src/org/httpkit/client/RespListener.class +0 -0
- data/src/org/httpkit/client/RespListener.java +160 -0
- data/src/org/httpkit/client/SslContextFactory.class +0 -0
- data/src/org/httpkit/client/SslContextFactory.java +79 -0
- data/src/org/httpkit/client/State.class +0 -0
- data/src/org/httpkit/client/TimeoutException.class +0 -0
- data/src/org/httpkit/client/TimeoutException.java +12 -0
- data/src/org/httpkit/client/TrustManagerFactory$1.class +0 -0
- data/src/org/httpkit/client/TrustManagerFactory.class +0 -0
- data/src/org/httpkit/server/AsyncChannel.class +0 -0
- data/src/org/httpkit/server/AsyncChannel.java +286 -0
- data/src/org/httpkit/server/ClojureRing.class +0 -0
- data/src/org/httpkit/server/Frame$BinaryFrame.class +0 -0
- data/src/org/httpkit/server/Frame$CloseFrame.class +0 -0
- data/src/org/httpkit/server/Frame$PingFrame.class +0 -0
- data/src/org/httpkit/server/Frame$TextFrame.class +0 -0
- data/src/org/httpkit/server/Frame.class +0 -0
- data/src/org/httpkit/server/Frame.java +73 -0
- data/src/org/httpkit/server/HttpAtta.class +0 -0
- data/src/org/httpkit/server/HttpAtta.java +10 -0
- data/src/org/httpkit/server/HttpDecoder$State.class +0 -0
- data/src/org/httpkit/server/HttpDecoder.class +0 -0
- data/src/org/httpkit/server/HttpDecoder.java +202 -0
- data/src/org/httpkit/server/HttpHandler.class +0 -0
- data/src/org/httpkit/server/HttpRequest.class +0 -0
- data/src/org/httpkit/server/HttpRequest.java +109 -0
- data/src/org/httpkit/server/HttpServer.class +0 -0
- data/src/org/httpkit/server/HttpServer.java +281 -0
- data/src/org/httpkit/server/IHandler.class +0 -0
- data/src/org/httpkit/server/IHandler.java +12 -0
- data/src/org/httpkit/server/LinkingRunnable.class +0 -0
- data/src/org/httpkit/server/Rack.class +0 -0
- data/src/org/httpkit/server/RackApplication.class +0 -0
- data/src/org/httpkit/server/RackApplication.java +10 -0
- data/src/org/httpkit/server/RackHandler$1.class +0 -0
- data/src/org/httpkit/server/RackHandler.class +0 -0
- data/src/org/httpkit/server/RackHandler.java +259 -0
- data/src/org/httpkit/server/RackHttpHandler.class +0 -0
- data/src/org/httpkit/server/RackHttpHandler.java +20 -0
- data/src/org/httpkit/server/RespCallback.class +0 -0
- data/src/org/httpkit/server/RespCallback.java +19 -0
- data/src/org/httpkit/server/RingHandler$1.class +0 -0
- data/src/org/httpkit/server/RingHandler.class +0 -0
- data/src/org/httpkit/server/RingHandler.java +222 -0
- data/src/org/httpkit/server/ServerAtta.class +0 -0
- data/src/org/httpkit/server/ServerAtta.java +22 -0
- data/src/org/httpkit/server/WSDecoder$State.class +0 -0
- data/src/org/httpkit/server/WSDecoder.class +0 -0
- data/src/org/httpkit/server/WSDecoder.java +181 -0
- data/src/org/httpkit/server/WSHandler.class +0 -0
- data/src/org/httpkit/server/WsAtta.class +0 -0
- data/src/org/httpkit/server/WsAtta.java +11 -0
- data/src/org/httpkit/timer/CancelableFutureTask.class +0 -0
- data/src/org/httpkit/timer/CancelableFutureTask.java +52 -0
- data/src/org/httpkit/timer/TimerService.class +0 -0
- data/src/org/httpkit/timer/TimerService.java +89 -0
- metadata +241 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.HttpUtils;
|
|
4
|
+
|
|
5
|
+
import java.nio.ByteBuffer;
|
|
6
|
+
|
|
7
|
+
public abstract class Frame {
|
|
8
|
+
public final byte[] data;
|
|
9
|
+
|
|
10
|
+
public Frame(byte data[]) {
|
|
11
|
+
this.data = data;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static class BinaryFrame extends Frame {
|
|
15
|
+
public BinaryFrame(byte[] data) {
|
|
16
|
+
super(data);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static class CloseFrame extends Frame {
|
|
21
|
+
|
|
22
|
+
public final static short CLOSE_NORMAL = 1000;
|
|
23
|
+
// indicates that an endpoint is "going away", such as a server going down
|
|
24
|
+
// or a browser having navigated away from a page.
|
|
25
|
+
public final static short CLOSE_AWAY = 1001;
|
|
26
|
+
// public final static short CLOSE_PROTOCOL_ERROR = 1002;
|
|
27
|
+
// public final static short CLOSE_NOT_IMPL = 1003;
|
|
28
|
+
// This is a generic status code that can be returned when there is no other
|
|
29
|
+
// more suitable status code (e.g., 1003 or 1009)
|
|
30
|
+
// public final static short CLOSE_VIOLATES_POLICY = 1008;
|
|
31
|
+
public final static short CLOSE_MESG_BIG = 1009;
|
|
32
|
+
// public final static short CLOSE_SEVER_ERROR = 1011;
|
|
33
|
+
|
|
34
|
+
// private static byte[] bytes(short code) {
|
|
35
|
+
// return ByteBuffer.allocate(2).putShort(code).array();
|
|
36
|
+
// }
|
|
37
|
+
|
|
38
|
+
// public static final CloseFrame NORMAL = new CloseFrame(bytes(CLOSE_NORMAL));
|
|
39
|
+
// public static final CloseFrame AWAY = new CloseFrame(bytes(CLOSE_AWAY));
|
|
40
|
+
// public static final CloseFrame MESG_BIG = new CloseFrame(bytes(CLOSE_MESG_BIG));
|
|
41
|
+
// public static final CloseFrame SERVER_ERROR = new CloseFrame(bytes(CLOSE_MESG_BIG));
|
|
42
|
+
|
|
43
|
+
public CloseFrame(byte[] data) {
|
|
44
|
+
super(data);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public int getStatus() {
|
|
48
|
+
if (data.length >= 2) {
|
|
49
|
+
return ByteBuffer.wrap(data, 0, 2).getShort();
|
|
50
|
+
}
|
|
51
|
+
return CLOSE_NORMAL;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public static class PingFrame extends Frame {
|
|
56
|
+
public PingFrame(byte[] data) {
|
|
57
|
+
super(data);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public static class TextFrame extends Frame {
|
|
62
|
+
private final String msg;
|
|
63
|
+
|
|
64
|
+
public TextFrame(byte[] data) {
|
|
65
|
+
super(data);
|
|
66
|
+
this.msg = new String(data, HttpUtils.UTF_8);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public String getText() {
|
|
70
|
+
return msg;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.*;
|
|
4
|
+
|
|
5
|
+
import java.nio.ByteBuffer;
|
|
6
|
+
import java.util.Arrays;
|
|
7
|
+
import java.util.Map;
|
|
8
|
+
import java.util.TreeMap;
|
|
9
|
+
|
|
10
|
+
import static org.httpkit.HttpUtils.*;
|
|
11
|
+
import static org.httpkit.HttpVersion.HTTP_1_0;
|
|
12
|
+
import static org.httpkit.HttpVersion.HTTP_1_1;
|
|
13
|
+
|
|
14
|
+
public class HttpDecoder {
|
|
15
|
+
|
|
16
|
+
public enum State {
|
|
17
|
+
ALL_READ, READ_INITIAL, READ_HEADER, READ_FIXED_LENGTH_CONTENT, READ_CHUNK_SIZE, READ_CHUNKED_CONTENT, READ_CHUNK_FOOTER, READ_CHUNK_DELIMITER,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private State state = State.READ_INITIAL;
|
|
21
|
+
private int readRemaining = 0; // bytes need read
|
|
22
|
+
private int readCount = 0; // already read bytes count
|
|
23
|
+
|
|
24
|
+
HttpRequest request; // package visible
|
|
25
|
+
private Map<String, Object> headers = new TreeMap<String, Object>();
|
|
26
|
+
byte[] content;
|
|
27
|
+
|
|
28
|
+
private final int maxBody;
|
|
29
|
+
private final LineReader lineReader;
|
|
30
|
+
|
|
31
|
+
public HttpDecoder(int maxBody, int maxLine) {
|
|
32
|
+
this.maxBody = maxBody;
|
|
33
|
+
this.lineReader = new LineReader(maxLine);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private void createRequest(String sb) throws ProtocolException {
|
|
37
|
+
int aStart;
|
|
38
|
+
int aEnd;
|
|
39
|
+
int bStart;
|
|
40
|
+
int bEnd;
|
|
41
|
+
int cStart;
|
|
42
|
+
int cEnd;
|
|
43
|
+
|
|
44
|
+
aStart = findNonWhitespace(sb, 0);
|
|
45
|
+
aEnd = findWhitespace(sb, aStart);
|
|
46
|
+
|
|
47
|
+
bStart = findNonWhitespace(sb, aEnd);
|
|
48
|
+
bEnd = findWhitespace(sb, bStart);
|
|
49
|
+
|
|
50
|
+
cStart = findNonWhitespace(sb, bEnd);
|
|
51
|
+
cEnd = findEndOfString(sb);
|
|
52
|
+
|
|
53
|
+
if (cStart < cEnd) {
|
|
54
|
+
try {
|
|
55
|
+
HttpMethod method = HttpMethod.valueOf(sb.substring(aStart, aEnd).toUpperCase());
|
|
56
|
+
HttpVersion version = HTTP_1_1;
|
|
57
|
+
if ("HTTP/1.0".equals(sb.substring(cStart, cEnd))) {
|
|
58
|
+
version = HTTP_1_0;
|
|
59
|
+
}
|
|
60
|
+
request = new HttpRequest(method, sb.substring(bStart, bEnd), version);
|
|
61
|
+
} catch (Exception e) {
|
|
62
|
+
throw new ProtocolException("method not understand");
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
throw new ProtocolException("not http?");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public HttpRequest decode(ByteBuffer buffer) throws LineTooLargeException,
|
|
70
|
+
ProtocolException, RequestTooLargeException {
|
|
71
|
+
String line;
|
|
72
|
+
while (buffer.hasRemaining()) {
|
|
73
|
+
switch (state) {
|
|
74
|
+
case ALL_READ:
|
|
75
|
+
return request;
|
|
76
|
+
case READ_INITIAL:
|
|
77
|
+
line = lineReader.readLine(buffer);
|
|
78
|
+
if (line != null) {
|
|
79
|
+
createRequest(line);
|
|
80
|
+
state = State.READ_HEADER;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case READ_HEADER:
|
|
84
|
+
readHeaders(buffer);
|
|
85
|
+
break;
|
|
86
|
+
case READ_CHUNK_SIZE:
|
|
87
|
+
line = lineReader.readLine(buffer);
|
|
88
|
+
if (line != null) {
|
|
89
|
+
readRemaining = getChunkSize(line);
|
|
90
|
+
if (readRemaining == 0) {
|
|
91
|
+
state = State.READ_CHUNK_FOOTER;
|
|
92
|
+
} else {
|
|
93
|
+
throwIfBodyIsTooLarge();
|
|
94
|
+
if (content == null) {
|
|
95
|
+
content = new byte[readRemaining];
|
|
96
|
+
} else if (content.length < readCount + readRemaining) {
|
|
97
|
+
// *1.3 to protect slow client
|
|
98
|
+
int newLength = (int) ((readRemaining + readCount) * 1.3);
|
|
99
|
+
content = Arrays.copyOf(content, newLength);
|
|
100
|
+
}
|
|
101
|
+
state = State.READ_CHUNKED_CONTENT;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case READ_FIXED_LENGTH_CONTENT:
|
|
106
|
+
readFixedLength(buffer);
|
|
107
|
+
if (readRemaining == 0) {
|
|
108
|
+
finish();
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case READ_CHUNKED_CONTENT:
|
|
112
|
+
readFixedLength(buffer);
|
|
113
|
+
if (readRemaining == 0) {
|
|
114
|
+
state = State.READ_CHUNK_DELIMITER;
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
case READ_CHUNK_FOOTER:
|
|
118
|
+
readEmptyLine(buffer);
|
|
119
|
+
finish();
|
|
120
|
+
break;
|
|
121
|
+
case READ_CHUNK_DELIMITER:
|
|
122
|
+
readEmptyLine(buffer);
|
|
123
|
+
state = State.READ_CHUNK_SIZE;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return state == State.ALL_READ ? request : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private void finish() {
|
|
131
|
+
state = State.ALL_READ;
|
|
132
|
+
request.setBody(content, readCount);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
void readEmptyLine(ByteBuffer buffer) {
|
|
136
|
+
byte b = buffer.get();
|
|
137
|
+
if (b == CR && buffer.hasRemaining()) {
|
|
138
|
+
buffer.get(); // should be LF
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
void readFixedLength(ByteBuffer buffer) {
|
|
143
|
+
int toRead = Math.min(buffer.remaining(), readRemaining);
|
|
144
|
+
buffer.get(content, readCount, toRead);
|
|
145
|
+
readRemaining -= toRead;
|
|
146
|
+
readCount += toRead;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private void readHeaders(ByteBuffer buffer) throws LineTooLargeException,
|
|
150
|
+
RequestTooLargeException, ProtocolException {
|
|
151
|
+
String line = lineReader.readLine(buffer);
|
|
152
|
+
while (line != null && !line.isEmpty()) {
|
|
153
|
+
HttpUtils.splitAndAddHeader(line, headers);
|
|
154
|
+
line = lineReader.readLine(buffer);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (line == null) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
request.setHeaders(headers);
|
|
162
|
+
|
|
163
|
+
String te = HttpUtils.getStringValue(headers, TRANSFER_ENCODING);
|
|
164
|
+
if (CHUNKED.equals(te)) {
|
|
165
|
+
state = State.READ_CHUNK_SIZE;
|
|
166
|
+
} else {
|
|
167
|
+
String cl = HttpUtils.getStringValue(headers, CONTENT_LENGTH);
|
|
168
|
+
if (cl != null) {
|
|
169
|
+
try {
|
|
170
|
+
readRemaining = Integer.parseInt(cl);
|
|
171
|
+
if (readRemaining > 0) {
|
|
172
|
+
throwIfBodyIsTooLarge();
|
|
173
|
+
content = new byte[readRemaining];
|
|
174
|
+
state = State.READ_FIXED_LENGTH_CONTENT;
|
|
175
|
+
} else {
|
|
176
|
+
state = State.ALL_READ;
|
|
177
|
+
}
|
|
178
|
+
} catch (NumberFormatException e) {
|
|
179
|
+
throw new ProtocolException(e.getMessage());
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
state = State.ALL_READ;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public void reset() {
|
|
188
|
+
state = State.READ_INITIAL;
|
|
189
|
+
headers = new TreeMap<String, Object>();
|
|
190
|
+
readCount = 0;
|
|
191
|
+
content = null;
|
|
192
|
+
lineReader.reset();
|
|
193
|
+
request = null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private void throwIfBodyIsTooLarge() throws RequestTooLargeException {
|
|
197
|
+
if (readCount + readRemaining > maxBody) {
|
|
198
|
+
throw new RequestTooLargeException("request body " + (readCount + readRemaining)
|
|
199
|
+
+ "; max request body " + maxBody);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.*;
|
|
4
|
+
|
|
5
|
+
import java.io.InputStream;
|
|
6
|
+
import java.net.InetSocketAddress;
|
|
7
|
+
import java.util.Map;
|
|
8
|
+
|
|
9
|
+
import static org.httpkit.HttpUtils.*;
|
|
10
|
+
import static org.httpkit.HttpVersion.HTTP_1_1;
|
|
11
|
+
|
|
12
|
+
public class HttpRequest {
|
|
13
|
+
public final String queryString;
|
|
14
|
+
public final String uri;
|
|
15
|
+
public final HttpMethod method;
|
|
16
|
+
public final HttpVersion version;
|
|
17
|
+
|
|
18
|
+
private byte[] body;
|
|
19
|
+
|
|
20
|
+
public int serverPort = 80;
|
|
21
|
+
public String serverName;
|
|
22
|
+
public Map<String, Object> headers;
|
|
23
|
+
public int contentLength = 0;
|
|
24
|
+
public String contentType;
|
|
25
|
+
public String charset = "utf8";
|
|
26
|
+
public boolean isKeepAlive = false;
|
|
27
|
+
public boolean isWebSocket = false;
|
|
28
|
+
|
|
29
|
+
public InetSocketAddress remoteAddr;
|
|
30
|
+
public AsyncChannel channel;
|
|
31
|
+
|
|
32
|
+
public HttpRequest(HttpMethod method, String url, HttpVersion version) {
|
|
33
|
+
this.method = method;
|
|
34
|
+
this.version = version;
|
|
35
|
+
int idx = url.indexOf('?');
|
|
36
|
+
if (idx > 0) {
|
|
37
|
+
uri = url.substring(0, idx);
|
|
38
|
+
queryString = url.substring(idx + 1);
|
|
39
|
+
} else {
|
|
40
|
+
uri = url;
|
|
41
|
+
queryString = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public InputStream getBody() {
|
|
46
|
+
if (body != null) {
|
|
47
|
+
return new BytesInputStream(body, contentLength);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public String getRemoteAddr() {
|
|
53
|
+
String h = getStringValue(headers, HttpUtils.X_FORWARDED_FOR);
|
|
54
|
+
if (null != h) {
|
|
55
|
+
int idx = h.indexOf(',');
|
|
56
|
+
if (idx == -1) {
|
|
57
|
+
return h;
|
|
58
|
+
} else {
|
|
59
|
+
// X-Forwarded-For: client, proxy1, proxy2
|
|
60
|
+
return h.substring(0, idx);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
return remoteAddr.getAddress().getHostAddress();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public void setBody(byte[] body, int count) {
|
|
68
|
+
this.body = body;
|
|
69
|
+
this.contentLength = count;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public void setHeaders(Map<String, Object> headers) {
|
|
73
|
+
String h = getStringValue(headers, "host");
|
|
74
|
+
if (h != null) {
|
|
75
|
+
int idx = h.lastIndexOf(':');
|
|
76
|
+
if (idx != -1) {
|
|
77
|
+
this.serverName = h.substring(0, idx);
|
|
78
|
+
serverPort = Integer.valueOf(h.substring(idx + 1));
|
|
79
|
+
} else {
|
|
80
|
+
this.serverName = h;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
String ct = getStringValue(headers, CONTENT_TYPE);
|
|
85
|
+
if (ct != null) {
|
|
86
|
+
int idx = ct.indexOf(";");
|
|
87
|
+
if (idx != -1) {
|
|
88
|
+
int cidx = ct.indexOf(CHARSET, idx);
|
|
89
|
+
if (cidx != -1) {
|
|
90
|
+
contentType = ct.substring(0, idx);
|
|
91
|
+
charset = ct.substring(cidx + CHARSET.length());
|
|
92
|
+
} else {
|
|
93
|
+
contentType = ct;
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
contentType = ct;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
String con = getStringValue(headers, CONNECTION);
|
|
101
|
+
if (con != null) {
|
|
102
|
+
con = con.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
isKeepAlive = (version == HTTP_1_1 && !"close".equals(con)) || "keep-alive".equals(con);
|
|
106
|
+
isWebSocket = "websocket".equalsIgnoreCase(getStringValue(headers, "upgrade"));
|
|
107
|
+
this.headers = headers;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
package org.httpkit.server;
|
|
2
|
+
|
|
3
|
+
import org.httpkit.*;
|
|
4
|
+
|
|
5
|
+
import java.io.IOException;
|
|
6
|
+
import java.net.InetSocketAddress;
|
|
7
|
+
import java.nio.ByteBuffer;
|
|
8
|
+
import java.nio.channels.*;
|
|
9
|
+
import java.util.*;
|
|
10
|
+
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
11
|
+
|
|
12
|
+
import static java.nio.channels.SelectionKey.*;
|
|
13
|
+
import static org.httpkit.HttpUtils.HttpEncode;
|
|
14
|
+
import static org.httpkit.HttpUtils.WsEncode;
|
|
15
|
+
import static org.httpkit.server.Frame.CloseFrame.*;
|
|
16
|
+
|
|
17
|
+
public class HttpServer implements Runnable {
|
|
18
|
+
|
|
19
|
+
static final String THREAD_NAME = "server-loop";
|
|
20
|
+
|
|
21
|
+
private final IHandler handler;
|
|
22
|
+
private final int maxBody;
|
|
23
|
+
private final int maxLine;
|
|
24
|
+
|
|
25
|
+
private final Selector selector;
|
|
26
|
+
private final ServerSocketChannel serverChannel;
|
|
27
|
+
|
|
28
|
+
private Thread serverThread;
|
|
29
|
+
|
|
30
|
+
private final ConcurrentLinkedQueue<SelectionKey> pending = new ConcurrentLinkedQueue<SelectionKey>();
|
|
31
|
+
// shared, single thread
|
|
32
|
+
private final ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 64);
|
|
33
|
+
|
|
34
|
+
public HttpServer(String ip, int port, IHandler handler, int maxBody, int maxLine)
|
|
35
|
+
throws IOException {
|
|
36
|
+
this.handler = handler;
|
|
37
|
+
this.maxLine = maxLine;
|
|
38
|
+
this.maxBody = maxBody;
|
|
39
|
+
this.selector = Selector.open();
|
|
40
|
+
this.serverChannel = ServerSocketChannel.open();
|
|
41
|
+
serverChannel.configureBlocking(false);
|
|
42
|
+
serverChannel.socket().bind(new InetSocketAddress(ip, port));
|
|
43
|
+
serverChannel.register(selector, OP_ACCEPT);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void accept(SelectionKey key) {
|
|
47
|
+
ServerSocketChannel ch = (ServerSocketChannel) key.channel();
|
|
48
|
+
SocketChannel s;
|
|
49
|
+
try {
|
|
50
|
+
while ((s = ch.accept()) != null) {
|
|
51
|
+
s.configureBlocking(false);
|
|
52
|
+
HttpAtta atta = new HttpAtta(maxBody, maxLine);
|
|
53
|
+
SelectionKey k = s.register(selector, OP_READ, atta);
|
|
54
|
+
atta.channel = new AsyncChannel(k, this);
|
|
55
|
+
}
|
|
56
|
+
} catch (Exception e) {
|
|
57
|
+
// too many open files. do not quit
|
|
58
|
+
HttpUtils.printError("accept incoming request", e);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private void closeKey(final SelectionKey key, int status) {
|
|
63
|
+
try {
|
|
64
|
+
key.channel().close();
|
|
65
|
+
} catch (Exception ignore) {
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ServerAtta att = (ServerAtta) key.attachment();
|
|
69
|
+
if (att instanceof HttpAtta) {
|
|
70
|
+
handler.clientClose(att.channel, -1);
|
|
71
|
+
} else {
|
|
72
|
+
handler.clientClose(att.channel, status);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private void decodeHttp(HttpAtta atta, SelectionKey key, SocketChannel ch) {
|
|
77
|
+
try {
|
|
78
|
+
do {
|
|
79
|
+
AsyncChannel channel = atta.channel;
|
|
80
|
+
HttpRequest request = atta.decoder.decode(buffer);
|
|
81
|
+
if (request != null) {
|
|
82
|
+
channel.reset(request);
|
|
83
|
+
if (request.isWebSocket) {
|
|
84
|
+
key.attach(new WsAtta(channel));
|
|
85
|
+
} else {
|
|
86
|
+
atta.keepalive = request.isKeepAlive;
|
|
87
|
+
}
|
|
88
|
+
request.channel = channel;
|
|
89
|
+
request.remoteAddr = (InetSocketAddress) ch.socket().getRemoteSocketAddress();
|
|
90
|
+
handler.handle(request, new RespCallback(key, this));
|
|
91
|
+
// pipelining not supported : need queue to ensure order
|
|
92
|
+
atta.decoder.reset();
|
|
93
|
+
}
|
|
94
|
+
} while (buffer.hasRemaining()); // consume all
|
|
95
|
+
} catch (ProtocolException e) {
|
|
96
|
+
closeKey(key, -1);
|
|
97
|
+
} catch (RequestTooLargeException e) {
|
|
98
|
+
atta.keepalive = false;
|
|
99
|
+
tryWrite(key, HttpEncode(413, new HeaderMap(), e.getMessage()));
|
|
100
|
+
} catch (LineTooLargeException e) {
|
|
101
|
+
atta.keepalive = false; // close after write
|
|
102
|
+
tryWrite(key, HttpEncode(414, new HeaderMap(), e.getMessage()));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private void decodeWs(WsAtta atta, SelectionKey key) {
|
|
107
|
+
try {
|
|
108
|
+
do {
|
|
109
|
+
Frame frame = atta.decoder.decode(buffer);
|
|
110
|
+
if (frame instanceof TextFrame || frame instanceof BinaryFrame) {
|
|
111
|
+
handler.handle(atta.channel, frame);
|
|
112
|
+
atta.decoder.reset();
|
|
113
|
+
} else if (frame instanceof PingFrame) {
|
|
114
|
+
atta.decoder.reset();
|
|
115
|
+
tryWrite(key, WsEncode(WSDecoder.OPCODE_PONG, frame.data));
|
|
116
|
+
} else if (frame instanceof CloseFrame) {
|
|
117
|
+
handler.clientClose(atta.channel, ((CloseFrame) frame).getStatus());
|
|
118
|
+
// close the TCP connection after sent
|
|
119
|
+
atta.keepalive = false;
|
|
120
|
+
tryWrite(key, WsEncode(WSDecoder.OPCODE_CLOSE, frame.data));
|
|
121
|
+
}
|
|
122
|
+
} while (buffer.hasRemaining()); // consume all
|
|
123
|
+
} catch (ProtocolException e) {
|
|
124
|
+
System.err.printf("%s [%s] WARN - %s\n", new Date(), THREAD_NAME, e.getMessage());
|
|
125
|
+
closeKey(key, CLOSE_MESG_BIG); // TODO more specific error
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private void doRead(final SelectionKey key) {
|
|
130
|
+
SocketChannel ch = (SocketChannel) key.channel();
|
|
131
|
+
try {
|
|
132
|
+
buffer.clear(); // clear for read
|
|
133
|
+
int read = ch.read(buffer);
|
|
134
|
+
if (read == -1) {
|
|
135
|
+
// remote entity shut the socket down cleanly.
|
|
136
|
+
closeKey(key, CLOSE_AWAY);
|
|
137
|
+
} else if (read > 0) {
|
|
138
|
+
buffer.flip(); // flip for read
|
|
139
|
+
final ServerAtta atta = (ServerAtta) key.attachment();
|
|
140
|
+
if (atta instanceof HttpAtta) {
|
|
141
|
+
decodeHttp((HttpAtta) atta, key, ch);
|
|
142
|
+
} else {
|
|
143
|
+
decodeWs((WsAtta) atta, key);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (IOException e) { // the remote forcibly closed the connection
|
|
147
|
+
closeKey(key, CLOSE_AWAY);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private void doWrite(SelectionKey key) {
|
|
152
|
+
ServerAtta atta = (ServerAtta) key.attachment();
|
|
153
|
+
SocketChannel ch = (SocketChannel) key.channel();
|
|
154
|
+
try {
|
|
155
|
+
// the sync is per socket (per client). virtually, no contention
|
|
156
|
+
// 1. keep byte data order, 2. ensure visibility
|
|
157
|
+
synchronized (atta) {
|
|
158
|
+
LinkedList<ByteBuffer> toWrites = atta.toWrites;
|
|
159
|
+
int size = toWrites.size();
|
|
160
|
+
if (size == 1) {
|
|
161
|
+
ch.write(toWrites.get(0));
|
|
162
|
+
// TODO investigate why needed.
|
|
163
|
+
// ws request for write, but has no data?
|
|
164
|
+
} else if (size > 0) {
|
|
165
|
+
ByteBuffer buffers[] = new ByteBuffer[size];
|
|
166
|
+
toWrites.toArray(buffers);
|
|
167
|
+
ch.write(buffers, 0, buffers.length);
|
|
168
|
+
}
|
|
169
|
+
Iterator<ByteBuffer> ite = toWrites.iterator();
|
|
170
|
+
while (ite.hasNext()) {
|
|
171
|
+
if (!ite.next().hasRemaining()) {
|
|
172
|
+
ite.remove();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// all done
|
|
176
|
+
if (toWrites.size() == 0) {
|
|
177
|
+
if (atta.isKeepAlive()) {
|
|
178
|
+
key.interestOps(OP_READ);
|
|
179
|
+
} else {
|
|
180
|
+
closeKey(key, CLOSE_NORMAL);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} catch (IOException e) { // the remote forcibly closed the connection
|
|
185
|
+
closeKey(key, CLOSE_AWAY);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public void tryWrite(final SelectionKey key, ByteBuffer... buffers) {
|
|
190
|
+
ServerAtta atta = (ServerAtta) key.attachment();
|
|
191
|
+
synchronized (atta) {
|
|
192
|
+
if (atta.toWrites.isEmpty()) {
|
|
193
|
+
SocketChannel ch = (SocketChannel) key.channel();
|
|
194
|
+
try {
|
|
195
|
+
// TCP buffer most of time is empty, writable(8K ~ 256k)
|
|
196
|
+
// One IO thread => One thread reading + Many thread writing
|
|
197
|
+
// Save 2 system call
|
|
198
|
+
ch.write(buffers, 0, buffers.length);
|
|
199
|
+
if (buffers[buffers.length - 1].hasRemaining()) {
|
|
200
|
+
for (ByteBuffer b : buffers) {
|
|
201
|
+
if (b.hasRemaining()) {
|
|
202
|
+
atta.toWrites.add(b);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
pending.add(key);
|
|
206
|
+
selector.wakeup();
|
|
207
|
+
} else if (!atta.isKeepAlive()) {
|
|
208
|
+
closeKey(key, CLOSE_NORMAL);
|
|
209
|
+
}
|
|
210
|
+
} catch (IOException e) {
|
|
211
|
+
closeKey(key, CLOSE_AWAY);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// If has pending write, order should be maintained. (WebSocket)
|
|
215
|
+
Collections.addAll(atta.toWrites, buffers);
|
|
216
|
+
pending.add(key);
|
|
217
|
+
selector.wakeup();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public void run() {
|
|
223
|
+
while (true) {
|
|
224
|
+
try {
|
|
225
|
+
SelectionKey k = null;
|
|
226
|
+
while ((k = pending.poll()) != null) {
|
|
227
|
+
if (k.isValid()) {
|
|
228
|
+
k.interestOps(OP_WRITE);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (selector.select() <= 0) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
Set<SelectionKey> selectedKeys = selector.selectedKeys();
|
|
235
|
+
for (SelectionKey key : selectedKeys) {
|
|
236
|
+
// TODO I do not know if this is needed
|
|
237
|
+
// if !valid, isAcceptable, isReadable.. will Exception
|
|
238
|
+
// run hours happily after commented, but not sure.
|
|
239
|
+
if (!key.isValid()) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (key.isAcceptable()) {
|
|
243
|
+
accept(key);
|
|
244
|
+
} else if (key.isReadable()) {
|
|
245
|
+
doRead(key);
|
|
246
|
+
} else if (key.isWritable()) {
|
|
247
|
+
doWrite(key);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
selectedKeys.clear();
|
|
251
|
+
} catch (ClosedSelectorException ignore) {
|
|
252
|
+
return; // stopped
|
|
253
|
+
// do not exits the while IO event loop. if exits, then will not process any IO event
|
|
254
|
+
// jvm can catch any exception, including OOM
|
|
255
|
+
} catch (Throwable e) { // catch any exception(including OOM), print it
|
|
256
|
+
HttpUtils.printError("http server loop error, should not happen", e);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public void start() throws IOException {
|
|
262
|
+
serverThread = new Thread(this, THREAD_NAME);
|
|
263
|
+
serverThread.start();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public void stop() {
|
|
267
|
+
if (selector.isOpen()) {
|
|
268
|
+
try {
|
|
269
|
+
serverChannel.close();
|
|
270
|
+
Set<SelectionKey> keys = selector.keys();
|
|
271
|
+
for (SelectionKey k : keys) {
|
|
272
|
+
k.channel().close();
|
|
273
|
+
}
|
|
274
|
+
selector.close();
|
|
275
|
+
handler.close();
|
|
276
|
+
} catch (IOException ignore) {
|
|
277
|
+
}
|
|
278
|
+
serverThread.interrupt();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
Binary file
|