h1p 0.2 → 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.
@@ -4,14 +4,15 @@ require_relative 'helper'
4
4
  require 'h1p'
5
5
  require 'socket'
6
6
  require_relative '../ext/h1p/limits'
7
+ require 'securerandom'
7
8
 
8
- class H1PTest < MiniTest::Test
9
+ class H1PRequestTest < MiniTest::Test
9
10
  Error = H1P::Error
10
11
 
11
12
  def setup
12
13
  super
13
14
  @i, @o = IO.pipe
14
- @parser = H1P::Parser.new(@i)
15
+ @parser = H1P::Parser.new(@i, :server)
15
16
  end
16
17
  alias_method :reset_parser, :setup
17
18
 
@@ -75,7 +76,7 @@ class H1PTest < MiniTest::Test
75
76
  @o.close
76
77
 
77
78
  assert_raises(Error) { @parser.parse_headers }
78
-
79
+
79
80
  max_length = H1P_LIMITS[:max_method_length]
80
81
 
81
82
  reset_parser
@@ -96,7 +97,7 @@ class H1PTest < MiniTest::Test
96
97
  def test_bad_path
97
98
  @o << "GET HTTP/1.1\r\n\r\n"
98
99
  assert_raises(Error) { @parser.parse_headers }
99
-
100
+
100
101
  max_length = H1P_LIMITS[:max_path_length]
101
102
 
102
103
  reset_parser
@@ -149,26 +150,26 @@ class H1PTest < MiniTest::Test
149
150
  assert_raises(Error) { @parser.parse_headers }
150
151
 
151
152
  reset_parser
152
- @o << "GET / http/1.1.1\r\n\r\n"
153
+ @o << "GET / HTTP/1.1.1\r\n\r\n"
153
154
  assert_raises(Error) { @parser.parse_headers }
154
155
  end
155
156
 
156
157
  def test_headers_eof
157
158
  @o << "GET / HTTP/1.1\r\na"
158
159
  @o.close
159
-
160
+
160
161
  assert_nil @parser.parse_headers
161
162
 
162
163
  reset_parser
163
164
  @o << "GET / HTTP/1.1\r\na:"
164
165
  @o.close
165
-
166
+
166
167
  assert_nil @parser.parse_headers
167
168
 
168
169
  reset_parser
169
170
  @o << "GET / HTTP/1.1\r\na: "
170
171
  @o.close
171
-
172
+
172
173
  assert_nil @parser.parse_headers
173
174
  end
174
175
 
@@ -197,46 +198,46 @@ class H1PTest < MiniTest::Test
197
198
  end
198
199
 
199
200
  def test_bad_headers
200
- @o << "GET / http/1.1\r\n a: b\r\n\r\n"
201
+ @o << "GET / HTTP/1.1\r\n a: b\r\n\r\n"
201
202
  assert_raises(Error) { @parser.parse_headers }
202
203
 
203
204
  reset_parser
204
- @o << "GET / http/1.1\r\na b\r\n\r\n"
205
+ @o << "GET / HTTP/1.1\r\na b\r\n\r\n"
205
206
  assert_raises(Error) { @parser.parse_headers }
206
207
 
207
208
  max_key_length = H1P_LIMITS[:max_header_key_length]
208
209
 
209
210
  reset_parser
210
- @o << "GET / http/1.1\r\n#{'a' * max_key_length}: b\r\n\r\n"
211
+ @o << "GET / HTTP/1.1\r\n#{'a' * max_key_length}: b\r\n\r\n"
211
212
  headers = @parser.parse_headers
212
213
  assert_equal 'b', headers['a' * max_key_length]
213
214
 
214
215
  reset_parser
215
- @o << "GET / http/1.1\r\n#{'a' * (max_key_length + 1)}: b\r\n\r\n"
216
+ @o << "GET / HTTP/1.1\r\n#{'a' * (max_key_length + 1)}: b\r\n\r\n"
216
217
  assert_raises(Error) { @parser.parse_headers }
217
218
 
218
219
  max_value_length = H1P_LIMITS[:max_header_value_length]
219
220
 
220
221
  reset_parser
221
- @o << "GET / http/1.1\r\nfoo: #{'a' * max_value_length}\r\n\r\n"
222
+ @o << "GET / HTTP/1.1\r\nfoo: #{'a' * max_value_length}\r\n\r\n"
222
223
  headers = @parser.parse_headers
223
224
  assert_equal 'a' * max_value_length, headers['foo']
224
225
 
225
226
  reset_parser
