h1p 0.4 → 0.5
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 +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
|