excon 0.75.0 → 0.80.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 +4 -4
- data/README.md +1 -1
- data/data/cacert.pem +252 -504
- data/excon.gemspec +2 -1
- data/lib/excon.rb +5 -5
- data/lib/excon/connection.rb +13 -6
- data/lib/excon/constants.rb +1 -0
- data/lib/excon/headers.rb +4 -3
- data/lib/excon/instrumentors/logging_instrumentor.rb +1 -1
- data/lib/excon/instrumentors/standard_instrumentor.rb +1 -1
- data/lib/excon/middlewares/capture_cookies.rb +1 -1
- data/lib/excon/middlewares/decompress.rb +2 -2
- data/lib/excon/middlewares/expects.rb +1 -1
- data/lib/excon/middlewares/mock.rb +2 -2
- data/lib/excon/middlewares/redirect_follower.rb +1 -1
- data/lib/excon/response.rb +11 -8
- data/lib/excon/socket.rb +15 -24
- data/lib/excon/ssl_socket.rb +13 -12
- 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 +2 -2
- data/lib/excon/version.rb +1 -1
- metadata +20 -6
data/excon.gemspec
CHANGED
@@ -11,7 +11,7 @@ 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 --
|
14
|
+
s.files = `git ls-files -- data/* lib/*`.split("\n") + [
|
15
15
|
"CONTRIBUTING.md",
|
16
16
|
"CONTRIBUTORS.md",
|
17
17
|
"LICENSE.md",
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
|
|
31
31
|
s.add_development_dependency('sinatra-contrib')
|
32
32
|
s.add_development_dependency('json', '>= 1.8.5')
|
33
33
|
s.add_development_dependency('puma')
|
34
|
+
s.add_development_dependency('webrick')
|
34
35
|
|
35
36
|
s.metadata = {
|
36
37
|
'homepage_uri' => 'https://github.com/excon/excon',
|
data/lib/excon.rb
CHANGED
@@ -146,10 +146,10 @@ module Excon
|
|
146
146
|
# @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
|
147
147
|
# @param response_params [Hash<Symbol, >] response params to return from matched request or block to call with params
|
148
148
|
def stub(request_params = {}, response_params = nil, &block)
|
149
|
-
if method = request_params.delete(:method)
|
149
|
+
if (method = request_params.delete(:method))
|
150
150
|
request_params[:method] = method.to_s.downcase.to_sym
|
151
151
|
end
|
152
|
-
if url = request_params.delete(:url)
|
152
|
+
if (url = request_params.delete(:url))
|
153
153
|
uri = URI.parse(url)
|
154
154
|
request_params = {
|
155
155
|
:host => uri.host,
|
@@ -190,7 +190,7 @@ module Excon
|
|
190
190
|
# @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
|
191
191
|
# @return [Hash<Symbol, >] response params to return from matched request or block to call with params
|
192
192
|
def stub_for(request_params={})
|
193
|
-
if method = request_params.delete(:method)
|
193
|
+
if (method = request_params.delete(:method))
|
194
194
|
request_params[:method] = method.to_s.downcase.to_sym
|
195
195
|
end
|
196
196
|
Excon.stubs.each do |stub, response_params|
|
@@ -198,7 +198,7 @@ module Excon
|
|
198
198
|
headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
|
199
199
|
case value = stub[:headers][key]
|
200
200
|
when Regexp
|
201
|
-
if match = value.match(request_params[:headers][key])
|
201
|
+
if (match = value.match(request_params[:headers][key]))
|
202
202
|
captures[:headers][key] = match.captures
|
203
203
|
end
|
204
204
|
match
|
@@ -209,7 +209,7 @@ module Excon
|
|
209
209
|
non_headers_match = (stub.keys - [:headers]).all? do |key|
|
210
210
|
case value = stub[key]
|
211
211
|
when Regexp
|
212
|
-
if match = value.match(request_params[key])
|
212
|
+
if (match = value.match(request_params[key]))
|
213
213
|
captures[key] = match.captures
|
214
214
|
end
|
215
215
|
match
|
data/lib/excon/connection.rb
CHANGED
@@ -79,7 +79,7 @@ module Excon
|
|
79
79
|
|
80
80
|
setup_proxy
|
81
81
|
|
82
|
-
if
|
82
|
+
if ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
|
83
83
|
@data[:instrumentor] = Excon::StandardInstrumentor
|
84
84
|
end
|
85
85
|
|
@@ -191,7 +191,7 @@ module Excon
|
|
191
191
|
socket.write(request) # write out request + headers
|
192
192
|
end
|
193
193
|
|
194
|
-
while chunk = body.read(datum[:chunk_size])
|
194
|
+
while (chunk = body.read(datum[:chunk_size]))
|
195
195
|
socket.write(chunk)
|
196
196
|
end
|
197
197
|
end
|
@@ -252,6 +252,12 @@ module Excon
|
|
252
252
|
datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
|
253
253
|
end
|
254
254
|
|
255
|
+
# RFC 7230, section 5.4, states that the Host header SHOULD be the first one # to be present.
|
256
|
+
# Some web servers will reject the request if it comes too late, so let's hoist it to the top.
|
257
|
+
if (host = datum[:headers].delete('Host'))
|
258
|
+
datum[:headers] = { 'Host' => host }.merge(datum[:headers])
|
259
|
+
end
|
260
|
+
|
255
261
|
# if path is empty or doesn't start with '/', insert one
|
256
262
|
unless datum[:path][0, 1] == '/'
|
257
263
|
datum[:path] = datum[:path].dup.insert(0, '/')
|
@@ -282,7 +288,7 @@ module Excon
|
|
282
288
|
@persistent_socket_reusable = true
|
283
289
|
|
284
290
|
if datum[:persistent]
|
285
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
291
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
|
286
292
|
if datum[:response][:headers][key].casecmp('close') == 0
|
287
293
|
reset
|
288
294
|
end
|
@@ -322,7 +328,7 @@ module Excon
|
|
322
328
|
end
|
323
329
|
|
324
330
|
if @data[:persistent]
|
325
|
-
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
331
|
+
if (key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
|
326
332
|
if responses.last[:headers][key].casecmp('close') == 0
|
327
333
|
reset
|
328
334
|
end
|
@@ -350,7 +356,7 @@ module Excon
|
|
350
356
|
end
|
351
357
|
|
352
358
|
def reset
|
353
|
-
if old_socket = sockets.delete(@socket_key)
|
359
|
+
if (old_socket = sockets.delete(@socket_key))
|
354
360
|
old_socket.close rescue nil
|
355
361
|
end
|
356
362
|
@persistent_socket_reusable = true
|
@@ -470,13 +476,14 @@ module Excon
|
|
470
476
|
|
471
477
|
def sockets
|
472
478
|
@_excon_sockets ||= {}
|
479
|
+
@_excon_sockets.compare_by_identity
|
473
480
|
|
474
481
|
if @data[:thread_safe_sockets]
|
475
482
|
# In a multi-threaded world, if the same connection is used by multiple
|
476
483
|
# threads at the same time to connect to the same destination, they may
|
477
484
|
# stomp on each other's sockets. This ensures every thread gets their
|
478
485
|
# own socket cache, within the context of a single connection.
|
479
|
-
@_excon_sockets[Thread.current
|
486
|
+
@_excon_sockets[Thread.current] ||= {}
|
480
487
|
else
|
481
488
|
@_excon_sockets
|
482
489
|
end
|
data/lib/excon/constants.rb
CHANGED
data/lib/excon/headers.rb
CHANGED
@@ -22,6 +22,7 @@ module Excon
|
|
22
22
|
alias_method :raw_values_at, :values_at
|
23
23
|
|
24
24
|
def initialize
|
25
|
+
super
|
25
26
|
@downcased = {}
|
26
27
|
end
|
27
28
|
|
@@ -29,11 +30,11 @@ module Excon
|
|
29
30
|
@downcased[key.to_s.downcase]
|
30
31
|
end
|
31
32
|
|
32
|
-
alias_method :[]=, :store
|
33
33
|
def []=(key, value)
|
34
34
|
raw_writer(key, value)
|
35
35
|
@downcased[key.to_s.downcase] = value
|
36
36
|
end
|
37
|
+
alias_method :store, :[]=
|
37
38
|
|
38
39
|
if SENTINEL.respond_to? :assoc
|
39
40
|
def assoc(obj)
|
@@ -54,11 +55,11 @@ module Excon
|
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
57
|
-
alias_method :has_key?, :key?
|
58
|
-
alias_method :has_key?, :member?
|
59
58
|
def has_key?(key)
|
60
59
|
raw_key?(key) || @downcased.has_key?(key.to_s.downcase)
|
61
60
|
end
|
61
|
+
alias_method :key?, :has_key?
|
62
|
+
alias_method :member?, :has_key?
|
62
63
|
|
63
64
|
def merge(other_hash)
|
64
65
|
self.dup.merge!(other_hash)
|
@@ -15,9 +15,9 @@ module Excon
|
|
15
15
|
def response_call(datum)
|
16
16
|
body = datum[:response][:body]
|
17
17
|
unless datum.has_key?(:response_block) || body.nil? || body.empty?
|
18
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
|
18
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 })
|
19
19
|
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
20
|
-
if encoding = encodings.last
|
20
|
+
if (encoding = encodings.last)
|
21
21
|
if encoding.casecmp('deflate') == 0
|
22
22
|
# assume inflate omits header
|
23
23
|
datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
|
@@ -12,7 +12,7 @@ module Excon
|
|
12
12
|
if datum.has_key?(:expects) && ![*datum[:expects]].include?(datum[:response][:status])
|
13
13
|
raise(
|
14
14
|
Excon::Errors.status_error(
|
15
|
-
datum.reject {|key,
|
15
|
+
datum.reject {|key,_| key == :response},
|
16
16
|
Excon::Response.new(datum[:response])
|
17
17
|
)
|
18
18
|
)
|
@@ -25,7 +25,7 @@ module Excon
|
|
25
25
|
raise Excon::Errors::InvalidStub.new("Request body should be a string or an IO object. #{datum[:body].class} provided")
|
26
26
|
end
|
27
27
|
|
28
|
-
if stub = Excon.stub_for(datum)
|
28
|
+
if (stub = Excon.stub_for(datum))
|
29
29
|
datum[:remote_ip] ||= '127.0.0.1'
|
30
30
|
datum[:response] = {
|
31
31
|
:body => '',
|
@@ -41,7 +41,7 @@ module Excon
|
|
41
41
|
stub.last
|
42
42
|
end
|
43
43
|
|
44
|
-
datum[:response].merge!(stub_datum.reject {|key,
|
44
|
+
datum[:response].merge!(stub_datum.reject {|key,_| key == :headers})
|
45
45
|
if stub_datum.has_key?(:headers)
|
46
46
|
datum[:response][:headers].merge!(stub_datum[:headers])
|
47
47
|
end
|
data/lib/excon/response.rb
CHANGED
@@ -59,10 +59,13 @@ module Excon
|
|
59
59
|
|
60
60
|
def self.parse(socket, datum)
|
61
61
|
# this will discard any trailing lines from the previous response if any.
|
62
|
-
|
62
|
+
line = nil
|
63
|
+
loop do
|
63
64
|
line = socket.readline
|
64
|
-
|
65
|
+
break if line[9,3].to_i != 0
|
66
|
+
end
|
65
67
|
|
68
|
+
status = line[9, 3].to_i
|
66
69
|
reason_phrase = line[13..-3] # -3 strips the trailing "\r\n"
|
67
70
|
|
68
71
|
datum[:response] = {
|
@@ -90,7 +93,7 @@ module Excon
|
|
90
93
|
|
91
94
|
unless (['HEAD', 'CONNECT'].include?(datum[:method].to_s.upcase)) || NO_ENTITY.include?(datum[:response][:status])
|
92
95
|
|
93
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 }
|
96
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 })
|
94
97
|
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
95
98
|
if (encoding = encodings.last) && encoding.casecmp('chunked') == 0
|
96
99
|
transfer_encoding_chunked = true
|
@@ -103,7 +106,7 @@ module Excon
|
|
103
106
|
end
|
104
107
|
|
105
108
|
# use :response_block unless :expects would fail
|
106
|
-
if response_block = datum[:response_block]
|
109
|
+
if (response_block = datum[:response_block])
|
107
110
|
if datum[:middlewares].include?(Excon::Middleware::Expects) && datum[:expects] &&
|
108
111
|
!Array(datum[:expects]).include?(datum[:response][:status])
|
109
112
|
response_block = nil
|
@@ -140,11 +143,11 @@ module Excon
|
|
140
143
|
end
|
141
144
|
parse_headers(socket, datum) # merge trailers into headers
|
142
145
|
else
|
143
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 }
|
146
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 })
|
144
147
|
content_length = datum[:response][:headers][key].to_i
|
145
148
|
end
|
146
149
|
|
147
|
-
if remaining = content_length
|
150
|
+
if (remaining = content_length)
|
148
151
|
if response_block
|
149
152
|
while remaining > 0
|
150
153
|
chunk = socket.read([datum[:chunk_size], remaining].min) || raise(EOFError)
|
@@ -160,11 +163,11 @@ module Excon
|
|
160
163
|
end
|
161
164
|
else
|
162
165
|
if response_block
|
163
|
-
while chunk = socket.read(datum[:chunk_size])
|
166
|
+
while (chunk = socket.read(datum[:chunk_size]))
|
164
167
|
response_block.call(chunk, nil, nil)
|
165
168
|
end
|
166
169
|
else
|
167
|
-
while chunk = socket.read(datum[:chunk_size])
|
170
|
+
while (chunk = socket.read(datum[:chunk_size]))
|
168
171
|
datum[:response][:body] << chunk
|
169
172
|
end
|
170
173
|
end
|
data/lib/excon/socket.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'resolv'
|
3
|
+
|
2
4
|
module Excon
|
3
5
|
class Socket
|
4
6
|
include Utils
|
@@ -60,18 +62,8 @@ module Excon
|
|
60
62
|
def readline
|
61
63
|
return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
|
62
64
|
buffer = String.new
|
63
|
-
|
64
|
-
|
65
|
-
buffer
|
66
|
-
rescue *READ_RETRY_EXCEPTION_CLASSES
|
67
|
-
select_with_timeout(@socket, :read) && retry
|
68
|
-
rescue OpenSSL::SSL::SSLError => error
|
69
|
-
if error.message == 'read would block'
|
70
|
-
select_with_timeout(@socket, :read) && retry
|
71
|
-
else
|
72
|
-
raise(error)
|
73
|
-
end
|
74
|
-
end
|
65
|
+
buffer << (read_nonblock(1) || raise(EOFError)) while buffer[-1] != "\n"
|
66
|
+
buffer
|
75
67
|
end
|
76
68
|
|
77
69
|
def legacy_readline
|
@@ -105,20 +97,17 @@ module Excon
|
|
105
97
|
def connect
|
106
98
|
@socket = nil
|
107
99
|
exception = nil
|
100
|
+
hostname = @data[:hostname]
|
101
|
+
port = @port
|
102
|
+
family = @data[:family]
|
108
103
|
|
109
104
|
if @data[:proxy]
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
family = @data[:family] || ::Socket::Constants::AF_UNSPEC
|
114
|
-
args = [@data[:hostname], @port, family, ::Socket::Constants::SOCK_STREAM]
|
105
|
+
hostname = @data[:proxy][:hostname]
|
106
|
+
port = @data[:proxy][:port]
|
107
|
+
family = @data[:proxy][:family]
|
115
108
|
end
|
116
|
-
if RUBY_VERSION >= '1.9.2' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
117
|
-
args << nil << nil << false # no reverse lookup
|
118
|
-
end
|
119
|
-
addrinfo = ::Socket.getaddrinfo(*args)
|
120
109
|
|
121
|
-
|
110
|
+
Resolv.each_address(hostname) do |ip|
|
122
111
|
# already succeeded on previous addrinfo
|
123
112
|
if @socket
|
124
113
|
break
|
@@ -130,8 +119,8 @@ module Excon
|
|
130
119
|
# nonblocking connect
|
131
120
|
begin
|
132
121
|
sockaddr = ::Socket.sockaddr_in(port, ip)
|
133
|
-
|
134
|
-
socket = ::Socket.new(
|
122
|
+
addrinfo = Addrinfo.getaddrinfo(ip, port, family, :STREAM).first
|
123
|
+
socket = ::Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
|
135
124
|
|
136
125
|
if @data[:reuseaddr]
|
137
126
|
socket.setsockopt(::Socket::Constants::SOL_SOCKET, ::Socket::Constants::SO_REUSEADDR, true)
|
@@ -161,6 +150,8 @@ module Excon
|
|
161
150
|
end
|
162
151
|
end
|
163
152
|
|
153
|
+
exception ||= Resolv::ResolvError.new("no address for #{hostname}")
|
154
|
+
|
164
155
|
# this will be our last encountered exception
|
165
156
|
fail exception unless @socket
|
166
157
|
|
data/lib/excon/ssl_socket.rb
CHANGED
@@ -39,13 +39,13 @@ module Excon
|
|
39
39
|
# turn verification on
|
40
40
|
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
41
41
|
|
42
|
-
if ca_file = @data[:ssl_ca_file] || ENV['SSL_CERT_FILE']
|
42
|
+
if (ca_file = @data[:ssl_ca_file] || ENV['SSL_CERT_FILE'])
|
43
43
|
ssl_context.ca_file = ca_file
|
44
44
|
end
|
45
|
-
if ca_path = @data[:ssl_ca_path] || ENV['SSL_CERT_DIR']
|
45
|
+
if (ca_path = @data[:ssl_ca_path] || ENV['SSL_CERT_DIR'])
|
46
46
|
ssl_context.ca_path = ca_path
|
47
47
|
end
|
48
|
-
if cert_store = @data[:ssl_cert_store]
|
48
|
+
if (cert_store = @data[:ssl_cert_store])
|
49
49
|
ssl_context.cert_store = cert_store
|
50
50
|
end
|
51
51
|
|
@@ -65,7 +65,7 @@ module Excon
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
if verify_callback = @data[:ssl_verify_callback]
|
68
|
+
if (verify_callback = @data[:ssl_verify_callback])
|
69
69
|
ssl_context.verify_callback = verify_callback
|
70
70
|
end
|
71
71
|
else
|
@@ -73,6 +73,9 @@ module Excon
|
|
73
73
|
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
74
74
|
end
|
75
75
|
|
76
|
+
# Verify certificate hostname if supported (ruby >= 2.4.0)
|
77
|
+
ssl_context.verify_hostname = @data[:ssl_verify_hostname] if ssl_context.respond_to?(:verify_hostname=)
|
78
|
+
|
76
79
|
if client_cert_data && client_key_data
|
77
80
|
ssl_context.cert = OpenSSL::X509::Certificate.new client_cert_data
|
78
81
|
if OpenSSL::PKey.respond_to? :read
|
@@ -142,18 +145,16 @@ module Excon
|
|
142
145
|
if @data[:ssl_verify_peer]
|
143
146
|
@socket.post_connection_check(@data[:ssl_verify_peer_host] || @data[:host])
|
144
147
|
end
|
145
|
-
|
146
|
-
@socket
|
147
148
|
end
|
148
149
|
|
149
150
|
private
|
150
151
|
|
151
152
|
def client_cert_data
|
152
|
-
@client_cert_data ||= if ccd = @data[:client_cert_data]
|
153
|
+
@client_cert_data ||= if (ccd = @data[:client_cert_data])
|
153
154
|
ccd
|
154
|
-
elsif path = @data[:client_cert]
|
155
|
+
elsif (path = @data[:client_cert])
|
155
156
|
File.read path
|
156
|
-
elsif path = @data[:certificate_path]
|
157
|
+
elsif (path = @data[:certificate_path])
|
157
158
|
warn ":certificate_path is no longer supported and will be deprecated. Please use :client_cert or :client_cert_data"
|
158
159
|
File.read path
|
159
160
|
end
|
@@ -166,11 +167,11 @@ module Excon
|
|
166
167
|
end
|
167
168
|
|
168
169
|
def client_key_data
|
169
|
-
@client_key_data ||= if ckd = @data[:client_key_data]
|
170
|
+
@client_key_data ||= if (ckd = @data[:client_key_data])
|
170
171
|
ckd
|
171
|
-
elsif path = @data[:client_key]
|
172
|
+
elsif (path = @data[:client_key])
|
172
173
|
File.read path
|
173
|
-
elsif path = @data[:private_key_path]
|
174
|
+
elsif (path = @data[:private_key_path])
|
174
175
|
warn ":private_key_path is no longer supported and will be deprecated. Please use :client_key or :client_key_data"
|
175
176
|
File.read path
|
176
177
|
end
|