http_tools 0.3.0 → 0.4.0

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.
@@ -2,7 +2,7 @@ base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
2
  require base + '/http_tools'
3
3
  require 'test/unit'
4
4
 
5
- class ResponseTest < Test::Unit::TestCase
5
+ class BuilderResponseTest < Test::Unit::TestCase
6
6
 
7
7
  def test_status_ok
8
8
  result = HTTPTools::Builder.response(:ok)
@@ -29,4 +29,17 @@ class ResponseTest < Test::Unit::TestCase
29
29
  assert_equal(expected, result)
30
30
  end
31
31
 
32
+ def test_newline_separated_multi_value_headers
33
+ result = HTTPTools::Builder.response(:ok, "Set-Cookie" => "foo=bar\nbaz=qux")
34
+
35
+ expected = "HTTP/1.1 200 OK\r\nSet-Cookie: foo=bar\r\nSet-Cookie: baz=qux\r\n\r\n"
36
+ assert_equal(expected, result)
37
+ end
38
+
39
+ def test_array_multi_value_headers
40
+ result = HTTPTools::Builder.response(:ok, "Set-Cookie" => ["foo=bar", "baz=qux"])
41
+
42
+ expected = "HTTP/1.1 200 OK\r\nSet-Cookie: foo=bar\r\nSet-Cookie: baz=qux\r\n\r\n"
43
+ assert_equal(expected, result)
44
+ end
32
45
  end
@@ -1,4 +1,4 @@
1
- #!/opt/local/bin/ruby1.9
1
+ #!/opt/local/bin/ruby1.9 -w
2
2
 
3
3
  require 'coverage' # >= ruby 1.9 only
4
4
 
@@ -16,8 +16,8 @@ at_exit do
16
16
  next unless value.include?(0)
17
17
  puts key
18
18
  puts
19
- File.readlines(key).zip(value).each_with_index do |(line, value), i|
20
- print "%3i %3s %s" % [(i + 1), value, line]
19
+ File.readlines(key).zip(value).each_with_index do |(line, val), i|
20
+ print "%3i %3s %s" % [(i + 1), val, line]
21
21
  end
22
22
  puts
23
23
  puts
@@ -2,7 +2,7 @@ base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
2
  require base + '/http_tools'
3
3
  require 'test/unit'
4
4
 
5
- class RequestTest < Test::Unit::TestCase
5
+ class ParserRequestTest < Test::Unit::TestCase
6
6
 
7
7
  def test_get
8
8
  parser = HTTPTools::Parser.new
@@ -461,7 +461,7 @@ class RequestTest < Test::Unit::TestCase
461
461
  def test_env
462
462
  parser = HTTPTools::Parser.new
463
463
  env = nil
464
- parser.on(:header) {env = parser.env}
464
+ parser.on(:finish) {env = parser.env}
465
465
 
466
466
  parser << "GET /test?q=foo HTTP/1.1\r\n"
467
467
  parser << "Host: www.example.com\r\n"
@@ -479,7 +479,8 @@ class RequestTest < Test::Unit::TestCase
479
479
 
480
480
  assert_equal([1,1], env["rack.version"])
481
481
  assert_equal("http", env["rack.url_scheme"])
482
- assert_equal(nil, env["rack.input"])
482
+ assert_instance_of(StringIO, env["rack.input"])
483
+ assert_equal("", env["rack.input"].read)
483
484
  assert_equal(STDERR, env["rack.errors"])
484
485
  assert_equal(false, env["rack.multithread"])
485
486
  assert_equal(false, env["rack.multiprocess"])
@@ -514,7 +515,8 @@ class RequestTest < Test::Unit::TestCase
514
515
 
515
516
  assert_equal([1,1], env["rack.version"])
516
517
  assert_equal("http", env["rack.url_scheme"])
517
- assert_equal(nil, env["rack.input"])
518
+ assert_instance_of(StringIO, env["rack.input"])
519
+ assert_equal("Hello world", env["rack.input"].read)
518
520
  assert_equal(STDERR, env["rack.errors"])
519
521
  assert_equal(false, env["rack.multithread"])
520
522
  assert_equal(false, env["rack.multiprocess"])
@@ -524,7 +526,7 @@ class RequestTest < Test::Unit::TestCase
524
526
  def test_env_server_port
525
527
  parser = HTTPTools::Parser.new
526
528
  env = nil
527
- parser.on(:header) {env = parser.env}
529
+ parser.on(:finish) {env = parser.env}
528
530
 
529
531
  parser << "GET / HTTP/1.1\r\n"
530
532
  parser << "Host: localhost:9292\r\n"
@@ -538,7 +540,7 @@ class RequestTest < Test::Unit::TestCase
538
540
  def test_env_post
539
541
  parser = HTTPTools::Parser.new
