h1p 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +32 -0
- data/ext/h1p/h1p.c +187 -41
- data/ext/h1p/h1p.h +5 -0
- data/lib/h1p/version.rb +1 -1
- data/lib/h1p.rb +12 -6
- data/test/test_h1p_server.rb +55 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53aadd6f6c4ae112ff844b68e8325fe334d51dd0a09bb11cc54d017190e97677
|
4
|
+
data.tar.gz: e9f6d504813d74c050a46b4e973e9fbc227dd790c02395a0073b62f43a7393da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ac98c2d7e702f8cf5f9052e78e9abe486ee3ba0814c4c16fe222acdccfb5338ac9d5c9b4a5d1da2178fca65472693c94a4997cc86de05db70f13c90f1bc767e
|
7
|
+
data.tar.gz: 73ce78e3435eb40f3e6d46ee8eef803284e8da99f32d610dbd723c3661548a74ed454ae70e1ec02dcfbdb70aa346059fb7713eab19de3e6e6e65a4ca859926c7
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -26,6 +26,8 @@ The H1P was originally written as part of
|
|
26
26
|
- Parses both HTTP request and HTTP response
|
27
27
|
- Support for chunked encoding
|
28
28
|
- Support for both `LF` and `CRLF` line breaks
|
29
|
+
- Support for **splicing** request/response bodies (when used with
|
30
|
+
[Polyphony](https://github.com/digital-fabric/polyphony))
|
29
31
|
- Track total incoming traffic
|
30
32
|
|
31
33
|
## Installing
|
@@ -161,6 +163,36 @@ end
|
|
161
163
|
The `#read_body` and `#read_body_chunk` methods will return `nil` if no body is
|
162
164
|
expected (based on the received headers).
|
163
165
|
|
166
|
+
## Splicing request/response bodies
|
167
|
+
|
168
|
+
> Splicing of request/response bodies is available only on Linux, and works only
|
169
|
+
> with [Polyphony](https://github.com/digital-fabric/polyphony).
|
170
|
+
|
171
|
+
H1P also lets you [splice](https://man7.org/linux/man-pages/man2/splice.2.html)
|
172
|
+
request or response bodies directly to a pipe. This is particularly useful for
|
173
|
+
uploading or downloading large files, as the data does not need to be loaded
|
174
|
+
into Ruby strings. In fact, the data will stay almost entirely in kernel
|
175
|
+
buffers, which means any data copying is reduced to the absolute minimum.
|
176
|
+
|
177
|
+
The following example sends a request, then splices the response body to a file:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
require 'polyphony'
|
181
|
+
require 'h1p'
|
182
|
+
|
183
|
+
socket = TCPSocket.new('example.com', 80)
|
184
|
+
socket << "GET /bigfile HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
185
|
+
|
186
|
+
parser = H1P::Parser.new(socket, :client)
|
187
|
+
headers = parser.parse_headers
|
188
|
+
|
189
|
+
pipe = Polyphony.pipe
|
190
|
+
File.open('bigfile', 'w+') do |f|
|
191
|
+
spin { parser.splice_body_to(pipe) }
|
192
|
+
f.splice_from(pipe)
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
164
196
|
## Parsing from arbitrary transports
|
165
197
|
|
166
198
|
The H1P parser was built to read from any arbitrary transport or source, as long
|
data/ext/h1p/h1p.c
CHANGED
@@ -16,6 +16,9 @@
|
|
16
16
|
ID ID_arity;
|
17
17
|
ID ID_backend_read;
|
18
18
|
ID ID_backend_recv;
|
19
|
+
ID ID_backend_send;
|
20
|
+
ID ID_backend_splice;
|
21
|
+
ID ID_backend_write;
|
19
22
|
ID ID_call;
|
20
23
|
ID ID_downcase;
|
21
24
|
ID ID_eof_p;
|
@@ -25,8 +28,8 @@ ID ID_read;
|
|
25
28
|
ID ID_readpartial;
|
26
29
|
ID ID_to_i;
|
27
30
|
ID ID_upcase;
|
31
|
+
ID ID_write_method;
|
28
32
|
|
29
|
-
static VALUE mPolyphony = Qnil;
|
30
33
|
static VALUE cError;
|
31
34
|
|
32
35
|
VALUE NUM_max_headers_read_length;
|
@@ -46,17 +49,24 @@ VALUE STR_transfer_encoding;
|
|
46
49
|
|
47
50
|
VALUE SYM_backend_read;
|
48
51
|
VALUE SYM_backend_recv;
|
52
|
+
VALUE SYM_backend_send;
|
53
|
+
VALUE SYM_backend_write;
|
49
54
|
VALUE SYM_stock_readpartial;
|
50
55
|
|
51
56
|
VALUE SYM_client;
|
52
57
|
VALUE SYM_server;
|
53
58
|
|
54
59
|
enum read_method {
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
RM_READPARTIAL, // receiver.readpartial(len, buf, pos, raise_on_eof: false) (Polyphony-specific)
|
61
|
+
RM_BACKEND_READ, // Polyphony.backend_read (Polyphony-specific)
|
62
|
+
RM_BACKEND_RECV, // Polyphony.backend_recv (Polyphony-specific)
|
63
|
+
RM_CALL, // receiver.call(len) (Universal)
|
64
|
+
RM_STOCK_READPARTIAL // receiver.readpartial(len)
|
65
|
+
};
|
66
|
+
|
67
|
+
enum write_method {
|
68
|
+
WM_BACKEND_WRITE,
|
69
|
+
WM_BACKEND_SEND
|
60
70
|
};
|
61
71
|
|
62
72
|
enum parser_mode {
|
@@ -115,31 +125,40 @@ static VALUE Parser_allocate(VALUE klass) {
|
|
115
125
|
#define GetParser(obj, parser) \
|
116
126
|
TypedData_Get_Struct((obj), Parser_t, &Parser_type, (parser))
|
117
127
|
|
118
|
-
static inline
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
128
|
+
static inline VALUE Polyphony() {
|
129
|
+
static VALUE mPolyphony = Qnil;
|
130
|
+
if (mPolyphony == Qnil) {
|
131
|
+
mPolyphony = rb_const_get(rb_cObject, rb_intern("Polyphony"));
|
132
|
+
rb_gc_register_mark_object(mPolyphony);
|
133
|
+
}
|
134
|
+
return mPolyphony;
|
123
135
|
}
|
124
136
|
|
125
|
-
enum read_method detect_read_method(VALUE io) {
|
137
|
+
static enum read_method detect_read_method(VALUE io) {
|
126
138
|
if (rb_respond_to(io, ID_read_method)) {
|
127
139
|
VALUE method = rb_funcall(io, ID_read_method, 0);
|
128
|
-
if (method == SYM_stock_readpartial) return
|
129
|
-
|
130
|
-
|
131
|
-
if (method == SYM_backend_read) return method_backend_read;
|
132
|
-
if (method == SYM_backend_recv) return method_backend_recv;
|
140
|
+
if (method == SYM_stock_readpartial) return RM_STOCK_READPARTIAL;
|
141
|
+
if (method == SYM_backend_read) return RM_BACKEND_READ;
|
142
|
+
if (method == SYM_backend_recv) return RM_BACKEND_RECV;
|
133
143
|
|
134
|
-
return
|
144
|
+
return RM_READPARTIAL;
|
135
145
|
}
|
136
146
|
else if (rb_respond_to(io, ID_call)) {
|
137
|
-
return
|
147
|
+
return RM_CALL;
|
138
148
|
}
|
139
149
|
else
|
140
150
|
rb_raise(rb_eRuntimeError, "Provided reader should be a callable or respond to #__read_method__");
|
141
151
|
}
|
142
152
|
|
153
|
+
static enum write_method detect_write_method(VALUE io) {
|
154
|
+
if (rb_respond_to(io, ID_write_method)) {
|
155
|
+
VALUE method = rb_funcall(io, ID_write_method, 0);
|
156
|
+
if (method == SYM_backend_write) return WM_BACKEND_WRITE;
|
157
|
+
if (method == SYM_backend_send) return WM_BACKEND_SEND;
|
158
|
+
}
|
159
|
+
rb_raise(rb_eRuntimeError, "Provided io should respond to #__write_method__");
|
160
|
+
}
|
161
|
+
|
143
162
|
enum parser_mode parse_parser_mode(VALUE mode) {
|
144
163
|
if (mode == SYM_server) return mode_server;
|
145
164
|
if (mode == SYM_client) return mode_client;
|
@@ -246,7 +265,7 @@ VALUE Parser_initialize(VALUE self, VALUE io, VALUE mode) {
|
|
246
265
|
}
|
247
266
|
|
248
267
|
#define SET_HEADER_VALUE_INT(parser, key, value) { \
|
249
|
-
rb_hash_aset(parser->headers, key,
|
268
|
+
rb_hash_aset(parser->headers, key, INT2FIX(value)); \
|
250
269
|
}
|
251
270
|
|
252
271
|
#define CONSUME_CRLF(parser) { \
|
@@ -294,21 +313,37 @@ static inline VALUE io_stock_readpartial(VALUE io, VALUE maxlen, VALUE buf, VALU
|
|
294
313
|
|
295
314
|
static inline VALUE parser_io_read(Parser_t *parser, VALUE maxlen, VALUE buf, VALUE buf_pos) {
|
296
315
|
switch (parser->read_method) {
|
297
|
-
case
|
298
|
-
return rb_funcall(
|
299
|
-
case
|
300
|
-
return rb_funcall(
|
301
|
-
case
|
302
|
-
return rb_funcall(parser->
|
303
|
-
case
|
304
|
-
return io_call(parser
|
305
|
-
case
|
316
|
+
case RM_BACKEND_READ:
|
317
|
+
return rb_funcall(Polyphony(), ID_backend_read, 5, parser->io, buf, maxlen, Qfalse, buf_pos);
|
318
|
+
case RM_BACKEND_RECV:
|
319
|
+
return rb_funcall(Polyphony(), ID_backend_recv, 4, parser->io, buf, maxlen, buf_pos);
|
320
|
+
case RM_READPARTIAL:
|
321
|
+
return rb_funcall(parser->io, ID_readpartial, 4, maxlen, buf, buf_pos, Qfalse);
|
322
|
+
case RM_CALL:
|
323
|
+
return io_call(parser->io, maxlen, buf, buf_pos);
|
324
|
+
case RM_STOCK_READPARTIAL:
|
306
325
|
return io_stock_readpartial(parser->io, maxlen, buf, buf_pos);
|
307
326
|
default:
|
308
327
|
return Qnil;
|
309
328
|
}
|
310
329
|
}
|
311
330
|
|
331
|
+
static inline VALUE parser_io_write(VALUE io, VALUE buf, enum write_method method) {
|
332
|
+
switch (method) {
|
333
|
+
case WM_BACKEND_WRITE:
|
334
|
+
return rb_funcall(Polyphony(), ID_backend_write, 2, io, buf);
|
335
|
+
case WM_BACKEND_SEND:
|
336
|
+
return rb_funcall(Polyphony(), ID_backend_send, 3, io, buf, INT2FIX(0));
|
337
|
+
default:
|
338
|
+
return Qnil;
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
static inline int parser_io_splice(VALUE src, VALUE dest, int len) {
|
343
|
+
VALUE ret = rb_funcall(Polyphony(), ID_backend_splice, 3, src, dest, INT2FIX(len));
|
344
|
+
return FIX2INT(ret);
|
345
|
+
}
|
346
|
+
|
312
347
|
static inline int fill_buffer(Parser_t *parser) {
|
313
348
|
VALUE ret = parser_io_read(parser, NUM_max_headers_read_length, parser->buffer, NUM_buffer_end);
|
314
349
|
if (ret == Qnil) return 0;
|
@@ -705,7 +740,7 @@ done:
|
|
705
740
|
|
706
741
|
parser->current_request_rx += read_bytes;
|
707
742
|
if (parser->headers != Qnil)
|
708
|
-
rb_hash_aset(parser->headers, STR_pseudo_rx,
|
743
|
+
rb_hash_aset(parser->headers, STR_pseudo_rx, INT2FIX(read_bytes));
|
709
744
|
return parser->headers;
|
710
745
|
}
|
711
746
|
|
@@ -754,7 +789,7 @@ VALUE read_body_with_content_length(Parser_t *parser, int read_entire_body, int
|
|
754
789
|
|
755
790
|
while (parser->body_left) {
|
756
791
|
int maxlen = parser->body_left <= MAX_BODY_READ_LENGTH ? parser->body_left : MAX_BODY_READ_LENGTH;
|
757
|
-
VALUE tmp_buf = parser_io_read(parser,
|
792
|
+
VALUE tmp_buf = parser_io_read(parser, INT2FIX(maxlen), Qnil, NUM_buffer_start);
|
758
793
|
if (tmp_buf == Qnil) goto eof;
|
759
794
|
if (body != Qnil)
|
760
795
|
rb_str_append(body, tmp_buf);
|
@@ -768,7 +803,7 @@ VALUE read_body_with_content_length(Parser_t *parser, int read_entire_body, int
|
|
768
803
|
if (!read_entire_body) goto done;
|
769
804
|
}
|
770
805
|
done:
|
771
|
-
rb_hash_aset(parser->headers, STR_pseudo_rx,
|
806
|
+
rb_hash_aset(parser->headers, STR_pseudo_rx, INT2FIX(parser->current_request_rx));
|
772
807
|
RB_GC_GUARD(body);
|
773
808
|
return body;
|
774
809
|
eof:
|
@@ -836,7 +871,7 @@ int read_body_chunk_with_chunked_encoding(Parser_t *parser, VALUE *body, int chu
|
|
836
871
|
while (left) {
|
837
872
|
int maxlen = left <= MAX_BODY_READ_LENGTH ? left : MAX_BODY_READ_LENGTH;
|
838
873
|
|
839
|
-
VALUE tmp_buf = parser_io_read(parser,
|
874
|
+
VALUE tmp_buf = parser_io_read(parser, INT2FIX(maxlen), Qnil, NUM_buffer_start);
|
840
875
|
if (tmp_buf == Qnil) goto eof;
|
841
876
|
if (*body != Qnil)
|
842
877
|
rb_str_append(*body, tmp_buf);
|
@@ -852,6 +887,33 @@ eof:
|
|
852
887
|
return 0;
|
853
888
|
}
|
854
889
|
|
890
|
+
int splice_body_chunk_with_chunked_encoding(Parser_t *parser, VALUE dest, int chunk_size, enum write_method method) {
|
891
|
+
int len = RSTRING_LEN(parser->buffer);
|
892
|
+
int pos = BUFFER_POS(parser);
|
893
|
+
int left = chunk_size;
|
894
|
+
|
895
|
+
if (pos < len) {
|
896
|
+
int available = len - pos;
|
897
|
+
if (available > left) available = left;
|
898
|
+
VALUE buf = rb_str_new(RSTRING_PTR(parser->buffer) + pos, available);
|
899
|
+
BUFFER_POS(parser) += available;
|
900
|
+
parser->current_request_rx += available;
|
901
|
+
parser_io_write(dest, buf, method);
|
902
|
+
RB_GC_GUARD(buf);
|
903
|
+
left -= available;
|
904
|
+
}
|
905
|
+
|
906
|
+
while (left) {
|
907
|
+
int spliced = parser_io_splice(parser->io, dest, left);
|
908
|
+
if (!spliced) goto eof;
|
909
|
+
parser->current_request_rx += spliced;
|
910
|
+
left -= spliced;
|
911
|
+
}
|
912
|
+
return 1;
|
913
|
+
eof:
|
914
|
+
return 0;
|
915
|
+
}
|
916
|
+
|
855
917
|
static inline int parse_chunk_postfix(Parser_t *parser) {
|
856
918
|
int initial_pos = BUFFER_POS(parser);
|
857
919
|
if (initial_pos == BUFFER_LEN(parser)) FILL_BUFFER_OR_GOTO_EOF(parser);
|
@@ -897,11 +959,70 @@ bad_request:
|
|
897
959
|
eof:
|
898
960
|
RAISE_BAD_REQUEST("Incomplete request body");
|
899
961
|
done:
|
900
|
-
rb_hash_aset(parser->headers, STR_pseudo_rx,
|
962
|
+
rb_hash_aset(parser->headers, STR_pseudo_rx, INT2FIX(parser->current_request_rx));
|
901
963
|
RB_GC_GUARD(body);
|
902
964
|
return body;
|
903
965
|
}
|
904
966
|
|
967
|
+
void splice_body_with_chunked_encoding(Parser_t *parser, VALUE dest, enum write_method method) {
|
968
|
+
buffer_trim(parser);
|
969
|
+
INIT_PARSER_STATE(parser);
|
970
|
+
|
971
|
+
while (1) {
|
972
|
+
int chunk_size = 0;
|
973
|
+
if (BUFFER_POS(parser) == BUFFER_LEN(parser)) FILL_BUFFER_OR_GOTO_EOF(parser);
|
974
|
+
if (!parse_chunk_size(parser, &chunk_size)) goto bad_request;
|
975
|
+
|
976
|
+
if (chunk_size) {
|
977
|
+
if (!splice_body_chunk_with_chunked_encoding(parser, dest, chunk_size, method))
|
978
|
+
goto bad_request;
|
979
|
+
}
|
980
|
+
else
|
981
|
+
parser->request_completed = 1;
|
982
|
+
|
983
|
+
// read post-chunk delimiter ("\r\n")
|
984
|
+
if (!parse_chunk_postfix(parser)) goto bad_request;
|
985
|
+
if (!chunk_size) goto done;
|
986
|
+
}
|
987
|
+
bad_request:
|
988
|
+
RAISE_BAD_REQUEST("Malformed request body");
|
989
|
+
eof:
|
990
|
+
RAISE_BAD_REQUEST("Incomplete request body");
|
991
|
+
done:
|
992
|
+
rb_hash_aset(parser->headers, STR_pseudo_rx, INT2FIX(parser->current_request_rx));
|
993
|
+
}
|
994
|
+
|
995
|
+
void splice_body_with_content_length(Parser_t *parser, VALUE dest, enum write_method method) {
|
996
|
+
if (parser->body_left <= 0) return;
|
997
|
+
|
998
|
+
int len = RSTRING_LEN(parser->buffer);
|
999
|
+
int pos = BUFFER_POS(parser);
|
1000
|
+
|
1001
|
+
if (pos < len) {
|
1002
|
+
int available = len - pos;
|
1003
|
+
if (available > parser->body_left) available = parser->body_left;
|
1004
|
+
VALUE buf = rb_str_new(RSTRING_PTR(parser->buffer) + pos, available);
|
1005
|
+
BUFFER_POS(parser) += available;
|
1006
|
+
parser_io_write(dest, buf, method);
|
1007
|
+
RB_GC_GUARD(buf);
|
1008
|
+
parser->current_request_rx += available;
|
1009
|
+
parser->body_left -= available;
|
1010
|
+
if (!parser->body_left) parser->request_completed = 1;
|
1011
|
+
}
|
1012
|
+
|
1013
|
+
while (parser->body_left) {
|
1014
|
+
int spliced = parser_io_splice(parser->io, dest, parser->body_left);
|
1015
|
+
if (!spliced) goto eof;
|
1016
|
+
parser->current_request_rx += spliced;
|
1017
|
+
parser->body_left -= spliced;
|
1018
|
+
}
|
1019
|
+
done:
|
1020
|
+
rb_hash_aset(parser->headers, STR_pseudo_rx, INT2FIX(parser->current_request_rx));
|
1021
|
+
return;
|
1022
|
+
eof:
|
1023
|
+
RAISE_BAD_REQUEST("Incomplete body");
|
1024
|
+
}
|
1025
|
+
|
905
1026
|
static inline void detect_body_read_mode(Parser_t *parser) {
|
906
1027
|
VALUE content_length = rb_hash_aref(parser->headers, STR_content_length);
|
907
1028
|
if (content_length != Qnil) {
|
@@ -931,7 +1052,8 @@ static inline VALUE read_body(VALUE self, int read_entire_body, int buffered_onl
|
|
931
1052
|
|
932
1053
|
if (parser->body_read_mode == BODY_READ_MODE_CHUNKED)
|
933
1054
|
return read_body_with_chunked_encoding(parser, read_entire_body, buffered_only);
|
934
|
-
|
1055
|
+
else
|
1056
|
+
return read_body_with_content_length(parser, read_entire_body, buffered_only);
|
935
1057
|
}
|
936
1058
|
|
937
1059
|
VALUE Parser_read_body(VALUE self) {
|
@@ -942,6 +1064,22 @@ VALUE Parser_read_body_chunk(VALUE self, VALUE buffered_only) {
|
|
942
1064
|
return read_body(self, 0, buffered_only == Qtrue);
|
943
1065
|
}
|
944
1066
|
|
1067
|
+
VALUE Parser_splice_body_to(VALUE self, VALUE dest) {
|
1068
|
+
Parser_t *parser;
|
1069
|
+
GetParser(self, parser);
|
1070
|
+
enum write_method method = detect_write_method(dest);
|
1071
|
+
|
1072
|
+
if (parser->body_read_mode == BODY_READ_MODE_UNKNOWN)
|
1073
|
+
detect_body_read_mode(parser);
|
1074
|
+
|
1075
|
+
if (parser->body_read_mode == BODY_READ_MODE_CHUNKED)
|
1076
|
+
splice_body_with_chunked_encoding(parser, dest, method);
|
1077
|
+
else
|
1078
|
+
splice_body_with_content_length(parser, dest, method);
|
1079
|
+
|
1080
|
+
return self;
|
1081
|
+
}
|
1082
|
+
|
945
1083
|
VALUE Parser_complete_p(VALUE self) {
|
946
1084
|
Parser_t *parser;
|
947
1085
|
GetParser(self, parser);
|
@@ -969,24 +1107,29 @@ void Init_H1P() {
|
|
969
1107
|
rb_define_method(cParser, "parse_headers", Parser_parse_headers, 0);
|
970
1108
|
rb_define_method(cParser, "read_body", Parser_read_body, 0);
|
971
1109
|
rb_define_method(cParser, "read_body_chunk", Parser_read_body_chunk, 1);
|
1110
|
+
rb_define_method(cParser, "splice_body_to", Parser_splice_body_to, 1);
|
972
1111
|
rb_define_method(cParser, "complete?", Parser_complete_p, 0);
|
973
1112
|
|
974
1113
|
ID_arity = rb_intern("arity");
|
975
1114
|
ID_backend_read = rb_intern("backend_read");
|
976
1115
|
ID_backend_recv = rb_intern("backend_recv");
|
1116
|
+
ID_backend_send = rb_intern("backend_send");
|
1117
|
+
ID_backend_splice = rb_intern("backend_splice");
|
1118
|
+
ID_backend_write = rb_intern("backend_write");
|
977
1119
|
ID_call = rb_intern("call");
|
978
1120
|
ID_downcase = rb_intern("downcase");
|
979
1121
|
ID_eof_p = rb_intern("eof?");
|
980
1122
|
ID_eq = rb_intern("==");
|
981
|
-
ID_read_method
|
1123
|
+
ID_read_method = rb_intern("__read_method__");
|
982
1124
|
ID_read = rb_intern("read");
|
983
1125
|
ID_readpartial = rb_intern("readpartial");
|
984
1126
|
ID_to_i = rb_intern("to_i");
|
985
1127
|
ID_upcase = rb_intern("upcase");
|
1128
|
+
ID_write_method = rb_intern("__write_method__");
|
986
1129
|
|
987
|
-
NUM_max_headers_read_length =
|
988
|
-
NUM_buffer_start =
|
989
|
-
NUM_buffer_end =
|
1130
|
+
NUM_max_headers_read_length = INT2FIX(MAX_HEADERS_READ_LENGTH);
|
1131
|
+
NUM_buffer_start = INT2FIX(0);
|
1132
|
+
NUM_buffer_end = INT2FIX(-1);
|
990
1133
|
|
991
1134
|
GLOBAL_STR(STR_pseudo_method, ":method");
|
992
1135
|
GLOBAL_STR(STR_pseudo_path, ":path");
|
@@ -999,8 +1142,11 @@ void Init_H1P() {
|
|
999
1142
|
GLOBAL_STR(STR_content_length, "content-length");
|
1000
1143
|
GLOBAL_STR(STR_transfer_encoding, "transfer-encoding");
|
1001
1144
|
|
1002
|
-
SYM_backend_read
|
1003
|
-
SYM_backend_recv
|
1145
|
+
SYM_backend_read = ID2SYM(ID_backend_read);
|
1146
|
+
SYM_backend_recv = ID2SYM(ID_backend_recv);
|
1147
|
+
SYM_backend_send = ID2SYM(ID_backend_send);
|
1148
|
+
SYM_backend_write = ID2SYM(ID_backend_write);
|
1149
|
+
|
1004
1150
|
SYM_stock_readpartial = ID2SYM(rb_intern("stock_readpartial"));
|
1005
1151
|
|
1006
1152
|
SYM_client = ID2SYM(rb_intern("client"));
|
data/ext/h1p/h1p.h
CHANGED
@@ -14,5 +14,10 @@
|
|
14
14
|
for (unsigned long i = 0; i < size; i++) printf("%s\n", strings[i]); \
|
15
15
|
free(strings); \
|
16
16
|
}
|
17
|
+
#define PRINT_BUFFER(prefix, ptr, len) { \
|
18
|
+
printf("%s buffer (%d): ", prefix, (int)len); \
|
19
|
+
for (int i = 0; i < len; i++) printf("%02X ", ptr[i]); \
|
20
|
+
printf("\n"); \
|
21
|
+
}
|
17
22
|
|
18
23
|
#endif /* H1P_H */
|
data/lib/h1p/version.rb
CHANGED
data/lib/h1p.rb
CHANGED
@@ -2,28 +2,34 @@
|
|
2
2
|
|
3
3
|
require_relative './h1p_ext'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
class ::IO
|
6
|
+
if !method_defined?(:__read_method__)
|
7
7
|
def __read_method__
|
8
8
|
:stock_readpartial
|
9
9
|
end
|
10
10
|
end
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
+
require 'socket'
|
13
14
|
|
14
|
-
|
15
|
+
class Socket
|
16
|
+
if !method_defined?(:__read_method__)
|
15
17
|
def __read_method__
|
16
18
|
:stock_readpartial
|
17
19
|
end
|
18
20
|
end
|
21
|
+
end
|
19
22
|
|
20
|
-
|
23
|
+
class TCPSocket
|
24
|
+
if !method_defined?(:__read_method__)
|
21
25
|
def __read_method__
|
22
26
|
:stock_readpartial
|
23
27
|
end
|
24
28
|
end
|
29
|
+
end
|
25
30
|
|
26
|
-
|
31
|
+
class UNIXSocket
|
32
|
+
if !method_defined?(:__read_method__)
|
27
33
|
def __read_method__
|
28
34
|
:stock_readpartial
|
29
35
|
end
|
data/test/test_h1p_server.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative 'helper'
|
|
4
4
|
require 'h1p'
|
5
5
|
require 'socket'
|
6
6
|
require_relative '../ext/h1p/limits'
|
7
|
+
require 'securerandom'
|
7
8
|
|
8
9
|
class H1PRequestTest < MiniTest::Test
|
9
10
|
Error = H1P::Error
|
@@ -446,6 +447,60 @@ class H1PRequestTest < MiniTest::Test
|
|
446
447
|
assert_raises(H1P::Error) { @parser.read_body_chunk(false) }
|
447
448
|
end
|
448
449
|
|
450
|
+
PolyphonyMockup = Object.new
|
451
|
+
def PolyphonyMockup.backend_write(io, buf)
|
452
|
+
io << buf
|
453
|
+
end
|
454
|
+
def PolyphonyMockup.backend_splice(src, dest, len)
|
455
|
+
buf = src.read(len)
|
456
|
+
len = dest.write(buf)
|
457
|
+
len
|
458
|
+
end
|
459
|
+
Object::Polyphony = PolyphonyMockup
|
460
|
+
|
461
|
+
def test_splice_body_to_chunked_encoding
|
462
|
+
req_body = SecureRandom.alphanumeric(60000)
|
463
|
+
req_headers = "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
|
464
|
+
r, w = IO.pipe
|
465
|
+
|
466
|
+
Thread.new do
|
467
|
+
@o << req_headers
|
468
|
+
@o << "#{req_body.bytesize.to_s(16)}\r\n"
|
469
|
+
@o << req_body
|
470
|
+
@o << "\r\n0\r\n\r\n"
|
471
|
+
@o.close
|
472
|
+
end
|
473
|
+
def w.__write_method__; :backend_write; end
|
474
|
+
|
475
|
+
headers = @parser.parse_headers
|
476
|
+
@parser.splice_body_to(w)
|
477
|
+
w.close
|
478
|
+
assert_equal req_body, r.read
|
479
|
+
|
480
|
+
chunk_header_size = "#{req_body.bytesize.to_s(16)}\r\n".bytesize + "\r\n0\r\n\r\n".bytesize
|
481
|
+
assert_equal req_headers.bytesize + req_body.bytesize + chunk_header_size, headers[':rx']
|
482
|
+
end
|
483
|
+
|
484
|
+
def test_splice_body_to_content_length
|
485
|
+
req_body = SecureRandom.alphanumeric(60000)
|
486
|
+
req_headers = "POST / HTTP/1.1\r\nContent-Length: #{req_body.bytesize}\r\n\r\n"
|
487
|
+
r, w = IO.pipe
|
488
|
+
|
489
|
+
Thread.new do
|
490
|
+
@o << req_headers
|
491
|
+
@o << req_body
|
492
|
+
@o.close
|
493
|
+
end
|
494
|
+
def w.__write_method__; :backend_write; end
|
495
|
+
|
496
|
+
headers = @parser.parse_headers
|
497
|
+
@parser.splice_body_to(w)
|
498
|
+
w.close
|
499
|
+
assert_equal req_body, r.read
|
500
|
+
|
501
|
+
assert_equal req_headers.bytesize + req_body.bytesize, headers[':rx']
|
502
|
+
end
|
503
|
+
|
449
504
|
def test_complete?
|
450
505
|
@o << "GET / HTTP/1.1\r\n\r\n"
|
451
506
|
headers = @parser.parse_headers
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: h1p
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|