http_tools 0.1.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 +80 -0
- data/bench/parser/request_bench.rb +59 -0
- data/bench/parser/response_bench.rb +21 -0
- data/example/http_client.rb +132 -0
- data/lib/http_tools.rb +110 -0
- data/lib/http_tools/builder.rb +49 -0
- data/lib/http_tools/encoding.rb +169 -0
- data/lib/http_tools/errors.rb +5 -0
- data/lib/http_tools/parser.rb +478 -0
- data/profile/parser/request_profile.rb +12 -0
- data/profile/parser/response_profile.rb +12 -0
- data/test/builder/request_test.rb +26 -0
- data/test/builder/response_test.rb +32 -0
- data/test/cover.rb +28 -0
- data/test/encoding/transfer_encoding_chunked_test.rb +141 -0
- data/test/encoding/url_encoding_test.rb +37 -0
- data/test/encoding/www_form_test.rb +42 -0
- data/test/parser/request_test.rb +481 -0
- data/test/parser/response_test.rb +446 -0
- data/test/runner.rb +1 -0
- metadata +89 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ruby-prof'
|
5
|
+
|
6
|
+
request = "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-gb) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16\r\nAccept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: en-gb\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n"
|
7
|
+
parser = HTTPTools::Parser.new
|
8
|
+
|
9
|
+
result = RubyProf.profile do
|
10
|
+
parser << request
|
11
|
+
end
|
12
|
+
RubyProf::FlatPrinter.new(result).print(STDOUT, 0)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ruby-prof'
|
5
|
+
|
6
|
+
response = "HTTP/1.1 200 OK\r\nServer: Apache/2.2.3 (CentOS)\r\nLast-Modified: Thu, 03 Jun 2010 17:40:12 GMT\r\nETag: \"4d2c-23e-48823b2cf3700\"\r\nAccept-Ranges: bytes\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: Keep-Alive\r\nDate: Wed, 21 Jul 2010 16:26:04 GMT\r\nAge: 7985 \r\nContent-Length: 574\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n<HTML>\r\n<HEAD>\r\n <META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n <TITLE>Example Web Page</TITLE>\r\n</HEAD> \r\n<body> \r\n<p>You have reached this web page by typing "example.com",\r\n"example.net",\r\n or "example.org" into your web browser.</p>\r\n<p>These domain names are reserved for use in documentation and are not available \r\n for registration. See <a href=\"http://www.rfc-editor.org/rfc/rfc2606.txt\">RFC \r\n 2606</a>, Section 3.</p>\r\n</BODY>\r\n</HTML>\r\n\r\n"
|
7
|
+
parser = HTTPTools::Parser.new
|
8
|
+
|
9
|
+
result = RubyProf.profile do
|
10
|
+
parser << response
|
11
|
+
end
|
12
|
+
RubyProf::FlatPrinter.new(result).print(STDOUT, 0)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
class RequestTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_get
|
9
|
+
result = HTTPTools::Builder.request(:get, "www.example.com", "/test")
|
10
|
+
|
11
|
+
assert_equal("GET /test HTTP/1.1\r\nHost: www.example.com\r\n\r\n", result)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_post
|
15
|
+
result = HTTPTools::Builder.request(:post, "www.test.com", "/example")
|
16
|
+
|
17
|
+
assert_equal("POST /example HTTP/1.1\r\nHost: www.test.com\r\n\r\n", result)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_headers
|
21
|
+
result = HTTPTools::Builder.request(:get, "www.foobar.com", "/", "x-test" => "foo")
|
22
|
+
|
23
|
+
assert_equal("GET / HTTP/1.1\r\nHost: www.foobar.com\r\nx-test: foo\r\n\r\n", result)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class ResponseTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_status_ok
|
8
|
+
result = HTTPTools::Builder.response(:ok)
|
9
|
+
|
10
|
+
assert_equal("HTTP/1.1 200 OK\r\n\r\n", result)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_status_not_found
|
14
|
+
result = HTTPTools::Builder.response(:not_found)
|
15
|
+
|
16
|
+
assert_equal("HTTP/1.1 404 Not Found\r\n\r\n", result)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_status_with_code
|
20
|
+
result = HTTPTools::Builder.response(500)
|
21
|
+
|
22
|
+
assert_equal("HTTP/1.1 500 Internal Server Error\r\n\r\n", result)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_headers
|
26
|
+
result = HTTPTools::Builder.response(:ok, "Content-Type" => "text/html", "Content-Length" => 1024)
|
27
|
+
|
28
|
+
expected = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 1024\r\n\r\n"
|
29
|
+
assert_equal(expected, result)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/test/cover.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/opt/local/bin/ruby1.9
|
2
|
+
|
3
|
+
require 'coverage' # >= ruby 1.9 only
|
4
|
+
|
5
|
+
at_exit do
|
6
|
+
testing = Dir["../lib/**/*.rb"].map(&File.method(:expand_path))
|
7
|
+
|
8
|
+
results = Coverage.result.select {|key, value| testing.include?(key)}
|
9
|
+
|
10
|
+
puts
|
11
|
+
total = results.map(&:last).flatten.compact
|
12
|
+
puts "#{total.select {|i| i > 0}.length}/#{total.length} executable lines covered"
|
13
|
+
puts
|
14
|
+
|
15
|
+
results.each do |key, value|
|
16
|
+
next unless value.include?(0)
|
17
|
+
puts key
|
18
|
+
puts
|
19
|
+
File.readlines(key).zip(value).each_with_index do |(line, value), i|
|
20
|
+
print "%3i %3s %s" % [(i + 1), value, line]
|
21
|
+
end
|
22
|
+
puts
|
23
|
+
puts
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Coverage.start
|
28
|
+
Dir["**/*_test.rb"].each {|test| require test}
|
@@ -0,0 +1,141 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class TransferEncodingChunkedTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_decode_1_chunk
|
8
|
+
encoded = "14\r\n<h1>Hello world</h1>\r\n0\r\n"
|
9
|
+
decoded = HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
10
|
+
|
11
|
+
assert_equal(["<h1>Hello world</h1>", nil], decoded)
|
12
|
+
assert_equal("14\r\n<h1>Hello world</h1>\r\n0\r\n", encoded)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_decode_2_chunks
|
16
|
+
encoded = "14\r\n<h1>Hello world</h1>\r\n12\r\n<p>Lorem ipsum</p>\r\n0\r\n"
|
17
|
+
decoded = HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
18
|
+
|
19
|
+
assert_equal(["<h1>Hello world</h1><p>Lorem ipsum</p>", nil], decoded)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_decode_incomplete
|
23
|
+
encoded = "14\r\n<h1>Hello world</h1>\r\n12\r\n<p>Lorem ipsum"
|
24
|
+
decoded = HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
25
|
+
|
26
|
+
assert_equal(["<h1>Hello world</h1>", "12\r\n<p>Lorem ipsum"], decoded)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_decode_line_by_line
|
30
|
+
part1 = "14\r\n"
|
31
|
+
part2 = "<h1>Hello world</h1>\r\n"
|
32
|
+
part3 = "12\r\n"
|
33
|
+
part4 = "<p>Lorem ipsum</p>\r\n"
|
34
|
+
part5 = "0\r\n"
|
35
|
+
|
36
|
+
result = HTTPTools::Encoding.transfer_encoding_chunked_decode(part1)
|
37
|
+
assert_equal([nil, "14\r\n"], result)
|
38
|
+
decoded, remainder = result
|
39
|
+
decoded ||= ""
|
40
|
+
|
41
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part2)
|
42
|
+
assert_equal(["<h1>Hello world</h1>", ""], res)
|
43
|
+
part, remainder = res
|
44
|
+
decoded += part if part
|
45
|
+
|
46
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part3)
|
47
|
+
assert_equal([nil, "12\r\n"], res)
|
48
|
+
part, remainder = res
|
49
|
+
decoded += part if part
|
50
|
+
|
51
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part4)
|
52
|
+
assert_equal(["<p>Lorem ipsum</p>", ""], res)
|
53
|
+
part, remainder = res
|
54
|
+
decoded += part if part
|
55
|
+
|
56
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part5)
|
57
|
+
assert_equal([nil, nil], res)
|
58
|
+
part, remainder = res
|
59
|
+
decoded += part if part
|
60
|
+
|
61
|
+
assert_equal("<h1>Hello world</h1><p>Lorem ipsum</p>", decoded)
|
62
|
+
assert_nil(remainder)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_decode_broken_between_lines
|
66
|
+
part1 = "14\r\n<h1>Hello"
|
67
|
+
part2 = " world</h1>\r\nf"
|
68
|
+
part3 = "\r\n<p>Lorem ipsum "
|
69
|
+
part4 = "\r\n12\r\n"
|
70
|
+
part5 = "dolor sit amet</p"
|
71
|
+
part6 = ">\r\n0\r\n"
|
72
|
+
|
73
|
+
result = HTTPTools::Encoding.transfer_encoding_chunked_decode(part1)
|
74
|
+
assert_equal([nil, "14\r\n<h1>Hello"], result)
|
75
|
+
decoded, remainder = result
|
76
|
+
decoded ||= ""
|
77
|
+
|
78
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part2)
|
79
|
+
assert_equal(["<h1>Hello world</h1>", "f"], res)
|
80
|
+
part, remainder = res
|
81
|
+
decoded += part if part
|
82
|
+
|
83
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part3)
|
84
|
+
assert_equal([nil, "f\r\n<p>Lorem ipsum "], res)
|
85
|
+
part, remainder = res
|
86
|
+
decoded += part if part
|
87
|
+
|
88
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part4)
|
89
|
+
assert_equal(["<p>Lorem ipsum ", "12\r\n"], res)
|
90
|
+
part, remainder = res
|
91
|
+
decoded += part if part
|
92
|
+
|
93
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part5)
|
94
|
+
assert_equal([nil, "12\r\ndolor sit amet</p"], res)
|
95
|
+
part, remainder = res
|
96
|
+
decoded += part if part
|
97
|
+
|
98
|
+
res = HTTPTools::Encoding.transfer_encoding_chunked_decode(remainder+part6)
|
99
|
+
assert_equal(["dolor sit amet</p>", nil], res)
|
100
|
+
part, remainder = res
|
101
|
+
decoded += part if part
|
102
|
+
|
103
|
+
expected = "<h1>Hello world</h1><p>Lorem ipsum dolor sit amet</p>"
|
104
|
+
assert_equal(expected, decoded)
|
105
|
+
assert_nil(remainder)
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_decode_with_empty_line
|
109
|
+
encode="16\r\n<h1>Hello world</h1>\r\n\r\n12\r\n<p>Lorem ipsum</p>\r\n0\r\n"
|
110
|
+
decoded = HTTPTools::Encoding.transfer_encoding_chunked_decode(encode)
|
111
|
+
|
112
|
+
assert_equal(["<h1>Hello world</h1>\r\n<p>Lorem ipsum</p>", nil], decoded)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_decode_doesnt_mangle_input
|
116
|
+
encoded = "14\r\n<h1>Hello world</h1>\r\n0\r\n"
|
117
|
+
HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
118
|
+
|
119
|
+
assert_equal("14\r\n<h1>Hello world</h1>\r\n0\r\n", encoded)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_encode
|
123
|
+
encoded = HTTPTools::Encoding.transfer_encoding_chunked_encode("foo")
|
124
|
+
|
125
|
+
assert_equal("3\r\nfoo\r\n", encoded)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_encode_empty_string
|
129
|
+
encoded = HTTPTools::Encoding.transfer_encoding_chunked_encode("")
|
130
|
+
|
131
|
+
assert_equal("0\r\n", encoded)
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def test_encode_nil
|
136
|
+
encoded = HTTPTools::Encoding.transfer_encoding_chunked_encode(nil)
|
137
|
+
|
138
|
+
assert_equal("0\r\n", encoded)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class URLEncodingTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_encode
|
8
|
+
result = HTTPTools::Encoding.url_encode("[A] (test/example)=<string>?")
|
9
|
+
|
10
|
+
assert_equal("%5bA%5d%20%28test%2fexample%29%3d%3cstring%3e%3f", result)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_decode
|
14
|
+
result = HTTPTools::Encoding.url_decode("%5bA%5d%20%28test%2fexample%29%3d%3cstring%3e%3f")
|
15
|
+
|
16
|
+
assert_equal("[A] (test/example)=<string>?", result)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_encode_allowed_characters
|
20
|
+
result = HTTPTools::Encoding.url_encode("A_test-string~1.")
|
21
|
+
|
22
|
+
assert_equal("A_test-string~1.", result)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_encode_latin_capital_letter_a_with_grave
|
26
|
+
result = HTTPTools::Encoding.url_encode("À")
|
27
|
+
|
28
|
+
assert_equal("%c3%80", result)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_decode_latin_capital_letter_a_with_grave
|
32
|
+
result = HTTPTools::Encoding.url_decode("%C3%80")
|
33
|
+
|
34
|
+
assert_equal("À", result)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class WWWFormTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_encode
|
8
|
+
result = HTTPTools::Encoding.www_form_encode("foo" => "bar", "baz" => "qux")
|
9
|
+
|
10
|
+
# may fail under Ruby < 1.9 because of the unpredictable ordering of Hash
|
11
|
+
assert_equal("foo=bar&baz=qux", result)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_encode_with_array
|
15
|
+
result = HTTPTools::Encoding.www_form_encode("lang" => ["en", "fr"], "q" => ["foo", "bar"])
|
16
|
+
|
17
|
+
# may fail under Ruby < 1.9 because of the unpredictable ordering of Hash
|
18
|
+
assert_equal("lang=en&lang=fr&q=foo&q=bar", result)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_decode
|
22
|
+
result = HTTPTools::Encoding.www_form_decode("foo=bar&baz=qux")
|
23
|
+
|
24
|
+
assert_equal({"foo" => "bar", "baz" => "qux"}, result)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_decode_with_array
|
28
|
+
result = HTTPTools::Encoding.www_form_decode("lang=en&lang=fr&q=foo&q=bar")
|
29
|
+
|
30
|
+
assert_equal({"lang" => ["en", "fr"], "q" => ["foo", "bar"]}, result)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_encode_decode
|
34
|
+
orginal = {"query" => "fish", "lang" => ["en", "fr"]}
|
35
|
+
|
36
|
+
encoded = HTTPTools::Encoding.www_form_encode(orginal)
|
37
|
+
decoded = HTTPTools::Encoding.www_form_decode(encoded)
|
38
|
+
|
39
|
+
assert_equal(orginal, decoded)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,481 @@
|
|
1
|
+
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require base + '/http_tools'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class RequestTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_get
|
8
|
+
parser = HTTPTools::Parser.new
|
9
|
+
result = nil
|
10
|
+
|
11
|
+
parser.add_listener(:method) do |method|
|
12
|
+
result = method
|
13
|
+
end
|
14
|
+
|
15
|
+
parser << "GET / HTTP/1.1\r\n"
|
16
|
+
|
17
|
+
assert_equal("GET", result)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_post
|
21
|
+
parser = HTTPTools::Parser.new
|
22
|
+
result = nil
|
23
|
+
|
24
|
+
parser.add_listener(:method) do |method|
|
25
|
+
result = method
|
26
|
+
end
|
27
|
+
|
28
|
+
parser << "POST / HTTP/1.1\r\n"
|
29
|
+
|
30
|
+
assert_equal("POST", result)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_empty_path
|
34
|
+
parser = HTTPTools::Parser.new
|
35
|
+
result = nil
|
36
|
+
|
37
|
+
parser.add_listener(:path) do |path, query|
|
38
|
+
result = path
|
39
|
+
end
|
40
|
+
|
41
|
+
parser << "GET / HTTP/1.1\r\n"
|
42
|
+
|
43
|
+
assert_equal("/", result)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_basic_path
|
47
|
+
parser = HTTPTools::Parser.new
|
48
|
+
result = nil
|
49
|
+
|
50
|
+
parser.add_listener(:path) do |path, query|
|
51
|
+
result = path
|
52
|
+
end
|
53
|
+
|
54
|
+
parser << "GET /foo HTTP/1.1\r\n"
|
55
|
+
|
56
|
+
assert_equal("/foo", result)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_complicated_path
|
60
|
+
parser = HTTPTools::Parser.new
|
61
|
+
path = nil
|
62
|
+
query = nil
|
63
|
+
|
64
|
+
parser.add_listener(:path) do |p, q|
|
65
|
+
path = p
|
66
|
+
query = q
|
67
|
+
end
|
68
|
+
|
69
|
+
parser << "GET /foo%20bar/baz.html?key=value#qux HTTP/1.1\r\n"
|
70
|
+
|
71
|
+
assert_equal("/foo%20bar/baz.html", path)
|
72
|
+
assert_equal("key=value", query)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_invalid_path
|
76
|
+
parser = HTTPTools::Parser.new
|
77
|
+
|
78
|
+
assert_raise(HTTPTools::ParseError) do
|
79
|
+
parser << "GET \\ HTTP/1.1\r\n"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_uri
|
84
|
+
parser = HTTPTools::Parser.new
|
85
|
+
result = nil
|
86
|
+
|
87
|
+
parser.add_listener(:uri) do |uri|
|
88
|
+
result = uri
|
89
|
+
end
|
90
|
+
|
91
|
+
parser << "GET http://example.com/foo HTTP/1.1\r\n"
|
92
|
+
|
93
|
+
assert_equal("http://example.com/foo", result)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_path_callback_not_called_with_uri
|
97
|
+
parser = HTTPTools::Parser.new
|
98
|
+
result = nil
|
99
|
+
|
100
|
+
parser.add_listener(:path) do |path, query|
|
101
|
+
result = path
|
102
|
+
end
|
103
|
+
|
104
|
+
parser << "GET http://example.com/foo HTTP/1.1\r\n"
|
105
|
+
|
106
|
+
assert_nil(result)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_uri_callback_called_with_path
|
110
|
+
parser = HTTPTools::Parser.new
|
111
|
+
uri = nil
|
112
|
+
path = nil
|
113
|
+
query = nil
|
114
|
+
|
115
|
+
parser.add_listener(:uri) {|u| uri = u}
|
116
|
+
parser.add_listener(:path) do |p, q|
|
117
|
+
path = p
|
118
|
+
query = q
|
119
|
+
end
|
120
|
+
|
121
|
+
parser << "GET /foo/bar.html?key=value HTTP/1.1\r\n"
|
122
|
+
|
123
|
+
assert_equal("/foo/bar.html?key=value", uri)
|
124
|
+
assert_equal("/foo/bar.html", path)
|
125
|
+
assert_equal("key=value", query)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_fragment_with_path
|
129
|
+
parser = HTTPTools::Parser.new
|
130
|
+
path = nil
|
131
|
+
fragment = nil
|
132
|
+
|
133
|
+
parser.add_listener(:path) {|p, q| path = p}
|
134
|
+
parser.add_listener(:fragment) {|f| fragment = f}
|
135
|
+
|
136
|
+
parser << "GET /foo#bar HTTP/1.1\r\n"
|
137
|
+
|
138
|
+
assert_equal("/foo", path)
|
139
|
+
assert_equal("bar", fragment)
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_fragment_with_uri
|
143
|
+
parser = HTTPTools::Parser.new
|
144
|
+
uri = nil
|
145
|
+
fragment = nil
|
146
|
+
|
147
|
+
parser.add_listener(:uri) {|u| uri = u}
|
148
|
+
parser.add_listener(:fragment) {|f| fragment = f}
|
149
|
+
|
150
|
+
parser << "GET http://example.com/foo#bar HTTP/1.1\r\n"
|
151
|
+
|
152
|
+
assert_equal("http://example.com/foo", uri)
|
153
|
+
assert_equal("bar", fragment)
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_with_header
|
157
|
+
parser = HTTPTools::Parser.new
|
158
|
+
method = nil
|
159
|
+
path = nil
|
160
|
+
headers = nil
|
161
|
+
|
162
|
+
parser.add_listener(:method) {|m| method = m}
|
163
|
+
parser.add_listener(:path) {|p, q| path = p}
|
164
|
+
parser.add_listener(:headers) {|h| headers = h}
|
165
|
+
|
166
|
+
parser << "GET / HTTP/1.1\r\n"
|
167
|
+
parser << "Host: www.example.com\r\n"
|
168
|
+
parser << "\r\n"
|
169
|
+
|
170
|
+
assert_equal("GET", method)
|
171
|
+
assert_equal("/", path)
|
172
|
+
assert_equal({"Host" => "www.example.com"}, headers)
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_with_headers
|
176
|
+
parser = HTTPTools::Parser.new
|
177
|
+
method = nil
|
178
|
+
path = nil
|
179
|
+
headers = nil
|
180
|
+
|
181
|
+
parser.add_listener(:method) {|m| method = m}
|
182
|
+
parser.add_listener(:path) {|p, q| path = p}
|
183
|
+
parser.add_listener(:headers) {|h| headers = h}
|
184
|
+
|
185
|
+
parser << "GET / HTTP/1.1\r\n"
|
186
|
+
parser << "Host: www.example.com\r\n"
|
187
|
+
parser << "Accept: text/plain\r\n"
|
188
|
+
parser << "\r\n"
|
189
|
+
|
190
|
+
assert_equal("GET", method)
|
191
|
+
assert_equal("/", path)
|
192
|
+
assert_equal({"Host"=>"www.example.com", "Accept"=>"text/plain"}, headers)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_sub_line_chunks
|
196
|
+
parser = HTTPTools::Parser.new
|
197
|
+
method = nil
|
198
|
+
path = nil
|
199
|
+
headers = nil
|
200
|
+
|
201
|
+
parser.add_listener(:method) {|m| method = m}
|
202
|
+
parser.add_listener(:path) {|p, q| path = p}
|
203
|
+
parser.add_listener(:headers) {|h| headers = h}
|
204
|
+
|
205
|
+
parser << "GE"
|
206
|
+
parser << "T /foo/"
|
207
|
+
parser << "bar HT"
|
208
|
+
parser << "TP/1.1\r\n"
|
209
|
+
parser << "Host: www.exam"
|
210
|
+
parser << "ple.com\r\n"
|
211
|
+
parser << "Accep"
|
212
|
+
parser << "t: text/plain\r\n\r\n"
|
213
|
+
|
214
|
+
assert_equal("GET", method)
|
215
|
+
assert_equal("/foo/bar", path)
|
216
|
+
assert_equal({"Host"=>"www.example.com", "Accept"=>"text/plain"}, headers)
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_sub_line_chunks_2
|
220
|
+
parser = HTTPTools::Parser.new
|
221
|
+
method = nil
|
222
|
+
path = nil
|
223
|
+
headers = nil
|
224
|
+
|
225
|
+
parser.add_listener(:method) {|m| method = m}
|
226
|
+
parser.add_listener(:path) {|p, q| path = p}
|
227
|
+
parser.add_listener(:headers) {|h| headers = h}
|
228
|
+
|
229
|
+
parser << "POST"
|
230
|
+
parser << " /bar/foo"
|
231
|
+
parser << " HTTP/"
|
232
|
+
parser << "1."
|
233
|
+
parser << "1\r\n"
|
234
|
+
parser << "Host: "
|
235
|
+
parser << "www.example.com\r\n"
|
236
|
+
parser << "Content-Length:"
|
237
|
+
parser << " 11\r\n\r\n"
|
238
|
+
parser << "hello="
|
239
|
+
parser << ""
|
240
|
+
parser << "world"
|
241
|
+
|
242
|
+
assert_equal("POST", method)
|
243
|
+
assert_equal("/bar/foo", path)
|
244
|
+
assert_equal({"Host"=>"www.example.com", "Content-Length"=>"11"}, headers)
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_all_at_once
|
248
|
+
parser = HTTPTools::Parser.new
|
249
|
+
method = nil
|
250
|
+
path = nil
|
251
|
+
headers = nil
|
252
|
+
|
253
|
+
parser.add_listener(:method) {|m| method = m}
|
254
|
+
parser.add_listener(:path) {|p, q| path = p}
|
255
|
+
parser.add_listener(:headers) {|h| headers = h}
|
256
|
+
|
257
|
+
request = "GET /foo/bar HTTP/1.1\r\n"
|
258
|
+
request << "Host: www.example.com\r\nAccept: text/plain\r\n\r\n"
|
259
|
+
|
260
|
+
parser << request
|
261
|
+
|
262
|
+
assert_equal("GET", method)
|
263
|
+
assert_equal("/foo/bar", path)
|
264
|
+
assert_equal({"Host"=>"www.example.com", "Accept"=>"text/plain"}, headers)
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_accepts_imaginary_method
|
268
|
+
parser = HTTPTools::Parser.new
|
269
|
+
method = nil
|
270
|
+
|
271
|
+
parser.add_listener(:method) {|m| method = m}
|
272
|
+
|
273
|
+
parser << "UNICORNS / HTTP/1.1\r\n"
|
274
|
+
|
275
|
+
assert_equal("UNICORNS", method)
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_without_http
|
279
|
+
parser = HTTPTools::Parser.new
|
280
|
+
method = nil
|
281
|
+
path = nil
|
282
|
+
headers = nil
|
283
|
+
|
284
|
+
parser.add_listener(:method) {|m| method = m}
|
285
|
+
parser.add_listener(:path) {|p, q| path = p}
|
286
|
+
parser.add_listener(:headers) {|h| headers = h}
|
287
|
+
|
288
|
+
parser << "GET /\r\nHost: www.example.com\r\n\r\n"
|
289
|
+
|
290
|
+
assert_equal("GET", method)
|
291
|
+
assert_equal("/", path)
|
292
|
+
assert_equal({"Host"=>"www.example.com"}, headers)
|
293
|
+
end
|
294
|
+
|
295
|
+
def test_unknown_protocol
|
296
|
+
parser = HTTPTools::Parser.new
|
297
|
+
|
298
|
+
assert_raise(HTTPTools::ParseError) {parser << "GET / SPDY/1.1\r\n"}
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_protocol_version
|
302
|
+
parser = HTTPTools::Parser.new
|
303
|
+
version = nil
|
304
|
+
|
305
|
+
parser.add_listener(:version) {|v| version = v}
|
306
|
+
|
307
|
+
parser << "GET / HTTP/1.1\r\n"
|
308
|
+
|
309
|
+
assert_equal("1.1", version)
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_protocol_without_version
|
313
|
+
parser = HTTPTools::Parser.new
|
314
|
+
|
315
|
+
assert_raise(HTTPTools::ParseError) {parser << "GET / HTTP\r\n"}
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_reset
|
319
|
+
parser = HTTPTools::Parser.new
|
320
|
+
method = nil
|
321
|
+
method_calls = 0
|
322
|
+
path = nil
|
323
|
+
path_calls = 0
|
324
|
+
headers = nil
|
325
|
+
header_calls = 0
|
326
|
+
|
327
|
+
parser.add_listener(:method) {|m| method = m; method_calls += 1}
|
328
|
+
parser.add_listener(:path) {|p, q| path = p; path_calls += 1}
|
329
|
+
parser.add_listener(:headers) {|h| headers = h; header_calls += 1}
|
330
|
+
|
331
|
+
parser << "GET / HTTP/1.1\r\n"
|
332
|
+
parser << "Host: www.example.com\r\n"
|
333
|
+
parser << "Accept: text/plain\r\n"
|
334
|
+
parser << "\r\n"
|
335
|
+
|
336
|
+
assert_equal("GET", method)
|
337
|
+
assert_equal("/", path)
|
338
|
+
assert_equal({"Host"=>"www.example.com", "Accept"=>"text/plain"}, headers)
|
339
|
+
assert_equal([1, 1, 1], [method_calls, path_calls, header_calls])
|
340
|
+
|
341
|
+
parser.reset
|
342
|
+
|
343
|
+
parser << "POST /example HTTP/1.1\r\n"
|
344
|
+
parser << "Host: www.test.co.uk\r\n"
|
345
|
+
parser << "Accept: text/html\r\n"
|
346
|
+
parser << "\r\n"
|
347
|
+
|
348
|
+
assert_equal("POST", method)
|
349
|
+
assert_equal("/example", path)
|
350
|
+
assert_equal({"Host"=>"www.test.co.uk", "Accept"=>"text/html"}, headers)
|
351
|
+
assert_equal([2, 2, 2], [method_calls, path_calls, header_calls])
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_not_a_http_request
|
355
|
+
parser = HTTPTools::Parser.new
|
356
|
+
|
357
|
+
assert_raise(HTTPTools::ParseError) {parser << "not a http request"}
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_data_past_end
|
361
|
+
parser = HTTPTools::Parser.new
|
362
|
+
parser << "POST /example HTTP/1.1\r\n"
|
363
|
+
parser << "Content-Length: 8\r\n"
|
364
|
+
parser << "\r\n"
|
365
|
+
parser << "test=foo"
|
366
|
+
|
367
|
+
assert_raise(HTTPTools::EndOfMessageError) {parser << "more"}
|
368
|
+
end
|
369
|
+
|
370
|
+
def test_lowecase_method
|
371
|
+
parser = HTTPTools::Parser.new
|
372
|
+
result = nil
|
373
|
+
|
374
|
+
parser.add_listener(:method) do |method|
|
375
|
+
result = method
|
376
|
+
end
|
377
|
+
|
378
|
+
parser << "get / HTTP/1.1\r\n"
|
379
|
+
|
380
|
+
assert_equal("GET", result)
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_lowercase_http
|
384
|
+
parser = HTTPTools::Parser.new
|
385
|
+
version = nil
|
386
|
+
|
387
|
+
parser.add_listener(:version) {|v| version = v}
|
388
|
+
|
389
|
+
parser << "GET / http/1.1\r\n"
|
390
|
+
|
391
|
+
assert_equal("1.1", version)
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_delegate
|
395
|
+
request_class = Class.new
|
396
|
+
request_class.class_eval do
|
397
|
+
attr_reader :http_method, :path, :headers, :body
|
398
|
+
def on_method(name)
|
399
|
+
@http_method = name
|
400
|
+
end
|
401
|
+
def on_path(path, query)
|
402
|
+
@path = path
|
403
|
+
@query = query
|
404
|
+
end
|
405
|
+
def on_headers(headers)
|
406
|
+
@headers = headers
|
407
|
+
end
|
408
|
+
def on_body(body)
|
409
|
+
@body = body
|
410
|
+
end
|
411
|
+
end
|
412
|
+
request = request_class.new
|
413
|
+
|
414
|
+
parser = HTTPTools::Parser.new(request)
|
415
|
+
parser << "POST /test HTTP/1.1\r\n"
|
416
|
+
parser << "Content-Length: 13\r\n"
|
417
|
+
parser << "\r\n"
|
418
|
+
parser << "query=example"
|
419
|
+
|
420
|
+
assert_equal("POST", request.http_method)
|
421
|
+
assert_equal("/test", request.path)
|
422
|
+
assert_equal({"Content-Length" => "13"}, request.headers)
|
423
|
+
assert_equal("query=example", request.body)
|
424
|
+
end
|
425
|
+
|
426
|
+
def test_invalid_version
|
427
|
+
parser = HTTPTools::Parser.new
|
428
|
+
|
429
|
+
assert_raise(HTTPTools::ParseError) {parser << "GET / HTTP/one dot one\r\n"}
|
430
|
+
end
|
431
|
+
|
432
|
+
def test_invalid_header_key_with_space
|
433
|
+
parser = HTTPTools::Parser.new
|
434
|
+
|
435
|
+
assert_raise(HTTPTools::ParseError) do
|
436
|
+
parser << "GET / HTTP/1.1\r\nx-invalid key: text/plain\r\n"
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def test_invalid_header_key_with_colon
|
441
|
+
parser = HTTPTools::Parser.new
|
442
|
+
|
443
|
+
assert_raise(HTTPTools::ParseError) do
|
444
|
+
parser << "GET / HTTP/1.1\r\nx-invalid:key: text/plain\r\n"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def test_invalid_header_key_with_control_character
|
449
|
+
parser = HTTPTools::Parser.new
|
450
|
+
|
451
|
+
assert_raise(HTTPTools::ParseError) do
|
452
|
+
parser << "GET / HTTP/1.1\r\nx-invalid\0key: text/plain\r\n"
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def test_invalid_header_key_with_non_ascii_character
|
457
|
+
parser = HTTPTools::Parser.new
|
458
|
+
|
459
|
+
assert_raise(HTTPTools::ParseError) do
|
460
|
+
parser << "GET / HTTP/1.1\r\nx-invalid-kéy: text/plain\r\n"
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def test_invalid_header_value_with_non_ascii_character
|
465
|
+
parser = HTTPTools::Parser.new
|
466
|
+
|
467
|
+
assert_raise(HTTPTools::ParseError) do
|
468
|
+
parser << "GET / HTTP/1.1\r\nAccept: téxt/plain\r\n"
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def test_error_callback
|
473
|
+
parser = HTTPTools::Parser.new
|
474
|
+
error = nil
|
475
|
+
parser.on(:error) {|e| error = e}
|
476
|
+
|
477
|
+
assert_nothing_raised(Exception) {parser << "1"}
|
478
|
+
assert_instance_of(HTTPTools::ParseError, error)
|
479
|
+
end
|
480
|
+
|
481
|
+
end
|