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.
- data/README.rdoc +3 -3
- data/bench/parser/large_response_bench.rb +40 -0
- data/bench/transfer_encoding_chunked_bench.rb +4 -4
- data/example/http_client.rb +24 -139
- data/example/http_server.rb +1 -14
- data/lib/http_tools.rb +7 -5
- data/lib/http_tools/builder.rb +23 -11
- data/lib/http_tools/encoding.rb +4 -2
- data/lib/http_tools/parser.rb +136 -57
- data/profile/parser/large_response_profile.rb +16 -0
- data/test/builder/request_test.rb +21 -3
- data/test/builder/response_test.rb +14 -1
- data/test/cover.rb +3 -3
- data/test/parser/request_test.rb +32 -6
- data/test/parser/response_test.rb +123 -5
- metadata +6 -6
- data/example/simple_http_client.rb +0 -67
- data/lib/http_tools/errors.rb +0 -6
@@ -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
|
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
|
data/test/cover.rb
CHANGED
@@ -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,
|
20
|
-
print "%3i %3s %s" % [(i + 1),
|
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
|
data/test/parser/request_test.rb
CHANGED
@@ -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
|
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(:
|
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
|
-
|
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
|
-
|
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(:
|
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(:
|
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
|
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" =>
|
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
|
634
|
-
parser << "
|
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:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 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-
|
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
|