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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a27a3323482c8a0cec845e60962516b755338ccc54d0ba76bf6716bbb449a8ff
4
- data.tar.gz: 22613ade68e261e3fca56cafcab410b1bed4587efe8c1da4ef52d1d335bc7efd
3
+ metadata.gz: 53aadd6f6c4ae112ff844b68e8325fe334d51dd0a09bb11cc54d017190e97677
4
+ data.tar.gz: e9f6d504813d74c050a46b4e973e9fbc227dd790c02395a0073b62f43a7393da
5
5
  SHA512:
6
- metadata.gz: 36cb9020745437627ad3dca5f83de6f81f8ad6e04075909db192a509b9f022960c5a7768c1f1ab8fdb807ae0d7b54e6febbc76581e68475c5f27057ff5b7aeb5
7
- data.tar.gz: 20943cfead5e0236e60cb3a0d70057c69a91bbde45973f0f4ff87f38a1866523c1a02132254eebc13b8c5e2c4f75cd8e5791246257a792f63391cff88a6e1011
6
+ metadata.gz: 3ac98c2d7e702f8cf5f9052e78e9abe486ee3ba0814c4c16fe222acdccfb5338ac9d5c9b4a5d1da2178fca65472693c94a4997cc86de05db70f13c90f1bc767e
7
+ data.tar.gz: 73ce78e3435eb40f3e6d46ee8eef803284e8da99f32d610dbd723c3661548a74ed454ae70e1ec02dcfbdb70aa346059fb7713eab19de3e6e6e65a4ca859926c7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.5 2022-03-19
2
+
3
+ - Implement `Parser#splice_body_to` (#3)
4
+
1
5
  ## 0.4 2022-02-28
2
6
 
3
7
  - Rename `__parser_read_method__` to `__read_method__`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- h1p (0.4)
4
+ h1p (0.5)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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
- method_readpartial, // receiver.readpartial(len, buf, pos, raise_on_eof: false) (Polyphony-specific)
56
- method_backend_read, // Polyphony.backend_read (Polyphony-specific)
57
- method_backend_recv, // Polyphony.backend_recv (Polyphony-specific)
58
- method_call, // receiver.call(len) (Universal)
59
- method_stock_readpartial // receiver.readpartial(len)
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 void get_polyphony() {
119
- if (mPolyphony != Qnil) return;
120
-
121
- mPolyphony = rb_const_get(rb_cObject, rb_intern("Polyphony"));
122
- rb_gc_register_mark_object(mPolyphony);
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 method_stock_readpartial;
129
-
130
- get_polyphony();
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 method_readpartial;
144
+ return RM_READPARTIAL;
135
145
  }
136
146
  else if (rb_respond_to(io, ID_call)) {
137
- return method_call;
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, INT2NUM(value)); \
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 method_backend_read:
298
- return rb_funcall(mPolyphony, ID_backend_read, 5, parser->io, buf, maxlen, Qfalse, buf_pos);
299
- case method_backend_recv:
300
- return rb_funcall(mPolyphony, ID_backend_recv, 4, parser->io, buf, maxlen, buf_pos);
301
- case method_readpartial:
302
- return rb_funcall(parser-> io, ID_readpartial, 4, maxlen, buf, buf_pos, Qfalse);
303
- case method_call:
304
- return io_call(parser ->io, maxlen, buf, buf_pos);
305
- case method_stock_readpartial:
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, INT2NUM(read_bytes));
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, INT2NUM(maxlen), Qnil, NUM_buffer_start);
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, INT2NUM(parser->current_request_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, INT2NUM(maxlen), Qnil, NUM_buffer_start);
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, INT2NUM(parser->current_request_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
- return read_body_with_content_length(parser, read_entire_body, buffered_only);
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 = rb_intern("__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 = INT2NUM(MAX_HEADERS_READ_LENGTH);
988
- NUM_buffer_start = INT2NUM(0);
989
- NUM_buffer_end = INT2NUM(-1);
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 = ID2SYM(ID_backend_read);
1003
- SYM_backend_recv = ID2SYM(ID_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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module H1P
4
- VERSION = '0.4'
4
+ VERSION = '0.5'
5
5
  end
data/lib/h1p.rb CHANGED
@@ -2,28 +2,34 @@
2
2
 
3
3
  require_relative './h1p_ext'
4
4
 
5
- unless Object.const_defined?('Polyphony')
6
- class IO
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
- require 'socket'
13
+ require 'socket'
13
14
 
14
- class Socket
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
- class TCPSocket
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
- class UNIXSocket
31
+ class UNIXSocket
32
+ if !method_defined?(:__read_method__)
27
33
  def __read_method__
28
34
  :stock_readpartial
29
35
  end
@@ -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'
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-02-28 00:00:00.000000000 Z
11
+ date: 2022-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler