h1p 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
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