540
542
  env = nil
541
- parser.on(:header) {env = parser.env}
543
+ parser.on(:finish) {env = parser.env}
542
544
 
543
545
  parser << "POST / HTTP/1.1\r\n"
544
546
  parser << "Host: www.example.com\r\n"
@@ -553,7 +555,31 @@ class RequestTest < Test::Unit::TestCase
553
555
  assert_equal("7", env["CONTENT_LENGTH"])
554
556
  assert_equal("application/x-www-form-urlencoded", env["CONTENT_TYPE"])
555
557
 
558
+ assert_instance_of(StringIO, env["rack.input"])
559
+ assert_equal("foo=bar", env["rack.input"].read)
560
+ end
561
+
562
+ def test_env_with_stream_listener
563
+ parser = HTTPTools::Parser.new
564
+ env = nil
565
+ body = ""
566
+ parser.on(:finish) {env = parser.env}
567
+ parser.on(:stream) {|chunk| body << chunk}
568
+
569
+ parser << "POST / HTTP/1.1\r\n"
570
+ parser << "Host: www.example.com\r\n"
571
+ parser << "Content-Length: 7\r\n"
572
+ parser << "\r\n"
573
+ parser << "foo=bar"
574
+
575
+ assert_equal("foo=bar", body)
556
576
  assert_equal(nil, env["rack.input"])
557
577
  end
558
578
 
