excon 0.62.0 → 0.85.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.
- checksums.yaml +5 -5
- data/LICENSE.md +1 -1
- data/README.md +6 -5
- data/data/cacert.pem +939 -1720
- data/excon.gemspec +17 -2
- data/lib/excon.rb +25 -17
- data/lib/excon/connection.rb +206 -139
- data/lib/excon/constants.rb +38 -13
- data/lib/excon/error.rb +15 -0
- data/lib/excon/headers.rb +4 -3
- data/lib/excon/instrumentors/logging_instrumentor.rb +4 -15
- data/lib/excon/instrumentors/standard_instrumentor.rb +2 -9
- data/lib/excon/middlewares/base.rb +6 -0
- data/lib/excon/middlewares/capture_cookies.rb +1 -1
- data/lib/excon/middlewares/decompress.rb +2 -2
- data/lib/excon/middlewares/expects.rb +7 -1
- data/lib/excon/middlewares/idempotent.rb +20 -3
- data/lib/excon/middlewares/instrumentor.rb +8 -0
- data/lib/excon/middlewares/mock.rb +12 -3
- data/lib/excon/middlewares/redirect_follower.rb +25 -3
- data/lib/excon/middlewares/response_parser.rb +3 -0
- data/lib/excon/pretty_printer.rb +1 -8
- data/lib/excon/response.rb +12 -9
- data/lib/excon/socket.rb +51 -33
- data/lib/excon/ssl_socket.rb +33 -13
- data/lib/excon/test/plugin/server/exec.rb +2 -2
- data/lib/excon/test/server.rb +1 -1
- data/lib/excon/unix_socket.rb +1 -0
- data/lib/excon/utils.rb +58 -5
- data/lib/excon/version.rb +1 -1
- metadata +27 -98
- data/.document +0 -5
- data/.gitignore +0 -13
- data/.rspec +0 -3
- data/.travis.yml +0 -29
- 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 -1083
- 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/requests/basic_spec.rb +0 -40
- data/spec/requests/eof_requests_spec.rb +0 -36
- data/spec/requests/unix_socket_spec.rb +0 -46
- data/spec/spec_helper.rb +0 -24
- data/spec/support/shared_contexts/test_server_context.rb +0 -83
- data/spec/support/shared_examples/shared_example_for_clients.rb +0 -218
- 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 -29
- data/tests/bad_tests.rb +0 -47
- 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 -20
- data/tests/data/127.0.0.1.cert.key +0 -27
- data/tests/data/excon.cert.crt +0 -20
- data/tests/data/excon.cert.key +0 -27
- data/tests/data/xs +0 -1
- data/tests/error_tests.rb +0 -145
- data/tests/header_tests.rb +0 -119
- 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 -206
- 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 -59
- data/tests/response_tests.rb +0 -197
- data/tests/servers/bad.rb +0 -20
- data/tests/servers/eof.rb +0 -17
- data/tests/servers/error.rb +0 -20
- data/tests/servers/good.rb +0 -350
- data/tests/test_helper.rb +0 -306
- data/tests/thread_safety_tests.rb +0 -39
- data/tests/timeout_tests.rb +0 -12
- data/tests/utils_tests.rb +0 -81
data/excon.gemspec
CHANGED
|
@@ -11,8 +11,13 @@ Gem::Specification.new do |s|
|
|
|
11
11
|
s.license = 'MIT'
|
|
12
12
|
s.rdoc_options = ["--charset=UTF-8"]
|
|
13
13
|
s.extra_rdoc_files = %w[README.md CONTRIBUTORS.md CONTRIBUTING.md]
|
|
14
|
-
s.files = `git ls-files
|
|
15
|
-
|
|
14
|
+
s.files = `git ls-files -- data/* lib/*`.split("\n") + [
|
|
15
|
+
"CONTRIBUTING.md",
|
|
16
|
+
"CONTRIBUTORS.md",
|
|
17
|
+
"LICENSE.md",
|
|
18
|
+
"README.md",
|
|
19
|
+
"excon.gemspec"
|
|
20
|
+
]
|
|
16
21
|
|
|
17
22
|
s.add_development_dependency('rspec', '>= 3.5.0')
|
|
18
23
|
s.add_development_dependency('activesupport')
|
|
@@ -26,4 +31,14 @@ Gem::Specification.new do |s|
|
|
|
26
31
|
s.add_development_dependency('sinatra-contrib')
|
|
27
32
|
s.add_development_dependency('json', '>= 1.8.5')
|
|
28
33
|
s.add_development_dependency('puma')
|
|
34
|
+
s.add_development_dependency('webrick')
|
|
35
|
+
|
|
36
|
+
s.metadata = {
|
|
37
|
+
'homepage_uri' => 'https://github.com/excon/excon',
|
|
38
|
+
'bug_tracker_uri' => 'https://github.com/excon/excon/issues',
|
|
39
|
+
'changelog_uri' => 'https://github.com/excon/excon/blob/master/changelog.txt',
|
|
40
|
+
'documentation_uri' => 'https://github.com/excon/excon/blob/master/README.md',
|
|
41
|
+
'source_code_uri' => 'https://github.com/excon/excon',
|
|
42
|
+
'wiki_uri' => 'https://github.com/excon/excon/wiki'
|
|
43
|
+
}
|
|
29
44
|
end
|
data/lib/excon.rb
CHANGED
|
@@ -23,11 +23,11 @@ require 'excon/middlewares/instrumentor'
|
|
|
23
23
|
require 'excon/middlewares/mock'
|
|
24
24
|
require 'excon/middlewares/response_parser'
|
|
25
25
|
|
|
26
|
+
require 'excon/error'
|
|
26
27
|
require 'excon/constants'
|
|
27
28
|
require 'excon/utils'
|
|
28
29
|
|
|
29
30
|
require 'excon/connection'
|
|
30
|
-
require 'excon/error'
|
|
31
31
|
require 'excon/headers'
|
|
32
32
|
require 'excon/response'
|
|
33
33
|
require 'excon/middlewares/decompress'
|
|
@@ -61,6 +61,14 @@ module Excon
|
|
|
61
61
|
if $VERBOSE || ENV['EXCON_DEBUG']
|
|
62
62
|
$stderr.puts "[excon][WARNING] #{warning}\n#{ caller.join("\n") }"
|
|
63
63
|
end
|
|
64
|
+
|
|
65
|
+
if @raise_on_warnings
|
|
66
|
+
raise Error::Warning.new(warning)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def set_raise_on_warnings!(should_raise)
|
|
71
|
+
@raise_on_warnings = should_raise
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
# Status of mocking
|
|
@@ -105,9 +113,9 @@ module Excon
|
|
|
105
113
|
|
|
106
114
|
# @see Connection#initialize
|
|
107
115
|
# Initializes a new keep-alive session for a given remote host
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
116
|
+
# @param [String] url The destination URL
|
|
117
|
+
# @param [Hash<Symbol, >] params One or more option params to set on the Connection instance
|
|
118
|
+
# @return [Connection] A new Excon::Connection instance
|
|
111
119
|
def new(url, params = {})
|
|
112
120
|
uri_parser = params[:uri_parser] || defaults[:uri_parser]
|
|
113
121
|
uri = uri_parser.parse(url)
|
|
@@ -135,13 +143,13 @@ module Excon
|
|
|
135
143
|
end
|
|
136
144
|
|
|
137
145
|
# push an additional stub onto the list to check for mock requests
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
def stub(request_params = {}, response_params = nil)
|
|
141
|
-
if method = request_params.delete(:method)
|
|
146
|
+
# @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
|
|
147
|
+
# @param response_params [Hash<Symbol, >] response params to return from matched request or block to call with params
|
|
148
|
+
def stub(request_params = {}, response_params = nil, &block)
|
|
149
|
+
if (method = request_params.delete(:method))
|
|
142
150
|
request_params[:method] = method.to_s.downcase.to_sym
|
|
143
151
|
end
|
|
144
|
-
if url = request_params.delete(:url)
|
|
152
|
+
if (url = request_params.delete(:url))
|
|
145
153
|
uri = URI.parse(url)
|
|
146
154
|
request_params = {
|
|
147
155
|
:host => uri.host,
|
|
@@ -167,7 +175,7 @@ module Excon
|
|
|
167
175
|
if response_params
|
|
168
176
|
raise(ArgumentError.new("stub requires either response_params OR a block"))
|
|
169
177
|
else
|
|
170
|
-
stub = [request_params,
|
|
178
|
+
stub = [request_params, block]
|
|
171
179
|
end
|
|
172
180
|
elsif response_params
|
|
173
181
|
stub = [request_params, response_params]
|
|
@@ -179,10 +187,10 @@ module Excon
|
|
|
179
187
|
end
|
|
180
188
|
|
|
181
189
|
# get a stub matching params or nil
|
|
182
|
-
#
|
|
183
|
-
#
|
|
190
|
+
# @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
|
|
191
|
+
# @return [Hash<Symbol, >] response params to return from matched request or block to call with params
|
|
184
192
|
def stub_for(request_params={})
|
|
185
|
-
if method = request_params.delete(:method)
|
|
193
|
+
if (method = request_params.delete(:method))
|
|
186
194
|
request_params[:method] = method.to_s.downcase.to_sym
|
|
187
195
|
end
|
|
188
196
|
Excon.stubs.each do |stub, response_params|
|
|
@@ -190,7 +198,7 @@ module Excon
|
|
|
190
198
|
headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
|
|
191
199
|
case value = stub[:headers][key]
|
|
192
200
|
when Regexp
|
|
193
|
-
if match = value.match(request_params[:headers][key])
|
|
201
|
+
if (match = value.match(request_params[:headers][key]))
|
|
194
202
|
captures[:headers][key] = match.captures
|
|
195
203
|
end
|
|
196
204
|
match
|
|
@@ -201,7 +209,7 @@ module Excon
|
|
|
201
209
|
non_headers_match = (stub.keys - [:headers]).all? do |key|
|
|
202
210
|
case value = stub[key]
|
|
203
211
|
when Regexp
|
|
204
|
-
if match = value.match(request_params[key])
|
|
212
|
+
if (match = value.match(request_params[key]))
|
|
205
213
|
captures[key] = match.captures
|
|
206
214
|
end
|
|
207
215
|
match
|
|
@@ -228,8 +236,8 @@ module Excon
|
|
|
228
236
|
end
|
|
229
237
|
|
|
230
238
|
# remove first/oldest stub matching request_params
|
|
231
|
-
#
|
|
232
|
-
#
|
|
239
|
+
# @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
|
|
240
|
+
# @return [Hash<Symbol, >] response params from deleted stub
|
|
233
241
|
def unstub(request_params = {})
|
|
234
242
|
stub = stub_for(request_params)
|
|
235
243
|
Excon.stubs.delete_at(Excon.stubs.index(stub))
|
data/lib/excon/connection.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'ipaddr'
|
|
3
|
+
|
|
2
4
|
module Excon
|
|
3
5
|
class Connection
|
|
4
6
|
include Utils
|
|
@@ -38,27 +40,27 @@ module Excon
|
|
|
38
40
|
end
|
|
39
41
|
end
|
|
40
42
|
def logger=(logger)
|
|
41
|
-
Excon::LoggingInstrumentor.logger = logger
|
|
42
43
|
@data[:instrumentor] = Excon::LoggingInstrumentor
|
|
44
|
+
@data[:logger] = logger
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
# Initializes a new Connection instance
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
48
|
+
# @param [Hash<Symbol, >] params One or more optional params
|
|
49
|
+
# @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
|
|
50
|
+
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request
|
|
51
|
+
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String. IPv6 addresses must be wrapped (e.g. [::1]). See URI#host.
|
|
52
|
+
# @option params [String] :hostname Same as host, but usable for socket connections. IPv6 addresses must not be wrapped (e.g. ::1). See URI#hostname.
|
|
53
|
+
# @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request
|
|
54
|
+
# @option params [Fixnum] :port The port on which to connect, to the destination host
|
|
55
|
+
# @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
|
|
56
|
+
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
|
|
57
|
+
# @option params [String] :socket The path to the unix socket (required for 'unix://' connections)
|
|
58
|
+
# @option params [String] :ciphers Only use the specified SSL/TLS cipher suites; use OpenSSL cipher spec format e.g. 'HIGH:!aNULL:!3DES' or 'AES256-SHA:DES-CBC3-SHA'
|
|
59
|
+
# @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
|
|
60
|
+
# @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
|
|
61
|
+
# @option params [Fixnum] :retry_interval Set how long to wait between retries. (Default 0)
|
|
62
|
+
# @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
|
|
63
|
+
# @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
|
|
62
64
|
def initialize(params = {})
|
|
63
65
|
@data = Excon.defaults.dup
|
|
64
66
|
# merge does not deep-dup, so make sure headers is not the original
|
|
@@ -67,12 +69,17 @@ module Excon
|
|
|
67
69
|
# the same goes for :middlewares
|
|
68
70
|
@data[:middlewares] = @data[:middlewares].dup
|
|
69
71
|
|
|
70
|
-
params = validate_params(:connection, params)
|
|
71
72
|
@data.merge!(params)
|
|
73
|
+
validate_params(:connection, @data, @data[:middlewares])
|
|
74
|
+
|
|
75
|
+
if @data.key?(:host) && !@data.key?(:hostname)
|
|
76
|
+
Excon.display_warning('hostname is missing! For IPv6 support, provide both host and hostname: Excon::Connection#new(:host => uri.host, :hostname => uri.hostname, ...).')
|
|
77
|
+
@data[:hostname] = @data[:host]
|
|
78
|
+
end
|
|
72
79
|
|
|
73
80
|
setup_proxy
|
|
74
81
|
|
|
75
|
-
if
|
|
82
|
+
if ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
|
|
76
83
|
@data[:instrumentor] = Excon::StandardInstrumentor
|
|
77
84
|
end
|
|
78
85
|
|
|
@@ -108,7 +115,7 @@ module Excon
|
|
|
108
115
|
# we already have data from a middleware, so bail
|
|
109
116
|
return datum
|
|
110
117
|
else
|
|
111
|
-
socket.data = datum
|
|
118
|
+
socket(datum).data = datum
|
|
112
119
|
# start with "METHOD /path"
|
|
113
120
|
request = datum[:method].to_s.upcase + ' '
|
|
114
121
|
if datum[:proxy] && datum[:scheme] != HTTPS
|
|
@@ -137,31 +144,25 @@ module Excon
|
|
|
137
144
|
end
|
|
138
145
|
|
|
139
146
|
# add headers to request
|
|
140
|
-
datum[:headers]
|
|
141
|
-
[values].flatten.each do |value|
|
|
142
|
-
request << key.to_s << ': ' << value.to_s << CR_NL
|
|
143
|
-
end
|
|
144
|
-
end
|
|
147
|
+
request << Utils.headers_hash_to_s(datum[:headers])
|
|
145
148
|
|
|
146
149
|
# add additional "\r\n" to indicate end of headers
|
|
147
150
|
request << CR_NL
|
|
148
151
|
|
|
149
152
|
if datum.has_key?(:request_block)
|
|
150
|
-
socket.write(request) # write out request + headers
|
|
153
|
+
socket(datum).write(request) # write out request + headers
|
|
151
154
|
while true # write out body with chunked encoding
|
|
152
155
|
chunk = datum[:request_block].call
|
|
153
|
-
|
|
154
|
-
chunk.force_encoding('BINARY')
|
|
155
|
-
end
|
|
156
|
+
chunk = binary_encode(chunk)
|
|
156
157
|
if chunk.length > 0
|
|
157
|
-
socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
|
|
158
|
+
socket(datum).write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
|
|
158
159
|
else
|
|
159
|
-
socket.write(String.new("0#{CR_NL}#{CR_NL}"))
|
|
160
|
+
socket(datum).write(String.new("0#{CR_NL}#{CR_NL}"))
|
|
160
161
|
break
|
|
161
162
|
end
|
|
162
163
|
end
|
|
163
164
|
elsif body.nil?
|
|
164
|
-
socket.write(request) # write out request + headers
|
|
165
|
+
socket(datum).write(request) # write out request + headers
|
|
165
166
|
else # write out body
|
|
166
167
|
if body.respond_to?(:binmode) && !body.is_a?(StringIO)
|
|
167
168
|
body.binmode
|
|
@@ -171,27 +172,23 @@ module Excon
|
|
|
171
172
|
end
|
|
172
173
|
|
|
173
174
|
# if request + headers is less than chunk size, fill with body
|
|
174
|
-
|
|
175
|
-
request.force_encoding('BINARY')
|
|
176
|
-
end
|
|
175
|
+
request = binary_encode(request)
|
|
177
176
|
chunk = body.read([datum[:chunk_size] - request.length, 0].max)
|
|
178
177
|
if chunk
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
end
|
|
182
|
-
socket.write(request << chunk)
|
|
178
|
+
chunk = binary_encode(chunk)
|
|
179
|
+
socket(datum).write(request << chunk)
|
|
183
180
|
else
|
|
184
|
-
socket.write(request) # write out request + headers
|
|
181
|
+
socket(datum).write(request) # write out request + headers
|
|
185
182
|
end
|
|
186
183
|
|
|
187
|
-
while chunk = body.read(datum[:chunk_size])
|
|
188
|
-
socket.write(chunk)
|
|
184
|
+
while (chunk = body.read(datum[:chunk_size]))
|
|
185
|
+
socket(datum).write(chunk)
|
|
189
186
|
end
|
|
190
187
|
end
|
|
191
188
|
end
|
|
192
189
|
rescue => error
|
|
193
190
|
case error
|
|
194
|
-
when Excon::Errors::StubNotFound, Excon::Errors::Timeout
|
|
191
|
+
when Excon::Errors::InvalidHeaderKey, Excon::Errors::InvalidHeaderValue, Excon::Errors::StubNotFound, Excon::Errors::Timeout
|
|
195
192
|
raise(error)
|
|
196
193
|
else
|
|
197
194
|
raise_socket_error(error)
|
|
@@ -216,18 +213,24 @@ module Excon
|
|
|
216
213
|
end
|
|
217
214
|
|
|
218
215
|
# Sends the supplied request to the destination host.
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
216
|
+
# @yield [chunk] @see Response#self.parse
|
|
217
|
+
# @param [Hash<Symbol, >] params One or more optional params, override defaults set in Connection.new
|
|
218
|
+
# @option params [String] :body text to be sent over a socket
|
|
219
|
+
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request
|
|
220
|
+
# @option params [String] :path appears after 'scheme://host:port/'
|
|
221
|
+
# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
|
|
225
222
|
def request(params={}, &block)
|
|
226
|
-
params = validate_params(:request, params)
|
|
227
223
|
# @data has defaults, merge in new params to override
|
|
228
224
|
datum = @data.merge(params)
|
|
229
225
|
datum[:headers] = @data[:headers].merge(datum[:headers] || {})
|
|
230
226
|
|
|
227
|
+
validate_params(:request, params, datum[:middlewares])
|
|
228
|
+
# If the user passed in new middleware, we want to validate that the original connection parameters
|
|
229
|
+
# are still valid with the provided middleware.
|
|
230
|
+
if params[:middlewares]
|
|
231
|
+
validate_params(:connection, @data, datum[:middlewares])
|
|
232
|
+
end
|
|
233
|
+
|
|
231
234
|
if datum[:user] || datum[:password]
|
|
232
235
|
user, pass = Utils.unescape_uri(datum[:user].to_s), Utils.unescape_uri(datum[:password].to_s)
|
|
233
236
|
datum[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
|
|
@@ -238,7 +241,12 @@ module Excon
|
|
|
238
241
|
else
|
|
239
242
|
datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
|
|
240
243
|
end
|
|
241
|
-
|
|
244
|
+
|
|
245
|
+
# RFC 7230, section 5.4, states that the Host header SHOULD be the first one # to be present.
|
|
246
|
+
# Some web servers will reject the request if it comes too late, so let's hoist it to the top.
|
|
247
|
+
if (host = datum[:headers].delete('Host'))
|
|
248
|
+
datum[:headers] = { 'Host' => host }.merge(datum[:headers])
|
|
249
|
+
end
|
|
242
250
|
|
|
243
251
|
# if path is empty or doesn't start with '/', insert one
|
|
244
252
|
unless datum[:path][0, 1] == '/'
|
|
@@ -247,11 +255,16 @@ module Excon
|
|
|
247
255
|
|
|
248
256
|
if block_given?
|
|
249
257
|
Excon.display_warning('Excon requests with a block are deprecated, pass :response_block instead.')
|
|
250
|
-
datum[:response_block] =
|
|
258
|
+
datum[:response_block] = block
|
|
251
259
|
end
|
|
252
260
|
|
|
253
261
|
datum[:connection] = self
|
|
254
262
|
|
|
263
|
+
# cleanup data left behind on persistent connection after interrupt
|
|
264
|
+
if datum[:persistent] && !@persistent_socket_reusable
|
|
265
|
+
reset
|
|
266
|
+
end
|
|
267
|
+
|
|
255
268
|
datum[:stack] = datum[:middlewares].map do |middleware|
|
|
256
269
|
lambda {|stack| middleware.new(stack)}
|
|
257
270
|
end.reverse.inject(self) do |middlewares, middleware|
|
|
@@ -260,10 +273,12 @@ module Excon
|
|
|
260
273
|
datum = datum[:stack].request_call(datum)
|
|
261
274
|
|
|
262
275
|
unless datum[:pipeline]
|
|
276
|
+
@persistent_socket_reusable = false
|
|
263
277
|
datum = response(datum)
|
|
278
|
+
@persistent_socket_reusable = true
|
|
264
279
|
|
|
265
280
|
if datum[:persistent]
|
|
266
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
|
281
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
|
|
267
282
|
if datum[:response][:headers][key].casecmp('close') == 0
|
|
268
283
|
reset
|
|
269
284
|
end
|
|
@@ -278,6 +293,10 @@ module Excon
|
|
|
278
293
|
end
|
|
279
294
|
rescue => error
|
|
280
295
|
reset
|
|
296
|
+
|
|
297
|
+
# If we didn't get far enough to initialize datum and the middleware stack, just raise
|
|
298
|
+
raise error if !datum
|
|
299
|
+
|
|
281
300
|
datum[:error] = error
|
|
282
301
|
if datum[:stack]
|
|
283
302
|
datum[:stack].error_call(datum)
|
|
@@ -287,7 +306,7 @@ module Excon
|
|
|
287
306
|
end
|
|
288
307
|
|
|
289
308
|
# Sends the supplied requests to the destination host using pipelining.
|
|
290
|
-
#
|
|
309
|
+
# @param pipeline_params [Array<Hash>] An array of one or more optional params, override defaults set in Connection.new, see #request for details
|
|
291
310
|
def requests(pipeline_params)
|
|
292
311
|
pipeline_params.each {|params| params.merge!(:pipeline => true, :persistent => true) }
|
|
293
312
|
pipeline_params.last.merge!(:persistent => @data[:persistent])
|
|
@@ -299,7 +318,7 @@ module Excon
|
|
|
299
318
|
end
|
|
300
319
|
|
|
301
320
|
if @data[:persistent]
|
|
302
|
-
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
|
321
|
+
if (key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
|
|
303
322
|
if responses.last[:headers][key].casecmp('close') == 0
|
|
304
323
|
reset
|
|
305
324
|
end
|
|
@@ -314,7 +333,7 @@ module Excon
|
|
|
314
333
|
# Sends the supplied requests to the destination host using pipelining in
|
|
315
334
|
# batches of @limit [Numeric] requests. This is your soft file descriptor
|
|
316
335
|
# limit by default, typically 256.
|
|
317
|
-
#
|
|
336
|
+
# @param pipeline_params [Array<Hash>] An array of one or more optional params, override defaults set in Connection.new, see #request for details
|
|
318
337
|
def batch_requests(pipeline_params, limit = nil)
|
|
319
338
|
limit ||= Process.respond_to?(:getrlimit) ? Process.getrlimit(:NOFILE).first : 256
|
|
320
339
|
responses = []
|
|
@@ -327,9 +346,10 @@ module Excon
|
|
|
327
346
|
end
|
|
328
347
|
|
|
329
348
|
def reset
|
|
330
|
-
if old_socket = sockets.delete(@socket_key)
|
|
349
|
+
if (old_socket = sockets.delete(@socket_key))
|
|
331
350
|
old_socket.close rescue nil
|
|
332
351
|
end
|
|
352
|
+
@persistent_socket_reusable = true
|
|
333
353
|
end
|
|
334
354
|
|
|
335
355
|
# Generate HTTP request verb methods
|
|
@@ -355,15 +375,7 @@ module Excon
|
|
|
355
375
|
vars = instance_variables.inject({}) do |accum, var|
|
|
356
376
|
accum.merge!(var.to_sym => instance_variable_get(var))
|
|
357
377
|
end
|
|
358
|
-
|
|
359
|
-
vars[:'@data'] = vars[:'@data'].dup
|
|
360
|
-
vars[:'@data'][:headers] = vars[:'@data'][:headers].dup
|
|
361
|
-
vars[:'@data'][:headers]['Authorization'] = REDACTED
|
|
362
|
-
end
|
|
363
|
-
if vars[:'@data'][:password]
|
|
364
|
-
vars[:'@data'] = vars[:'@data'].dup
|
|
365
|
-
vars[:'@data'][:password] = REDACTED
|
|
366
|
-
end
|
|
378
|
+
vars[:'@data'] = Utils.redact(vars[:'@data'])
|
|
367
379
|
inspection = '#<Excon::Connection:'
|
|
368
380
|
inspection += (object_id << 1).to_s(16)
|
|
369
381
|
vars.each do |key, value|
|
|
@@ -373,6 +385,10 @@ module Excon
|
|
|
373
385
|
inspection
|
|
374
386
|
end
|
|
375
387
|
|
|
388
|
+
def valid_request_keys(middlewares)
|
|
389
|
+
valid_middleware_keys(middlewares) + Excon::VALID_REQUEST_KEYS
|
|
390
|
+
end
|
|
391
|
+
|
|
376
392
|
private
|
|
377
393
|
|
|
378
394
|
def detect_content_length(body)
|
|
@@ -387,60 +403,77 @@ module Excon
|
|
|
387
403
|
end
|
|
388
404
|
end
|
|
389
405
|
|
|
390
|
-
def
|
|
406
|
+
def valid_middleware_keys(middlewares)
|
|
407
|
+
middlewares.flat_map do |middleware|
|
|
408
|
+
if middleware.respond_to?(:valid_parameter_keys)
|
|
409
|
+
middleware.valid_parameter_keys
|
|
410
|
+
else
|
|
411
|
+
Excon.display_warning(
|
|
412
|
+
"Excon middleware #{middleware} does not define #valid_parameter_keys"
|
|
413
|
+
)
|
|
414
|
+
[]
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def validate_params(validation, params, middlewares)
|
|
391
420
|
valid_keys = case validation
|
|
392
421
|
when :connection
|
|
393
|
-
Excon::VALID_CONNECTION_KEYS
|
|
422
|
+
valid_middleware_keys(middlewares) + Excon::VALID_CONNECTION_KEYS
|
|
394
423
|
when :request
|
|
395
|
-
|
|
424
|
+
valid_request_keys(middlewares)
|
|
425
|
+
else
|
|
426
|
+
raise ArgumentError.new("Invalid validation type '#{validation}'")
|
|
396
427
|
end
|
|
428
|
+
|
|
397
429
|
invalid_keys = params.keys - valid_keys
|
|
398
430
|
unless invalid_keys.empty?
|
|
399
431
|
Excon.display_warning("Invalid Excon #{validation} keys: #{invalid_keys.map(&:inspect).join(', ')}")
|
|
400
|
-
# FIXME: for now, just warn, don't mutate, give things (ie fog) a chance to catch up
|
|
401
|
-
#params = params.dup
|
|
402
|
-
#invalid_keys.each {|key| params.delete(key) }
|
|
403
|
-
end
|
|
404
432
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
433
|
+
if validation == :request
|
|
434
|
+
deprecated_keys = invalid_keys & Excon::DEPRECATED_VALID_REQUEST_KEYS.keys
|
|
435
|
+
mw_msg = deprecated_keys.map do |k|
|
|
436
|
+
"#{k}: #{Excon::DEPRECATED_VALID_REQUEST_KEYS[k]}"
|
|
437
|
+
end.join(', ')
|
|
438
|
+
Excon.display_warning(
|
|
439
|
+
"The following request keys are only valid with the associated middleware: #{mw_msg}"
|
|
440
|
+
)
|
|
441
|
+
end
|
|
408
442
|
end
|
|
409
|
-
|
|
410
|
-
params
|
|
411
443
|
end
|
|
412
444
|
|
|
413
445
|
def response(datum={})
|
|
414
446
|
datum[:stack].response_call(datum)
|
|
415
447
|
rescue => error
|
|
416
448
|
case error
|
|
417
|
-
when Excon::Errors::HTTPStatusError, Excon::Errors::Timeout
|
|
449
|
+
when Excon::Errors::HTTPStatusError, Excon::Errors::Timeout, Excon::Errors::TooManyRedirects
|
|
418
450
|
raise(error)
|
|
419
451
|
else
|
|
420
452
|
raise_socket_error(error)
|
|
421
453
|
end
|
|
422
454
|
end
|
|
423
455
|
|
|
424
|
-
def socket
|
|
425
|
-
unix_proxy =
|
|
426
|
-
sockets[@socket_key] ||= if
|
|
427
|
-
Excon::UnixSocket.new(
|
|
428
|
-
elsif
|
|
429
|
-
Excon::SSLSocket.new(
|
|
456
|
+
def socket(datum = @data)
|
|
457
|
+
unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
|
|
458
|
+
sockets[@socket_key] ||= if datum[:scheme] == UNIX || unix_proxy
|
|
459
|
+
Excon::UnixSocket.new(datum)
|
|
460
|
+
elsif datum[:ssl_uri_schemes].include?(datum[:scheme])
|
|
461
|
+
Excon::SSLSocket.new(datum)
|
|
430
462
|
else
|
|
431
|
-
Excon::Socket.new(
|
|
463
|
+
Excon::Socket.new(datum)
|
|
432
464
|
end
|
|
433
465
|
end
|
|
434
466
|
|
|
435
467
|
def sockets
|
|
436
468
|
@_excon_sockets ||= {}
|
|
469
|
+
@_excon_sockets.compare_by_identity
|
|
437
470
|
|
|
438
471
|
if @data[:thread_safe_sockets]
|
|
439
472
|
# In a multi-threaded world, if the same connection is used by multiple
|
|
440
473
|
# threads at the same time to connect to the same destination, they may
|
|
441
474
|
# stomp on each other's sockets. This ensures every thread gets their
|
|
442
475
|
# own socket cache, within the context of a single connection.
|
|
443
|
-
@_excon_sockets[Thread.current
|
|
476
|
+
@_excon_sockets[Thread.current] ||= {}
|
|
444
477
|
else
|
|
445
478
|
@_excon_sockets
|
|
446
479
|
end
|
|
@@ -454,6 +487,47 @@ module Excon
|
|
|
454
487
|
end
|
|
455
488
|
end
|
|
456
489
|
|
|
490
|
+
def proxy_match_host_port(host, port)
|
|
491
|
+
host_match = if host.is_a? IPAddr
|
|
492
|
+
begin
|
|
493
|
+
host.include? @data[:host]
|
|
494
|
+
rescue IPAddr::Error
|
|
495
|
+
false
|
|
496
|
+
end
|
|
497
|
+
else
|
|
498
|
+
/(^|\.)#{host}$/.match(@data[:host])
|
|
499
|
+
end
|
|
500
|
+
host_match && (port.nil? || port.to_i == @data[:port])
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def proxy_from_env
|
|
504
|
+
if (no_proxy_env = ENV['no_proxy'] || ENV['NO_PROXY'])
|
|
505
|
+
no_proxy_list = no_proxy_env.scan(/\s*(?:\[([\dA-Fa-f:\/]+)\]|\*?\.?([^\s,:]+))(?::(\d+))?\s*/i).map { |e|
|
|
506
|
+
if e[0]
|
|
507
|
+
begin
|
|
508
|
+
[IPAddr.new(e[0]), e[2]]
|
|
509
|
+
rescue IPAddr::Error
|
|
510
|
+
nil
|
|
511
|
+
end
|
|
512
|
+
else
|
|
513
|
+
begin
|
|
514
|
+
[IPAddr.new(e[1]), e[2]]
|
|
515
|
+
rescue IPAddr::Error
|
|
516
|
+
[e[1], e[2]]
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
}.reject { |e| e.nil? || e[0].nil? }
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
unless no_proxy_env && no_proxy_list.index { |h| proxy_match_host_port(h[0], h[1]) }
|
|
523
|
+
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
|
|
524
|
+
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
|
|
525
|
+
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
|
|
526
|
+
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
457
531
|
def setup_proxy
|
|
458
532
|
if @data[:disable_proxy]
|
|
459
533
|
if @data[:proxy]
|
|
@@ -462,64 +536,57 @@ module Excon
|
|
|
462
536
|
return
|
|
463
537
|
end
|
|
464
538
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
539
|
+
return if @data[:scheme] == UNIX
|
|
540
|
+
|
|
541
|
+
proxy_from_env
|
|
542
|
+
|
|
543
|
+
case @data[:proxy]
|
|
544
|
+
when nil
|
|
545
|
+
@data.delete(:proxy)
|
|
546
|
+
when ''
|
|
547
|
+
@data.delete(:proxy)
|
|
548
|
+
when Hash
|
|
549
|
+
# no processing needed
|
|
550
|
+
when String, URI
|
|
551
|
+
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
|
|
552
|
+
@data[:proxy] = {
|
|
553
|
+
:host => uri.host,
|
|
554
|
+
:hostname => uri.hostname,
|
|
555
|
+
# path is only sensible for a Unix socket proxy
|
|
556
|
+
:path => uri.scheme == UNIX ? uri.path : nil,
|
|
557
|
+
:port => uri.port,
|
|
558
|
+
:scheme => uri.scheme,
|
|
559
|
+
}
|
|
560
|
+
if uri.password
|
|
561
|
+
@data[:proxy][:password] = uri.password
|
|
468
562
|
end
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
|
|
472
|
-
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
|
|
473
|
-
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
|
|
474
|
-
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
|
475
|
-
end
|
|
563
|
+
if uri.user
|
|
564
|
+
@data[:proxy][:user] = uri.user
|
|
476
565
|
end
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
# no processing needed
|
|
485
|
-
when String, URI
|
|
486
|
-
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
|
|
487
|
-
@data[:proxy] = {
|
|
488
|
-
:host => uri.host,
|
|
489
|
-
:hostname => uri.hostname,
|
|
490
|
-
# path is only sensible for a Unix socket proxy
|
|
491
|
-
:path => uri.scheme == UNIX ? uri.path : nil,
|
|
492
|
-
:port => uri.port,
|
|
493
|
-
:scheme => uri.scheme,
|
|
494
|
-
}
|
|
495
|
-
if uri.password
|
|
496
|
-
@data[:proxy][:password] = uri.password
|
|
497
|
-
end
|
|
498
|
-
if uri.user
|
|
499
|
-
@data[:proxy][:user] = uri.user
|
|
500
|
-
end
|
|
501
|
-
if @data[:proxy][:scheme] == UNIX
|
|
502
|
-
if @data[:proxy][:host]
|
|
503
|
-
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
|
|
504
|
-
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
|
|
505
|
-
end
|
|
506
|
-
else
|
|
507
|
-
unless uri.host && uri.port && uri.scheme
|
|
508
|
-
raise Excon::Errors::ProxyParse, "Proxy is invalid"
|
|
509
|
-
end
|
|
566
|
+
if @data[:ssl_proxy_headers] && !@data[:ssl_uri_schemes].include?(@data[:scheme])
|
|
567
|
+
raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
|
|
568
|
+
end
|
|
569
|
+
if @data[:proxy][:scheme] == UNIX
|
|
570
|
+
if @data[:proxy][:host]
|
|
571
|
+
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
|
|
572
|
+
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
|
|
510
573
|
end
|
|
511
574
|
else
|
|
512
|
-
|
|
575
|
+
unless uri.host && uri.port && uri.scheme
|
|
576
|
+
raise Excon::Errors::ProxyParse, "Proxy is invalid"
|
|
577
|
+
end
|
|
513
578
|
end
|
|
579
|
+
else
|
|
580
|
+
raise Excon::Errors::ProxyParse, "Proxy is invalid"
|
|
581
|
+
end
|
|
514
582
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
end
|
|
583
|
+
if @data.has_key?(:proxy) && @data[:scheme] == 'http'
|
|
584
|
+
@data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
|
|
585
|
+
# https credentials happen in handshake
|
|
586
|
+
if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
|
|
587
|
+
user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
|
|
588
|
+
auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
|
|
589
|
+
@data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
|
|
523
590
|
end
|
|
524
591
|
end
|
|
525
592
|
end
|