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.

Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/data/cacert.pem +108 -2
  3. data/excon.gemspec +7 -2
  4. data/lib/excon.rb +11 -11
  5. data/lib/excon/connection.rb +25 -25
  6. data/lib/excon/response.rb +1 -1
  7. data/lib/excon/version.rb +1 -1
  8. metadata +3 -101
  9. data/.document +0 -5
  10. data/.github/stale.yml +0 -17
  11. data/.gitignore +0 -13
  12. data/.rspec +0 -3
  13. data/.travis.yml +0 -17
  14. data/Gemfile +0 -19
  15. data/Rakefile +0 -41
  16. data/benchmarks/class_vs_lambda.rb +0 -50
  17. data/benchmarks/concat_vs_insert.rb +0 -21
  18. data/benchmarks/concat_vs_interpolate.rb +0 -22
  19. data/benchmarks/cr_lf.rb +0 -21
  20. data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
  21. data/benchmarks/excon.rb +0 -69
  22. data/benchmarks/excon_vs.rb +0 -165
  23. data/benchmarks/for_vs_array_each.rb +0 -27
  24. data/benchmarks/for_vs_hash_each.rb +0 -27
  25. data/benchmarks/has_key-vs-lookup.rb +0 -177
  26. data/benchmarks/headers_case_sensitivity.rb +0 -83
  27. data/benchmarks/headers_split_vs_match.rb +0 -34
  28. data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
  29. data/benchmarks/merging.rb +0 -21
  30. data/benchmarks/single_vs_double_quotes.rb +0 -21
  31. data/benchmarks/string_ranged_index.rb +0 -87
  32. data/benchmarks/strip_newline.rb +0 -115
  33. data/benchmarks/vs_stdlib.rb +0 -82
  34. data/changelog.txt +0 -1113
  35. data/spec/excon/error_spec.rb +0 -139
  36. data/spec/excon/test/server_spec.rb +0 -28
  37. data/spec/excon_spec.rb +0 -7
  38. data/spec/helpers/file_path_helpers.rb +0 -22
  39. data/spec/helpers/warning_helpers.rb +0 -9
  40. data/spec/requests/basic_spec.rb +0 -40
  41. data/spec/requests/eof_requests_spec.rb +0 -36
  42. data/spec/requests/unix_socket_spec.rb +0 -38
  43. data/spec/requests/validation_spec.rb +0 -80
  44. data/spec/spec_helper.rb +0 -26
  45. data/spec/support/shared_contexts/test_server_context.rb +0 -83
  46. data/spec/support/shared_contexts/test_stub_context.rb +0 -11
  47. data/spec/support/shared_examples/shared_example_for_clients.rb +0 -220
  48. data/spec/support/shared_examples/shared_example_for_streaming_clients.rb +0 -20
  49. data/spec/support/shared_examples/shared_example_for_test_servers.rb +0 -16
  50. data/tests/authorization_header_tests.rb +0 -27
  51. data/tests/bad_tests.rb +0 -69
  52. data/tests/basic_tests.rb +0 -351
  53. data/tests/batch_requests.rb +0 -133
  54. data/tests/complete_responses.rb +0 -31
  55. data/tests/data/127.0.0.1.cert.crt +0 -17
  56. data/tests/data/127.0.0.1.cert.key +0 -28
  57. data/tests/data/excon.cert.crt +0 -17
  58. data/tests/data/excon.cert.key +0 -28
  59. data/tests/data/xs +0 -1
  60. data/tests/error_tests.rb +0 -145
  61. data/tests/header_tests.rb +0 -119
  62. data/tests/instrumentors/logging_instrumentor_tests.rb +0 -28
  63. data/tests/middleware_tests.rb +0 -27
  64. data/tests/middlewares/canned_response_tests.rb +0 -34
  65. data/tests/middlewares/capture_cookies_tests.rb +0 -34
  66. data/tests/middlewares/decompress_tests.rb +0 -157
  67. data/tests/middlewares/escape_path_tests.rb +0 -36
  68. data/tests/middlewares/idempotent_tests.rb +0 -245
  69. data/tests/middlewares/instrumentation_tests.rb +0 -315
  70. data/tests/middlewares/mock_tests.rb +0 -304
  71. data/tests/middlewares/redirect_follower_tests.rb +0 -112
  72. data/tests/pipeline_tests.rb +0 -40
  73. data/tests/proxy_tests.rb +0 -306
  74. data/tests/query_string_tests.rb +0 -87
  75. data/tests/rackups/basic.rb +0 -41
  76. data/tests/rackups/basic.ru +0 -3
  77. data/tests/rackups/basic_auth.ru +0 -14
  78. data/tests/rackups/deflater.ru +0 -4
  79. data/tests/rackups/proxy.ru +0 -18
  80. data/tests/rackups/query_string.ru +0 -13
  81. data/tests/rackups/redirecting.ru +0 -23
  82. data/tests/rackups/redirecting_with_cookie.ru +0 -40
  83. data/tests/rackups/request_headers.ru +0 -15
  84. data/tests/rackups/request_methods.ru +0 -21
  85. data/tests/rackups/response_header.ru +0 -18
  86. data/tests/rackups/ssl.ru +0 -16
  87. data/tests/rackups/ssl_mismatched_cn.ru +0 -15
  88. data/tests/rackups/ssl_verify_peer.ru +0 -16
  89. data/tests/rackups/streaming.ru +0 -30
  90. data/tests/rackups/thread_safety.ru +0 -17
  91. data/tests/rackups/timeout.ru +0 -14
  92. data/tests/rackups/webrick_patch.rb +0 -34
  93. data/tests/request_headers_tests.rb +0 -21
  94. data/tests/request_method_tests.rb +0 -47
  95. data/tests/request_tests.rb +0 -58
  96. data/tests/response_tests.rb +0 -197
  97. data/tests/servers/bad.rb +0 -25
  98. data/tests/servers/eof.rb +0 -17
  99. data/tests/servers/error.rb +0 -20
  100. data/tests/servers/good.rb +0 -342
  101. data/tests/servers/good_ipv4.rb +0 -8
  102. data/tests/servers/good_ipv6.rb +0 -8
  103. data/tests/test_helper.rb +0 -297
  104. data/tests/thread_safety_tests.rb +0 -39
  105. data/tests/timeout_tests.rb +0 -12
  106. 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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