226
- @o << "GET / http/1.1\r\nfoo: #{'a' * (max_value_length + 1)}\r\n\r\n"
227
+ @o << "GET / HTTP/1.1\r\nfoo: #{'a' * (max_value_length + 1)}\r\n\r\n"
227
228
  assert_raises(Error) { @parser.parse_headers }
228
229
 
229
230
  max_header_count = H1P_LIMITS[:max_header_count]
230
231
 
231
232
  reset_parser
232
233
  hdrs = (1..max_header_count).map { |i| "foo#{i}: bar\r\n" }.join
233
- @o << "GET / http/1.1\r\n#{hdrs}\r\n"
234
+ @o << "GET / HTTP/1.1\r\n#{hdrs}\r\n"
234
235
  headers = @parser.parse_headers
235
236
  assert_equal (max_header_count + 4), headers.size
236
237
 
237
238
  reset_parser
238
239
  hdrs = (1..(max_header_count + 1)).map { |i| "foo#{i}: bar\r\n" }.join
239
- @o << "GET / http/1.1\r\n#{hdrs}\r\n"
240
+ @o << "GET / HTTP/1.1\r\n#{hdrs}\r\n"
240
241
  assert_raises(Error) { @parser.parse_headers }
241
242
  end
242
243
 
@@ -256,7 +257,7 @@ class H1PTest < MiniTest::Test
256
257
  def test_read_body_with_content_length
257
258
  10.times do
258
259
  data = ' ' * rand(20..60000)
259
- msg = "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
260
+ msg = "GET / HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
260
261
  Thread.new { @o << msg }
261
262
 
262
263
  headers = @parser.parse_headers
@@ -270,7 +271,7 @@ class H1PTest < MiniTest::Test
270
271
 
271
272
  def test_read_body_chunk_with_content_length
272
273
  data = 'abc' * 20000
273
- msg = "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
274
+ msg = "GET / HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
274
275
  Thread.new { @o << msg }
275
276
  headers = @parser.parse_headers
276
277
  assert_equal data.bytesize.to_s, headers['content-length']
@@ -290,7 +291,7 @@ class H1PTest < MiniTest::Test
290
291
  def test_read_body_with_content_length_incomplete
291
292
  data = ' ' * rand(20..60000)
292
293
  Thread.new do
293
- @o << "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize + 1}\r\n\r\n#{data}"
294
+ @o << "GET / HTTP/1.1\r\nContent-Length: #{data.bytesize + 1}\r\n\r\n#{data}"
294
295
  @o.close # !!! otherwise the parser will keep waiting
295
296
  end
296
297
  headers = @parser.parse_headers
@@ -300,7 +301,7 @@ class H1PTest < MiniTest::Test
300
301
 
301
302
  def test_read_body_chunk_with_content_length_incomplete
302
303
  data = 'abc' * 50
303
- @o << "POST /foo HTTP/1.1\r\nContent-Length: #{data.bytesize + 1}\r\n\r\n#{data}"
304
+ @o << "GET / HTTP/1.1\r\nContent-Length: #{data.bytesize + 1}\r\n\r\n#{data}"
304
305
  @o.close
305
306
  headers = @parser.parse_headers
306
307
 
@@ -311,7 +312,7 @@ class H1PTest < MiniTest::Test
311
312
  chunks = []
312
313
  total_sent = 0
313
314
  Thread.new do
314
- msg = "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
315
+ msg = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
315
316
  @o << msg
316
317
  total_sent += msg.bytesize
317
318
  rand(8..16).times do |i|
@@ -324,7 +325,7 @@ class H1PTest < MiniTest::Test
324
325
  msg = "0\r\n\r\n"
325
326
  @o << msg
326
327
  total_sent += msg.bytesize
327
- end
328
+ end
328
329
  headers = @parser.parse_headers
329
330
  assert_equal 'chunked', headers['transfer-encoding']
330
331
 
@@ -337,7 +338,7 @@ class H1PTest < MiniTest::Test
337
338
  chunks = []
338
339
  total_sent = 0
339
340
  Thread.new do
340
- msg = "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
341
+ msg = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
341
342
  @o << msg
342
343
  total_sent += msg.bytesize
343
344
  rand(8..16).times do |i|
@@ -350,7 +351,7 @@ class H1PTest < MiniTest::Test
350
351
  msg = "0\r\n\r\n"
351
352
  @o << msg
352
353
  total_sent += msg.bytesize
353
- end
354
+ end
354
355
  headers = @parser.parse_headers
355
356
  assert_equal 'chunked', headers['transfer-encoding']
356
357
 
