h1p 0.2 → 0.5

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