excon 0.64.0 → 0.65.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of excon might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/data/cacert.pem +108 -2
- data/excon.gemspec +7 -2
- data/lib/excon.rb +11 -11
- data/lib/excon/connection.rb +25 -25
- data/lib/excon/response.rb +1 -1
- data/lib/excon/version.rb +1 -1
- metadata +3 -101
- data/.document +0 -5
- data/.github/stale.yml +0 -17
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/.travis.yml +0 -17
- data/Gemfile +0 -19
- data/Rakefile +0 -41
- data/benchmarks/class_vs_lambda.rb +0 -50
- data/benchmarks/concat_vs_insert.rb +0 -21
- data/benchmarks/concat_vs_interpolate.rb +0 -22
- data/benchmarks/cr_lf.rb +0 -21
- data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
- data/benchmarks/excon.rb +0 -69
- data/benchmarks/excon_vs.rb +0 -165
- data/benchmarks/for_vs_array_each.rb +0 -27
- data/benchmarks/for_vs_hash_each.rb +0 -27
- data/benchmarks/has_key-vs-lookup.rb +0 -177
- data/benchmarks/headers_case_sensitivity.rb +0 -83
- data/benchmarks/headers_split_vs_match.rb +0 -34
- data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
- data/benchmarks/merging.rb +0 -21
- data/benchmarks/single_vs_double_quotes.rb +0 -21
- data/benchmarks/string_ranged_index.rb +0 -87
- data/benchmarks/strip_newline.rb +0 -115
- data/benchmarks/vs_stdlib.rb +0 -82
- data/changelog.txt +0 -1113
- data/spec/excon/error_spec.rb +0 -139
- data/spec/excon/test/server_spec.rb +0 -28
- data/spec/excon_spec.rb +0 -7
- data/spec/helpers/file_path_helpers.rb +0 -22
- data/spec/helpers/warning_helpers.rb +0 -9
- data/spec/requests/basic_spec.rb +0 -40
- data/spec/requests/eof_requests_spec.rb +0 -36
- data/spec/requests/unix_socket_spec.rb +0 -38
- data/spec/requests/validation_spec.rb +0 -80
- data/spec/spec_helper.rb +0 -26
- data/spec/support/shared_contexts/test_server_context.rb +0 -83
- data/spec/support/shared_contexts/test_stub_context.rb +0 -11
- data/spec/support/shared_examples/shared_example_for_clients.rb +0 -220
- data/spec/support/shared_examples/shared_example_for_streaming_clients.rb +0 -20
- data/spec/support/shared_examples/shared_example_for_test_servers.rb +0 -16
- data/tests/authorization_header_tests.rb +0 -27
- data/tests/bad_tests.rb +0 -69
- data/tests/basic_tests.rb +0 -351
- data/tests/batch_requests.rb +0 -133
- data/tests/complete_responses.rb +0 -31
- data/tests/data/127.0.0.1.cert.crt +0 -17
- data/tests/data/127.0.0.1.cert.key +0 -28
- data/tests/data/excon.cert.crt +0 -17
- data/tests/data/excon.cert.key +0 -28
- data/tests/data/xs +0 -1
- data/tests/error_tests.rb +0 -145
- data/tests/header_tests.rb +0 -119
- data/tests/instrumentors/logging_instrumentor_tests.rb +0 -28
- data/tests/middleware_tests.rb +0 -27
- data/tests/middlewares/canned_response_tests.rb +0 -34
- data/tests/middlewares/capture_cookies_tests.rb +0 -34
- data/tests/middlewares/decompress_tests.rb +0 -157
- data/tests/middlewares/escape_path_tests.rb +0 -36
- data/tests/middlewares/idempotent_tests.rb +0 -245
- data/tests/middlewares/instrumentation_tests.rb +0 -315
- data/tests/middlewares/mock_tests.rb +0 -304
- data/tests/middlewares/redirect_follower_tests.rb +0 -112
- data/tests/pipeline_tests.rb +0 -40
- data/tests/proxy_tests.rb +0 -306
- data/tests/query_string_tests.rb +0 -87
- data/tests/rackups/basic.rb +0 -41
- data/tests/rackups/basic.ru +0 -3
- data/tests/rackups/basic_auth.ru +0 -14
- data/tests/rackups/deflater.ru +0 -4
- data/tests/rackups/proxy.ru +0 -18
- data/tests/rackups/query_string.ru +0 -13
- data/tests/rackups/redirecting.ru +0 -23
- data/tests/rackups/redirecting_with_cookie.ru +0 -40
- data/tests/rackups/request_headers.ru +0 -15
- data/tests/rackups/request_methods.ru +0 -21
- data/tests/rackups/response_header.ru +0 -18
- data/tests/rackups/ssl.ru +0 -16
- data/tests/rackups/ssl_mismatched_cn.ru +0 -15
- data/tests/rackups/ssl_verify_peer.ru +0 -16
- data/tests/rackups/streaming.ru +0 -30
- data/tests/rackups/thread_safety.ru +0 -17
- data/tests/rackups/timeout.ru +0 -14
- data/tests/rackups/webrick_patch.rb +0 -34
- data/tests/request_headers_tests.rb +0 -21
- data/tests/request_method_tests.rb +0 -47
- data/tests/request_tests.rb +0 -58
- data/tests/response_tests.rb +0 -197
- data/tests/servers/bad.rb +0 -25
- data/tests/servers/eof.rb +0 -17
- data/tests/servers/error.rb +0 -20
- data/tests/servers/good.rb +0 -342
- data/tests/servers/good_ipv4.rb +0 -8
- data/tests/servers/good_ipv6.rb +0 -8
- data/tests/test_helper.rb +0 -297
- data/tests/thread_safety_tests.rb +0 -39
- data/tests/timeout_tests.rb +0 -12
- data/tests/utils_tests.rb +0 -81
@@ -1,21 +0,0 @@
|
|
1
|
-
Shindo.tests('Excon request methods') do
|
2
|
-
|
3
|
-
with_rackup('request_headers.ru') do
|
4
|
-
|
5
|
-
tests 'empty headers sent' do
|
6
|
-
|
7
|
-
test('Excon.post') do
|
8
|
-
headers = {
|
9
|
-
:one => 1,
|
10
|
-
:two => nil,
|
11
|
-
:three => 3,
|
12
|
-
}
|
13
|
-
r = Excon.post('http://localhost:9292', :headers => headers).body
|
14
|
-
!r.match(/two:/).nil?
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
Shindo.tests('Excon request methods') do
|
2
|
-
|
3
|
-
with_rackup('request_methods.ru') do
|
4
|
-
|
5
|
-
tests 'one-offs' do
|
6
|
-
|
7
|
-
tests('Excon.get').returns('GET') do
|
8
|
-
Excon.get('http://localhost:9292').body
|
9
|
-
end
|
10
|
-
|
11
|
-
tests('Excon.post').returns('POST') do
|
12
|
-
Excon.post('http://localhost:9292').body
|
13
|
-
end
|
14
|
-
|
15
|
-
tests('Excon.delete').returns('DELETE') do
|
16
|
-
Excon.delete('http://localhost:9292').body
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
tests 'with a connection object' do
|
22
|
-
connection = nil
|
23
|
-
|
24
|
-
tests('connection.get').returns('GET') do
|
25
|
-
connection = Excon.new('http://localhost:9292')
|
26
|
-
connection.get.body
|
27
|
-
end
|
28
|
-
|
29
|
-
tests('connection.post').returns('POST') do
|
30
|
-
connection.post.body
|
31
|
-
end
|
32
|
-
|
33
|
-
tests('connection.delete').returns('DELETE') do
|
34
|
-
connection.delete.body
|
35
|
-
end
|
36
|
-
|
37
|
-
tests('not modifies path argument').returns('path') do
|
38
|
-
path = 'path'
|
39
|
-
connection.get(:path => path)
|
40
|
-
path
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
data/tests/request_tests.rb
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
Shindo.tests('Request Tests') do
|
2
|
-
tests('persistent connections') do
|
3
|
-
ip_ports = [['127.0.0.1:9292', 'good_ipv4']]
|
4
|
-
ip_ports << ['[::1]:9293', 'good_ipv6'] unless RUBY_PLATFORM == 'java'
|
5
|
-
ip_ports.each do |ip_port, server|
|
6
|
-
with_server(server) do
|
7
|
-
|
8
|
-
tests("with default :persistent => true, #{ip_port}") do
|
9
|
-
connection = nil
|
10
|
-
|
11
|
-
returns(['1', '2'], 'uses a persistent connection') do
|
12
|
-
connection = Excon.new("http://#{ip_port}", :persistent => true)
|
13
|
-
2.times.map do
|
14
|
-
connection.request(:method => :get, :path => '/echo/request_count').body
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
returns(['3', '1', '2'], ':persistent => false resets connection') do
|
19
|
-
ret = []
|
20
|
-
ret << connection.request(:method => :get,
|
21
|
-
:path => '/echo/request_count',
|
22
|
-
:persistent => false).body
|
23
|
-
ret << connection.request(:method => :get,
|
24
|
-
:path => '/echo/request_count').body
|
25
|
-
ret << connection.request(:method => :get,
|
26
|
-
:path => '/echo/request_count').body
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
tests("with default :persistent => false, #{ip_port}") do
|
31
|
-
connection = nil
|
32
|
-
|
33
|
-
returns(['1', '1'], 'does not use a persistent connection') do
|
34
|
-
connection = Excon.new("http://#{ip_port}", :persistent => false)
|
35
|
-
2.times.map do
|
36
|
-
connection.request(:method => :get, :path => '/echo/request_count').body
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
returns(['1', '2', '3', '1'], ':persistent => true enables persistence') do
|
41
|
-
ret = []
|
42
|
-
ret << connection.request(:method => :get,
|
43
|
-
:path => '/echo/request_count',
|
44
|
-
:persistent => true).body
|
45
|
-
ret << connection.request(:method => :get,
|
46
|
-
:path => '/echo/request_count',
|
47
|
-
:persistent => true).body
|
48
|
-
ret << connection.request(:method => :get,
|
49
|
-
:path => '/echo/request_count').body
|
50
|
-
ret << connection.request(:method => :get,
|
51
|
-
:path => '/echo/request_count').body
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
data/tests/response_tests.rb
DELETED
@@ -1,197 +0,0 @@
|
|
1
|
-
Shindo.tests('Excon Response Parsing') do
|
2
|
-
env_init
|
3
|
-
|
4
|
-
with_server('good_ipv4') do
|
5
|
-
|
6
|
-
tests('responses with chunked transfer-encoding') do
|
7
|
-
|
8
|
-
tests('simple response').returns('hello world') do
|
9
|
-
Excon.get('http://127.0.0.1:9292/chunked/simple').body
|
10
|
-
end
|
11
|
-
|
12
|
-
tests('with :response_block') do
|
13
|
-
|
14
|
-
tests('simple response').
|
15
|
-
returns([['hello ', nil, nil], ['world', nil, nil]]) do
|
16
|
-
capture_response_block do |block|
|
17
|
-
Excon.get('http://127.0.0.1:9292/chunked/simple',
|
18
|
-
:response_block => block,
|
19
|
-
:chunk_size => 5) # not used
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
tests('simple response has empty body').returns('') do
|
24
|
-
response_block = lambda { |_, _, _| }
|
25
|
-
Excon.get('http://127.0.0.1:9292/chunked/simple', :response_block => response_block).body
|
26
|
-
end
|
27
|
-
|
28
|
-
tests('with expected response status').
|
29
|
-
returns([['hello ', nil, nil], ['world', nil, nil]]) do
|
30
|
-
capture_response_block do |block|
|
31
|
-
Excon.get('http://127.0.0.1:9292/chunked/simple',
|
32
|
-
:response_block => block,
|
33
|
-
:expects => 200)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
tests('with unexpected response status').returns('hello world') do
|
38
|
-
begin
|
39
|
-
Excon.get('http://127.0.0.1:9292/chunked/simple',
|
40
|
-
:response_block => Proc.new { raise 'test failed' },
|
41
|
-
:expects => 500)
|
42
|
-
rescue Excon::Errors::HTTPStatusError => err
|
43
|
-
err.response[:body]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
tests('merges trailers into headers').
|
50
|
-
returns('one, two, three, four, five, six') do
|
51
|
-
Excon.get('http://127.0.0.1:9292/chunked/trailers').headers['Test-Header']
|
52
|
-
end
|
53
|
-
|
54
|
-
tests("removes 'chunked' from Transfer-Encoding").returns(nil) do
|
55
|
-
Excon.get('http://127.0.0.1:9292/chunked/simple').headers['Transfer-Encoding']
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
tests('responses with content-length') do
|
61
|
-
|
62
|
-
tests('simple response').returns('hello world') do
|
63
|
-
Excon.get('http://127.0.0.1:9292/content-length/simple').body
|
64
|
-
end
|
65
|
-
|
66
|
-
tests('with :response_block') do
|
67
|
-
|
68
|
-
tests('simple response').
|
69
|
-
returns([['hello', 6, 11], [' worl', 1, 11], ['d', 0, 11]]) do
|
70
|
-
capture_response_block do |block|
|
71
|
-
Excon.get('http://127.0.0.1:9292/content-length/simple',
|
72
|
-
:response_block => block,
|
73
|
-
:chunk_size => 5)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
tests('simple response has empty body').returns('') do
|
78
|
-
response_block = lambda { |_, _, _| }
|
79
|
-
Excon.get('http://127.0.0.1:9292/content-length/simple', :response_block => response_block).body
|
80
|
-
end
|
81
|
-
|
82
|
-
tests('with expected response status').
|
83
|
-
returns([['hello', 6, 11], [' worl', 1, 11], ['d', 0, 11]]) do
|
84
|
-
capture_response_block do |block|
|
85
|
-
Excon.get('http://127.0.0.1:9292/content-length/simple',
|
86
|
-
:response_block => block,
|
87
|
-
:chunk_size => 5,
|
88
|
-
:expects => 200)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
tests('with unexpected response status').returns('hello world') do
|
93
|
-
begin
|
94
|
-
Excon.get('http://127.0.0.1:9292/content-length/simple',
|
95
|
-
:response_block => Proc.new { raise 'test failed' },
|
96
|
-
:chunk_size => 5,
|
97
|
-
:expects => 500)
|
98
|
-
rescue Excon::Errors::HTTPStatusError => err
|
99
|
-
err.response[:body]
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
tests('responses with unknown length') do
|
108
|
-
|
109
|
-
tests('simple response').returns('hello world') do
|
110
|
-
Excon.get('http://127.0.0.1:9292/unknown/simple').body
|
111
|
-
end
|
112
|
-
|
113
|
-
tests('with :response_block') do
|
114
|
-
|
115
|
-
tests('simple response').
|
116
|
-
returns([['hello', nil, nil], [' worl', nil, nil], ['d', nil, nil]]) do
|
117
|
-
capture_response_block do |block|
|
118
|
-
Excon.get('http://127.0.0.1:9292/unknown/simple',
|
119
|
-
:response_block => block,
|
120
|
-
:chunk_size => 5)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
tests('simple response has empty body').returns('') do
|
125
|
-
response_block = lambda { |_, _, _| }
|
126
|
-
Excon.get('http://127.0.0.1:9292/unknown/simple', :response_block => response_block).body
|
127
|
-
end
|
128
|
-
|
129
|
-
tests('with expected response status').
|
130
|
-
returns([['hello', nil, nil], [' worl', nil, nil], ['d', nil, nil]]) do
|
131
|
-
capture_response_block do |block|
|
132
|
-
Excon.get('http://127.0.0.1:9292/unknown/simple',
|
133
|
-
:response_block => block,
|
134
|
-
:chunk_size => 5,
|
135
|
-
:expects => 200)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
tests('with unexpected response status').returns('hello world') do
|
140
|
-
begin
|
141
|
-
Excon.get('http://127.0.0.1:9292/unknown/simple',
|
142
|
-
:response_block => Proc.new { raise 'test failed' },
|
143
|
-
:chunk_size => 5,
|
144
|
-
:expects => 500)
|
145
|
-
rescue Excon::Errors::HTTPStatusError => err
|
146
|
-
err.response[:body]
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
end
|
153
|
-
|
154
|
-
tests('cookies') do
|
155
|
-
|
156
|
-
tests('parses cookies into array').returns(['one, two', 'three, four']) do
|
157
|
-
resp = Excon.get('http://127.0.0.1:9292/unknown/cookies')
|
158
|
-
resp[:cookies]
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
tests('header continuation') do
|
164
|
-
|
165
|
-
tests('proper continuation').returns('one, two, three, four, five, six') do
|
166
|
-
resp = Excon.get('http://127.0.0.1:9292/unknown/header_continuation')
|
167
|
-
resp.headers['Test-Header']
|
168
|
-
end
|
169
|
-
|
170
|
-
tests('malformed header').raises(Excon::Errors::SocketError) do
|
171
|
-
Excon.get('http://127.0.0.1:9292/bad/malformed_header')
|
172
|
-
end
|
173
|
-
|
174
|
-
tests('malformed header continuation').raises(Excon::Errors::SocketError) do
|
175
|
-
Excon.get('http://127.0.0.1:9292/bad/malformed_header_continuation')
|
176
|
-
end
|
177
|
-
|
178
|
-
end
|
179
|
-
|
180
|
-
tests('status line parsing') do
|
181
|
-
|
182
|
-
tests('proper status code').returns(404) do
|
183
|
-
resp = Excon.get('http://127.0.0.1:9292/not-found')
|
184
|
-
resp.status
|
185
|
-
end
|
186
|
-
|
187
|
-
tests('proper reason phrase').returns("Not Found") do
|
188
|
-
resp = Excon.get('http://127.0.0.1:9292/not-found')
|
189
|
-
resp.reason_phrase
|
190
|
-
end
|
191
|
-
|
192
|
-
end
|
193
|
-
|
194
|
-
end
|
195
|
-
|
196
|
-
env_restore
|
197
|
-
end
|
data/tests/servers/bad.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "eventmachine"
|
4
|
-
|
5
|
-
module BadServer
|
6
|
-
def receive_data(data)
|
7
|
-
case data
|
8
|
-
when %r{^GET /echo\s}
|
9
|
-
send_data "HTTP/1.1 200 OK\r\n"
|
10
|
-
send_data "\r\n"
|
11
|
-
send_data data
|
12
|
-
close_connection(true)
|
13
|
-
when %r{^GET /eof/no_content_length_and_no_chunking\s}
|
14
|
-
send_data "HTTP/1.1 200 OK\r\n"
|
15
|
-
send_data "\r\n"
|
16
|
-
send_data "hello"
|
17
|
-
close_connection(true)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
EM.run do
|
23
|
-
EM.start_server("127.0.0.1", 9292, BadServer)
|
24
|
-
$stderr.puts "ready"
|
25
|
-
end
|
data/tests/servers/eof.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "eventmachine"
|
4
|
-
|
5
|
-
module EOFServer
|
6
|
-
def receive_data(data)
|
7
|
-
case data
|
8
|
-
when %r{^GET /eof\s}
|
9
|
-
close_connection(true)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
EM.run do
|
15
|
-
EM.start_server("127.0.0.1", 9292, EOFServer)
|
16
|
-
$stderr.puts "ready"
|
17
|
-
end
|
data/tests/servers/error.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "eventmachine"
|
4
|
-
|
5
|
-
module ErrorServer
|
6
|
-
def receive_data(data)
|
7
|
-
case data
|
8
|
-
when %r{^GET /error/not_found\s}
|
9
|
-
send_data "HTTP/1.1 404 Not Found\r\n"
|
10
|
-
send_data "\r\n"
|
11
|
-
send_data "server says not found"
|
12
|
-
close_connection(true)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
EM.run do
|
18
|
-
EM.start_server("127.0.0.1", 9292, ErrorServer)
|
19
|
-
$stderr.puts "ready"
|
20
|
-
end
|
data/tests/servers/good.rb
DELETED
@@ -1,342 +0,0 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
require 'stringio'
|
3
|
-
require 'uri'
|
4
|
-
require 'zlib'
|
5
|
-
|
6
|
-
module GoodServer
|
7
|
-
# This method will be called with each request received.
|
8
|
-
#
|
9
|
-
# request = {
|
10
|
-
# :method => method,
|
11
|
-
# :uri => URI.parse(uri),
|
12
|
-
# :headers => {},
|
13
|
-
# :body => ''
|
14
|
-
# }
|
15
|
-
#
|
16
|
-
# Each connection to this server is persistent unless the client sends
|
17
|
-
# "Connection: close" in the request. If a response requires the connection
|
18
|
-
# to be closed, use `start_response(:persistent => false)`.
|
19
|
-
def send_response(request)
|
20
|
-
type, path = request[:uri].path.split('/', 3)[1, 2]
|
21
|
-
case type
|
22
|
-
when 'echo'
|
23
|
-
case path
|
24
|
-
when 'request'
|
25
|
-
data = Marshal.dump(request)
|
26
|
-
start_response
|
27
|
-
send_data "Content-Length: #{ data.size }\r\n"
|
28
|
-
send_data "\r\n"
|
29
|
-
send_data data
|
30
|
-
|
31
|
-
when 'request_count'
|
32
|
-
(@request_count ||= '0').next!
|
33
|
-
start_response
|
34
|
-
send_data "Content-Length: #{ @request_count.size }\r\n"
|
35
|
-
send_data "Connection: Keep-Alive\r\n"
|
36
|
-
send_data "\r\n"
|
37
|
-
send_data @request_count
|
38
|
-
|
39
|
-
when /(content|transfer)-encoded\/?(.*)/
|
40
|
-
if (encoding_type = $1) == 'content'
|
41
|
-
accept_header = 'Accept-Encoding'
|
42
|
-
encoding_header = 'Content-Encoding'
|
43
|
-
else
|
44
|
-
accept_header = 'TE'
|
45
|
-
encoding_header = 'Transfer-Encoding'
|
46
|
-
end
|
47
|
-
chunked = $2 == 'chunked'
|
48
|
-
|
49
|
-
encodings = parse_encodings(request[:headers][accept_header])
|
50
|
-
while encoding = encodings.pop
|
51
|
-
break if ['gzip', 'deflate'].include?(encoding)
|
52
|
-
end
|
53
|
-
|
54
|
-
case encoding
|
55
|
-
when 'gzip'
|
56
|
-
body = request[:body]
|
57
|
-
if(body.nil? || body.empty?)
|
58
|
-
body = ''
|
59
|
-
else
|
60
|
-
io = (Zlib::GzipWriter.new(StringIO.new) << request[:body]).finish
|
61
|
-
io.rewind
|
62
|
-
body = io.read
|
63
|
-
end
|
64
|
-
when 'deflate'
|
65
|
-
# drops the zlib header
|
66
|
-
deflator = Zlib::Deflate.new(nil, -Zlib::MAX_WBITS)
|
67
|
-
body = deflator.deflate(request[:body], Zlib::FINISH)
|
68
|
-
deflator.close
|
69
|
-
else
|
70
|
-
body = request[:body]
|
71
|
-
end
|
72
|
-
|
73
|
-
# simulate server pre/post content encoding
|
74
|
-
encodings = [
|
75
|
-
request[:headers]["#{ encoding_header }-Pre"],
|
76
|
-
encoding,
|
77
|
-
request[:headers]["#{ encoding_header }-Post"],
|
78
|
-
]
|
79
|
-
if chunked && encoding_type == 'transfer'
|
80
|
-
encodings << 'chunked'
|
81
|
-
end
|
82
|
-
encodings = encodings.compact.join(', ')
|
83
|
-
|
84
|
-
start_response
|
85
|
-
# let the test know what the server sent
|
86
|
-
send_data "#{ encoding_header }-Sent: #{ encodings }\r\n"
|
87
|
-
send_data "#{ encoding_header }: #{ encodings }\r\n" unless encodings.empty?
|
88
|
-
if chunked
|
89
|
-
if encoding_type == 'content'
|
90
|
-
send_data "Transfer-Encoding: chunked\r\n"
|
91
|
-
end
|
92
|
-
send_data "\r\n"
|
93
|
-
send_data chunks_for(body)
|
94
|
-
send_data "\r\n"
|
95
|
-
else
|
96
|
-
send_data "Content-Length: #{ body.size }\r\n"
|
97
|
-
send_data "\r\n"
|
98
|
-
send_data body
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
when 'chunked'
|
103
|
-
case path
|
104
|
-
when 'simple'
|
105
|
-
start_response
|
106
|
-
send_data "Transfer-Encoding: chunked\r\n"
|
107
|
-
send_data "\r\n"
|
108
|
-
# chunk-extension is currently ignored.
|
109
|
-
# this works because "6; chunk-extension".to_i => "6"
|
110
|
-
send_data "6; chunk-extension\r\n"
|
111
|
-
send_data "hello \r\n"
|
112
|
-
send_data "5; chunk-extension\r\n"
|
113
|
-
send_data "world\r\n"
|
114
|
-
send_data "0; chunk-extension\r\n" # last-chunk
|
115
|
-
send_data "\r\n"
|
116
|
-
|
117
|
-
# merged trailers also support continuations
|
118
|
-
when 'trailers'
|
119
|
-
start_response
|
120
|
-
send_data "Transfer-Encoding: chunked\r\n"
|
121
|
-
send_data "Test-Header: one, two\r\n"
|
122
|
-
send_data "\r\n"
|
123
|
-
send_data chunks_for('hello world')
|
124
|
-
send_data "Test-Header: three, four,\r\n"
|
125
|
-
send_data "\tfive, six\r\n"
|
126
|
-
send_data "\r\n"
|
127
|
-
end
|
128
|
-
|
129
|
-
when 'content-length'
|
130
|
-
case path
|
131
|
-
when 'simple'
|
132
|
-
start_response
|
133
|
-
send_data "Content-Length: 11\r\n"
|
134
|
-
send_data "\r\n"
|
135
|
-
send_data "hello world"
|
136
|
-
end
|
137
|
-
|
138
|
-
when 'unknown'
|
139
|
-
case path
|
140
|
-
when 'cookies'
|
141
|
-
start_response(:persistent => false)
|
142
|
-
send_data "Set-Cookie: one, two\r\n"
|
143
|
-
send_data "Set-Cookie: three, four\r\n"
|
144
|
-
send_data "\r\n"
|
145
|
-
send_data "hello world"
|
146
|
-
|
147
|
-
when 'simple'
|
148
|
-
start_response(:persistent => false)
|
149
|
-
send_data "\r\n"
|
150
|
-
send_data "hello world"
|
151
|
-
|
152
|
-
when 'header_continuation'
|
153
|
-
start_response(:persistent => false)
|
154
|
-
send_data "Test-Header: one, two\r\n"
|
155
|
-
send_data "Test-Header: three, four,\r\n"
|
156
|
-
send_data " five, six\r\n"
|
157
|
-
send_data "\r\n"
|
158
|
-
send_data "hello world"
|
159
|
-
end
|
160
|
-
|
161
|
-
when 'bad'
|
162
|
-
# Excon will close these connections due to the errors.
|
163
|
-
case path
|
164
|
-
when 'malformed_header'
|
165
|
-
start_response
|
166
|
-
send_data "Bad-Header\r\n" # no ':'
|
167
|
-
send_data "\r\n"
|
168
|
-
send_data "hello world"
|
169
|
-
|
170
|
-
when 'malformed_header_continuation'
|
171
|
-
send_data "HTTP/1.1 200 OK\r\n"
|
172
|
-
send_data " Bad-Header: one, two\r\n" # no previous header
|
173
|
-
send_data "\r\n"
|
174
|
-
send_data "hello world"
|
175
|
-
end
|
176
|
-
|
177
|
-
when 'not-found'
|
178
|
-
start_response(:status => "404 Not Found")
|
179
|
-
send_data "Content-Length: 11\r\n"
|
180
|
-
send_data "\r\n"
|
181
|
-
send_data "hello world"
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Sends response status-line, plus headers common to all responses.
|
186
|
-
def start_response(opts = {})
|
187
|
-
opts = {
|
188
|
-
:status => '200 OK',
|
189
|
-
:persistent => @persistent # true unless client sent Connection: close
|
190
|
-
}.merge!(opts)
|
191
|
-
|
192
|
-
@persistent = opts[:persistent]
|
193
|
-
send_data "HTTP/1.1 #{ opts[:status] }\r\n"
|
194
|
-
send_data "Connection: close\r\n" unless @persistent
|
195
|
-
end
|
196
|
-
|
197
|
-
def post_init
|
198
|
-
@buffer = StringIO.new
|
199
|
-
@buffer.set_encoding('BINARY') if @buffer.respond_to?(:set_encoding)
|
200
|
-
end
|
201
|
-
|
202
|
-
# Receives a String of +data+ sent from the client.
|
203
|
-
# +data+ may only be a portion of what the client sent.
|
204
|
-
# The data is buffered, then processed and removed from the buffer
|
205
|
-
# as data becomes available until the @request is complete.
|
206
|
-
def receive_data(data)
|
207
|
-
@buffer.seek(0, IO::SEEK_END)
|
208
|
-
@buffer.write(data)
|
209
|
-
|
210
|
-
parse_headers unless @request
|
211
|
-
parse_body if @request
|
212
|
-
|
213
|
-
if @request_complete
|
214
|
-
send_response(@request)
|
215
|
-
if @persistent
|
216
|
-
@request = nil
|
217
|
-
@request_complete = false
|
218
|
-
# process remaining buffer for next request
|
219
|
-
receive_data('') unless @buffer.eof?
|
220
|
-
else
|
221
|
-
close_connection(true)
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
# Removes the processed portion of the buffer
|
227
|
-
# by replacing the buffer with it's contents from the current pos.
|
228
|
-
def sync_buffer
|
229
|
-
@buffer.string = @buffer.read
|
230
|
-
end
|
231
|
-
|
232
|
-
def parse_headers
|
233
|
-
@buffer.rewind
|
234
|
-
# wait until buffer contains the end of the headers
|
235
|
-
if /\sHTTP\/\d+\.\d+\r\n.*?\r\n\r\n/m =~ @buffer.read
|
236
|
-
@buffer.rewind
|
237
|
-
# For persistent connections, the buffer could start with the
|
238
|
-
# \r\n chunked-message terminator from the previous request.
|
239
|
-
# This will discard anything up to the request-line.
|
240
|
-
until m = /^(\w+)\s(.*)\sHTTP\/\d+\.\d+$/.match(@buffer.readline.chop!); end
|
241
|
-
method, uri = m[1, 2]
|
242
|
-
|
243
|
-
headers = {}
|
244
|
-
last_key = nil
|
245
|
-
until (line = @buffer.readline.chop!).empty?
|
246
|
-
if !line.lstrip!.nil?
|
247
|
-
headers[last_key] << ' ' << line.rstrip
|
248
|
-
else
|
249
|
-
key, value = line.split(':', 2)
|
250
|
-
headers[key] = ([headers[key]] << value.strip).compact.join(', ')
|
251
|
-
last_key = key
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
sync_buffer
|
256
|
-
|
257
|
-
@chunked = headers['Transfer-Encoding'] =~ /chunked/i
|
258
|
-
@content_length = headers['Content-Length'].to_i
|
259
|
-
@persistent = headers['Connection'] !~ /close/i
|
260
|
-
@request = {
|
261
|
-
:method => method,
|
262
|
-
:uri => URI.parse(uri),
|
263
|
-
:headers => headers,
|
264
|
-
:body => ''
|
265
|
-
}
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def parse_body
|
270
|
-
if @chunked
|
271
|
-
@buffer.rewind
|
272
|
-
until @request_complete || @buffer.eof?
|
273
|
-
unless @chunk_size
|
274
|
-
# in case buffer only contains a portion of the chunk-size line
|
275
|
-
if (line = @buffer.readline) =~ /\r\n\z/
|
276
|
-
@chunk_size = line.to_i(16)
|
277
|
-
if @chunk_size > 0
|
278
|
-
sync_buffer
|
279
|
-
else # last-chunk
|
280
|
-
@buffer.read(2) # the final \r\n may or may not be in the buffer
|
281
|
-
sync_buffer
|
282
|
-
@chunk_size = nil
|
283
|
-
@request_complete = true
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|
287
|
-
if @chunk_size
|
288
|
-
if @buffer.size >= @chunk_size + 2
|
289
|
-
@request[:body] << @buffer.read(@chunk_size + 2).chop!
|
290
|
-
@chunk_size = nil
|
291
|
-
sync_buffer
|
292
|
-
else
|
293
|
-
break # wait for more data
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
elsif @content_length > 0
|
298
|
-
@buffer.rewind
|
299
|
-
unless @buffer.eof? # buffer only contained the headers
|
300
|
-
@request[:body] << @buffer.read(@content_length - @request[:body].size)
|
301
|
-
sync_buffer
|
302
|
-
if @request[:body].size == @content_length
|
303
|
-
@request_complete = true
|
304
|
-
end
|
305
|
-
end
|
306
|
-
else
|
307
|
-
# no body
|
308
|
-
@request_complete = true
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
def chunks_for(str)
|
313
|
-
chunks = ''
|
314
|
-
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
|
315
|
-
chunk_size = str.size / 2
|
316
|
-
until (chunk = str.slice!(0, chunk_size)).empty?
|
317
|
-
chunks << chunk.size.to_s(16) << "\r\n"
|
318
|
-
chunks << chunk << "\r\n"
|
319
|
-
end
|
320
|
-
chunks << "0\r\n" # last-chunk
|
321
|
-
end
|
322
|
-
|
323
|
-
# only supports a single quality parameter for tokens
|
324
|
-
def parse_encodings(encodings)
|
325
|
-
return [] if encodings.nil?
|
326
|
-
split_header_value(encodings).map do |value|
|
327
|
-
token, q_val = /^(.*?)(?:;q=(.*))?$/.match(value.strip)[1, 2]
|
328
|
-
if q_val && q_val.to_f == 0
|
329
|
-
nil
|
330
|
-
else
|
331
|
-
[token, (q_val || 1).to_f]
|
332
|
-
end
|
333
|
-
end.compact.sort_by {|_, q_val| q_val }.map {|token, _| token }
|
334
|
-
end
|
335
|
-
|
336
|
-
# Splits a header value +str+ according to HTTP specification.
|
337
|
-
def split_header_value(str)
|
338
|
-
return [] if str.nil?
|
339
|
-
str.strip.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
|
340
|
-
(?:,\s*|\Z)'xn).flatten
|
341
|
-
end
|
342
|
-
end
|