@@ -364,7 +365,7 @@ class H1PTest < MiniTest::Test
364
365
 
365
366
  def test_read_body_with_chunked_encoding_malformed
366
367
  Thread.new do
367
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
368
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
368
369
  chunk = ' '.to_s * rand(40000..360000)
369
370
  @o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n3"
370
371
  @o << "0\r\n\r\n"
@@ -376,22 +377,22 @@ class H1PTest < MiniTest::Test
376
377
  reset_parser
377
378
  # missing last empty chunk
378
379
  Thread.new do
379
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
380
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
380
381
  chunk = ' '.to_s * rand(40000..360000)
381
382
  @o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
382
383
  @o.close
383
- end
384
+ end
384
385
  headers = @parser.parse_headers
385
386
  assert_raises(H1P::Error) { @parser.read_body }
386
387
 
387
388
  reset_parser
388
389
  # bad chunk size
389
390
  Thread.new do
390
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
391
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
391
392
  chunk = ' '.to_s * rand(40000..360000)
392
393
  @o << "-#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
393
394
  @o.close
394
- end
395
+ end
395
396
  headers = @parser.parse_headers
396
397
  assert_raises(H1P::Error) { @parser.read_body }
397
398
  end
@@ -399,7 +400,7 @@ class H1PTest < MiniTest::Test
399
400
  def test_read_body_chunk_with_chunked_encoding_malformed
400
401
  chunk = nil
401
402
  Thread.new do
402
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
403
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
403
404
  chunk = ' ' * rand(40000..360000)
404
405
  @o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n3"
405
406
  @o << "0\r\n\r\n"
@@ -415,7 +416,7 @@ class H1PTest < MiniTest::Test
415
416
  # missing last empty chunk
416
417
  chunk = nil
417
418
  Thread.new do
418
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
419
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
419
420
  chunk = ' '.to_s * rand(20..1600)
420
421
  @o << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
421
422
  @o.close
@@ -429,23 +430,77 @@ class H1PTest < MiniTest::Test
429
430
 
430
431
  # bad chunk size
431
432
  Thread.new do
432
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
433
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
433
434
  chunk = ' '.to_s * rand(20..1600)
434
435
  @o << "-#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
435
436
  @o.close
436
- end
437
+ end
437
438
  headers = @parser.parse_headers
438
439
  assert_raises(H1P::Error) { @parser.read_body_chunk(false) }
439
440
 
440
441
  reset_parser
441
442
 
442
443
  # missing body
443
- @o << "POST /foo HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
444
+ @o << "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n"
444
445
  @o.close
445
446
  headers = @parser.parse_headers
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
@@ -519,7 +574,7 @@ class H1PTest < MiniTest::Test
519
574
  server_thread = Thread.new do
520
575
  while (socket = server.accept)
521
576
  Thread.new do
522
- parser = H1P::Parser.new(socket)
577
+ parser = H1P::Parser.new(socket, :server)
523
578
  headers = parser.parse_headers
524
579
  socket << headers.inspect
525
580
  socket.shutdown
@@ -556,7 +611,7 @@ class H1PTest < MiniTest::Test
556
611
  request
557
612
  end
558
613
 
559
- parser = H1P::Parser.new(callable)
614
+ parser = H1P::Parser.new(callable, :server)
560
615
 
561
616
  headers = parser.parse_headers
562
617
  assert_equal({
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.2'
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: 2021-08-20 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
@@ -70,6 +70,7 @@ files:
70
70
  - Rakefile
71
71
  - TODO.md
72
72
  - benchmarks/bm_http1_parser.rb
73
+ - benchmarks/pipelined.rb
73
74
  - examples/callable.rb
74
75
  - examples/http_server.rb
75
76
  - ext/h1p/extconf.rb
@@ -80,7 +81,9 @@ files:
80
81
  - lib/h1p.rb
81
82
  - lib/h1p/version.rb
82
83
  - test/helper.rb
83
- - test/test_h1p.rb
84
+ - test/run.rb
85
+ - test/test_h1p_client.rb
86
+ - test/test_h1p_server.rb
84
87
  homepage: http://github.com/digital-fabric/h1p
85
88
  licenses:
86
89
  - MIT
@@ -105,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
108
  - !ruby/object:Gem::Version
106
109
  version: '0'
107
110
  requirements: []
108
- rubygems_version: 3.1.4
111
+ rubygems_version: 3.1.6
109
112
  signing_key:
110
113
  specification_version: 4
111
114
  summary: H1P is a blocking HTTP/1 parser for Ruby