579
+ def test_inspect
580
+ parser = HTTPTools::Parser.new
581
+
582
+ assert(/#<HTTPTools::Parser:[a-fx0-9]+ line 1, char 1 start>/ === parser.inspect, "Inspect should return useful information")
583
+ end
584
+
559
585
  end
@@ -2,7 +2,7 @@ base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
2
  require base + '/http_tools'
3
3
  require 'test/unit'
4
4
 
5
- class ResponseTest < Test::Unit::TestCase
5
+ class ParserResponseTest < Test::Unit::TestCase
6
6
 
7
7
  def test_version
8
8
  parser = HTTPTools::Parser.new
@@ -231,7 +231,7 @@ class ResponseTest < Test::Unit::TestCase
231
231
  parser << "Set-Cookie: foo=bar\r\n"
232
232
  parser << "Set-Cookie: baz=qux\r\n\r\n"
233
233
 
234
- assert_equal({"Set-Cookie" => ["foo=bar", "baz=qux"]}, headers)
234
+ assert_equal({"Set-Cookie" => "foo=bar\nbaz=qux"}, headers)
235
235
  end
236
236
 
237
237
  def test_skip_junk_headers_at_end
@@ -392,6 +392,22 @@ class ResponseTest < Test::Unit::TestCase
392
392
  assert(parser.finished?, "parser should be finished")
393
393
  end
394
394
 
395
+ def test_case_insensitive_content_length
396
+ parser = HTTPTools::Parser.new
397
+ headers, body = nil, ""
398
+
399
+ parser.add_listener(:header) {headers = parser.header}
400
+ parser.add_listener(:stream) {|chunk| body << chunk}
401
+
402
+ parser << "HTTP/1.1 200 OK\r\n"
403
+ parser << "content-length: 20\r\n\r\n"
404
+ parser << "<h1>Hello world</h1>"
405
+
406
+ assert_equal({"content-length" => "20"}, headers)
407
+ assert_equal("<h1>Hello world</h1>", body)
408
+ assert(parser.finished?, "parser should be finished")
409
+ end
410
+
395
411
  def test_zero_length_body
396
412
  parser = HTTPTools::Parser.new
397
413
  code, message, headers, body = nil
@@ -588,6 +604,24 @@ class ResponseTest < Test::Unit::TestCase
588
604
  assert(parser.finished?, "parser should be finished")
589
605
  end
590
606
 
607
+ def test_case_insensitive_chunked
608
+ parser = HTTPTools::Parser.new
609
+ headers, body = nil, ""
610
+
611
+ parser.add_listener(:header) do
612
+ headers = parser.header
613
+ end
614
+ parser.add_listener(:stream) {|chunk| body << chunk}
615
+
616
+ parser << "HTTP/1.1 200 OK\r\n"
617
+ parser << "transfer-encoding: CHUNKED\r\n\r\n"
618
+ parser << "14\r\n<h1>Hello world</h1>\r\n0\r\n"
619
+
620
+ assert_equal({"transfer-encoding" => "CHUNKED"}, headers)
621
+ assert_equal("<h1>Hello world</h1>", body)
622
+ assert(parser.finished?, "parser should be finished")
623
+ end
624
+
591
625
  def test_chunked_stream
592
626
  parser = HTTPTools::Parser.new
593
627
  code, message, headers = nil
@@ -630,13 +664,13 @@ class ResponseTest < Test::Unit::TestCase
630
664
  parser << "Transfer-Encoding: chunked\r\n"
631
665
  parser << "\r\n"
632
666
  parser << "9\r\n<h1>Hello\r\nb\r\n world</h1>\r\n"
633
- parser << "12\r\n<p>Lorem ipsum</p>\r\n"
634
- parser << "0\r\n"
667
+ parser << "12\r\n<p>Lorem ipsum</p>"
668
+ parser << "\r\n0\r\n"
635
669
 
636
670
  assert_equal(200, code)
637
671
  assert_equal("OK", message)
638
672
  assert_equal({"Transfer-Encoding" => "chunked"}, headers)
639
- assert_equal(["<h1>Hello world</h1>", "<p>Lorem ipsum</p>"], body)
673
+ assert_equal(["<h1>Hello", " world</h1>", "<p>Lorem ipsum</p>"], body)
640
674
  assert(parser.finished?, "parser should be finished")
641
675
  end
642
676
 
@@ -670,6 +704,26 @@ class ResponseTest < Test::Unit::TestCase
670
704
  assert(parser.finished?, "parser should be finished")
671
705
  end
672
706
 
707
+ def test_case_insensitive_chunked_terminated_by_close
708
+ parser = HTTPTools::Parser.new
709
+ headers, body = nil, ""
710
+
711
+ parser.add_listener(:header) {headers = parser.header}
712
+ parser.add_listener(:stream) {|chunk| body << chunk}
713
+
714
+ parser << "HTTP/1.1 200 OK\r\n"
715
+ parser << "connection: CLOSE\r\n"
716
+ parser << "Transfer-Encoding: chunked\r\n\r\n"
717
+ parser << "9\r\n<h1>Hello\r\nb\r\n world</h1>\r\n"
718
+ parser.finish # notify parser the connection has closed
719
+
720
+ assert_equal({
721
+ "Transfer-Encoding" => "chunked",
722
+ "connection" => "CLOSE"}, headers)
723
+ assert_equal("<h1>Hello world</h1>", body)
724
+ assert(parser.finished?, "parser should be finished")
725
+ end
726
+
673
727
  def test_html_body_only_not_allowed
674
728
  parser = HTTPTools::Parser.new
675
729
 
@@ -704,6 +758,37 @@ class ResponseTest < Test::Unit::TestCase
704
758
  assert(parser.finished?, "parser should be finished")
705
759
  end
706
760
 
761
+ def test_default_stream_listener
762
+ parser = HTTPTools::Parser.new
763
+ body = nil
764
+
765
+ parser.add_listener(:finish) do
766
+ body = parser.body
767
+ end
768
+
769
+ parser << "HTTP/1.1 200 OK\r\n"
770
+ parser << "Content-Length: 20\r\n\r\n"
771
+ parser << "<h1>Hello"
772
+ parser << " world</h1>"
773
+
774
+ assert_equal("<h1>Hello world</h1>", body)
775
+ end
776
+
777
+ def test_overide_default_stream_listener
778
+ parser = HTTPTools::Parser.new
779
+ body = ""
780
+
781
+ parser.add_listener(:stream) {|chunk| body << chunk}
782
+
783
+ parser << "HTTP/1.1 200 OK\r\n"
784
+ parser << "Content-Length: 20\r\n\r\n"
785
+ parser << "<h1>Hello"
786
+ parser << " world</h1>"
787
+
788
+ assert_equal("<h1>Hello world</h1>", body)
789
+ assert_nil(parser.body)
790
+ end
791
+
707
792
  def test_finished
708
793
  parser = HTTPTools::Parser.new
709
794
  code, message, remainder = nil
@@ -788,6 +873,23 @@ class ResponseTest < Test::Unit::TestCase
788
873
  assert(parser.finished?, "parser should be finished")
789
874
  end
790
875
 
876
+ def test_case_insensitive_trailer
877
+ parser = HTTPTools::Parser.new
878
+ headers, trailer = nil
879
+
880
+ parser.add_listener(:header) {headers = parser.header}
881
+ parser.add_listener(:trailer) {trailer = parser.trailer}
882
+
883
+ parser << "HTTP/1.1 200 OK\r\n"
884
+ parser << "Transfer-Encoding: chunked\r\ntrailer: x-checksum\r\n\r\n"
885
+ parser << "14\r\n<h1>Hello world</h1>\r\n0\r\n"
886
+ parser << "X-Checksum: 2a2e12c8edad17de62354ea4531ac82c\r\n\r\n"
887
+
888
+ assert_equal({"Transfer-Encoding" => "chunked", "trailer" => "x-checksum"}, headers)
889
+ assert_equal({"X-Checksum" => "2a2e12c8edad17de62354ea4531ac82c"}, trailer)
890
+ assert(parser.finished?, "parser should be finished")
891
+ end
892
+
791
893
  def test_trailer_sub_line_chunks
792
894
  parser = HTTPTools::Parser.new
793
895
  trailer = nil
@@ -821,6 +923,22 @@ class ResponseTest < Test::Unit::TestCase
821
923
  assert(parser.finished?, "parser should be finished")
822
924
  end
823
925
 
926
+ def test_multiple_trailer_values
927
+ parser = HTTPTools::Parser.new
928
+ trailer = nil
929
+
930
+ parser.add_listener(:trailer) {trailer = parser.trailer}
931
+
932
+ parser << "HTTP/1.1 200 OK\r\n"
933
+ parser << "Transfer-Encoding: chunked\r\n"
934
+ parser << "Trailer: X-Test\r\n\r\n"
935
+ parser << "14\r\n<h1>Hello world</h1>\r\n0\r\n"
936
+ parser << "X-Test: 1\r\n"
937
+ parser << "X-Test: 2\r\n\r\n"
938
+
939
+ assert_equal({"X-Test" => "1\n2"}, trailer)
940
+ end
941
+
824
942
  def test_messed_up_iis_header_style_trailer_1
825
943
  parser = HTTPTools::Parser.new
826
944
  trailer = nil
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_tools
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthew Sadler
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-10 00:00:00 +01:00
18
+ date: 2011-06-12 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -30,7 +30,6 @@ extra_rdoc_files:
30
30
  files:
31
31
  - lib/http_tools/builder.rb
32
32
  - lib/http_tools/encoding.rb
33
- - lib/http_tools/errors.rb
34
33
  - lib/http_tools/parser.rb
35
34
  - lib/http_tools.rb
36
35
  - test/builder/request_test.rb
@@ -42,14 +41,15 @@ files:
42
41
  - test/parser/request_test.rb
43
42
  - test/parser/response_test.rb
44
43
  - test/runner.rb
44
+ - bench/parser/large_response_bench.rb
45
45
  - bench/parser/request_bench.rb
46
46
  - bench/parser/response_bench.rb
47
47
  - bench/transfer_encoding_chunked_bench.rb
48
+ - profile/parser/large_response_profile.rb
48
49
  - profile/parser/request_profile.rb
49
50
  - profile/parser/response_profile.rb
50
51
  - example/http_client.rb
51
52
  - example/http_server.rb
52
- - example/simple_http_client.rb
53
53
  - README.rdoc
54
54
  has_rdoc: true
55
55
  homepage: http://github.com/matsadler/http_tools
@@ -1,67 +0,0 @@
1
- require 'socket'
2
- require 'rubygems'
3
- require 'http_tools'
4
-
5
- # Usage:
6
- # uri = URI.parse("http://example.com/")
7
- # client = HTTP::Client.new(uri.host, uri.port)
8
- # response = client.get(uri.path)
9
- #
10
- # puts "#{response.status} #{response.message}"
11
- # puts response.headers.inspect
12
- # puts response.body
13
- #
14
- module HTTP
15
- class Client
16
- Response = Struct.new(:status, :message, :headers, :body)
17
-
18
- def initialize(host, port=80)
19
- @host, @port = host, port
20
- end
21
-
22
- def head(path, headers={})
23
- request(:head, path, nil, headers, false)
24
- end
25
-
26
- def get(path, headers={})
27
- request(:get, path, nil, headers)
28
- end
29
-
30
- def post(path, body=nil, headers={})
31
- request(:post, path, body, headers)
32
- end
33
-
34
- def put(path, body=nil, headers={})
35
- request(:put, path, body, headers)
36
- end
37
-
38
- def delete(path, headers={})
39
- request(:delete, path, nil, headers)
40
- end
41
-
42
- private
43
- def request(method, path, body=nil, headers={}, response_has_body=true)
44
- parser = HTTPTools::Parser.new
45
- parser.force_no_body = !response_has_body
46
- response = nil
47
-
48
- parser.on(:header) do
49
- code, message, head = parser.status_code, parser.message, parser.header
50
- response = Response.new(code, message, head, "")
51
- end
52
- parser.on(:stream) {|chunk| response.body << chunk}
53
-
54
- socket = TCPSocket.new(@host, @port)
55
- socket << HTTPTools::Builder.request(method, @host, path, headers)
56
- socket << body if body
57
- begin
58
- parser << socket.sysread(1024 * 16)
59
- rescue EOFError
60
- break parser.finish
61
- end until parser.finished?
62
- socket.close
63
-
64
- response
65
- end
66
- end
67
- end
@@ -1,6 +0,0 @@
1
- module HTTPTools
2
- class ParseError < StandardError; end
3
- class EndOfMessageError < ParseError; end
4
- class MessageIncompleteError < EndOfMessageError; end
5
- class EmptyMessageError < MessageIncompleteError; end
6
- end