excon 0.71.1 → 0.89.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/cacert.pem +473 -673
- data/excon.gemspec +4 -2
- data/lib/excon/connection.rb +122 -84
- data/lib/excon/constants.rb +3 -0
- data/lib/excon/error.rb +11 -1
- data/lib/excon/headers.rb +4 -3
- data/lib/excon/instrumentors/logging_instrumentor.rb +3 -3
- data/lib/excon/instrumentors/standard_instrumentor.rb +1 -1
- data/lib/excon/middlewares/capture_cookies.rb +1 -1
- data/lib/excon/middlewares/decompress.rb +11 -4
- 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 +23 -13
- data/lib/excon/test/plugin/server/exec.rb +5 -2
- data/lib/excon/test/plugin/server/puma.rb +4 -1
- data/lib/excon/test/plugin/server/unicorn.rb +5 -0
- data/lib/excon/test/plugin/server/webrick.rb +4 -1
- data/lib/excon/test/server.rb +1 -1
- data/lib/excon/unix_socket.rb +1 -0
- data/lib/excon/utils.rb +20 -2
- data/lib/excon/version.rb +1 -1
- data/lib/excon.rb +5 -5
- metadata +20 -6
data/excon.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
require 'excon/version'
|
2
3
|
|
3
4
|
Gem::Specification.new do |s|
|
4
5
|
s.name = 'excon'
|
@@ -11,7 +12,7 @@ Gem::Specification.new do |s|
|
|
11
12
|
s.license = 'MIT'
|
12
13
|
s.rdoc_options = ["--charset=UTF-8"]
|
13
14
|
s.extra_rdoc_files = %w[README.md CONTRIBUTORS.md CONTRIBUTING.md]
|
14
|
-
s.files = `git ls-files --
|
15
|
+
s.files = `git ls-files -- data/* lib/*`.split("\n") + [
|
15
16
|
"CONTRIBUTING.md",
|
16
17
|
"CONTRIBUTORS.md",
|
17
18
|
"LICENSE.md",
|
@@ -31,6 +32,7 @@ Gem::Specification.new do |s|
|
|
31
32
|
s.add_development_dependency('sinatra-contrib')
|
32
33
|
s.add_development_dependency('json', '>= 1.8.5')
|
33
34
|
s.add_development_dependency('puma')
|
35
|
+
s.add_development_dependency('webrick')
|
34
36
|
|
35
37
|
s.metadata = {
|
36
38
|
'homepage_uri' => 'https://github.com/excon/excon',
|
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
|
@@ -77,7 +79,7 @@ module Excon
|
|
77
79
|
|
78
80
|
setup_proxy
|
79
81
|
|
80
|
-
if
|
82
|
+
if ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
|
81
83
|
@data[:instrumentor] = Excon::StandardInstrumentor
|
82
84
|
end
|
83
85
|
|
@@ -113,7 +115,7 @@ module Excon
|
|
113
115
|
# we already have data from a middleware, so bail
|
114
116
|
return datum
|
115
117
|
else
|
116
|
-
socket.data = datum
|
118
|
+
socket(datum).data = datum
|
117
119
|
# start with "METHOD /path"
|
118
120
|
request = datum[:method].to_s.upcase + ' '
|
119
121
|
if datum[:proxy] && datum[:scheme] != HTTPS
|
@@ -142,35 +144,25 @@ module Excon
|
|
142
144
|
end
|
143
145
|
|
144
146
|
# add headers to request
|
145
|
-
datum[:headers]
|
146
|
-
if key.to_s.match(/[\r\n]/)
|
147
|
-
raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
|
148
|
-
end
|
149
|
-
[values].flatten.each do |value|
|
150
|
-
if value.to_s.match(/[\r\n]/)
|
151
|
-
raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
|
152
|
-
end
|
153
|
-
request << key.to_s << ': ' << value.to_s << CR_NL
|
154
|
-
end
|
155
|
-
end
|
147
|
+
request << Utils.headers_hash_to_s(datum[:headers])
|
156
148
|
|
157
149
|
# add additional "\r\n" to indicate end of headers
|
158
150
|
request << CR_NL
|
159
151
|
|
160
152
|
if datum.has_key?(:request_block)
|
161
|
-
socket.write(request) # write out request + headers
|
153
|
+
socket(datum).write(request) # write out request + headers
|
162
154
|
while true # write out body with chunked encoding
|
163
155
|
chunk = datum[:request_block].call
|
164
156
|
chunk = binary_encode(chunk)
|
165
157
|
if chunk.length > 0
|
166
|
-
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)
|
167
159
|
else
|
168
|
-
socket.write(String.new("0#{CR_NL}#{CR_NL}"))
|
160
|
+
socket(datum).write(String.new("0#{CR_NL}#{CR_NL}"))
|
169
161
|
break
|
170
162
|
end
|
171
163
|
end
|
172
164
|
elsif body.nil?
|
173
|
-
socket.write(request) # write out request + headers
|
165
|
+
socket(datum).write(request) # write out request + headers
|
174
166
|
else # write out body
|
175
167
|
if body.respond_to?(:binmode) && !body.is_a?(StringIO)
|
176
168
|
body.binmode
|
@@ -184,13 +176,13 @@ module Excon
|
|
184
176
|
chunk = body.read([datum[:chunk_size] - request.length, 0].max)
|
185
177
|
if chunk
|
186
178
|
chunk = binary_encode(chunk)
|
187
|
-
socket.write(request << chunk)
|
179
|
+
socket(datum).write(request << chunk)
|
188
180
|
else
|
189
|
-
socket.write(request) # write out request + headers
|
181
|
+
socket(datum).write(request) # write out request + headers
|
190
182
|
end
|
191
183
|
|
192
|
-
while chunk = body.read(datum[:chunk_size])
|
193
|
-
socket.write(chunk)
|
184
|
+
while (chunk = body.read(datum[:chunk_size]))
|
185
|
+
socket(datum).write(chunk)
|
194
186
|
end
|
195
187
|
end
|
196
188
|
end
|
@@ -198,6 +190,11 @@ module Excon
|
|
198
190
|
case error
|
199
191
|
when Excon::Errors::InvalidHeaderKey, Excon::Errors::InvalidHeaderValue, Excon::Errors::StubNotFound, Excon::Errors::Timeout
|
200
192
|
raise(error)
|
193
|
+
when Errno::EPIPE
|
194
|
+
# Read whatever remains in the pipe to aid in debugging
|
195
|
+
response = socket.read
|
196
|
+
error = Excon::Error.new(response + error.message)
|
197
|
+
raise_socket_error(error)
|
201
198
|
else
|
202
199
|
raise_socket_error(error)
|
203
200
|
end
|
@@ -250,6 +247,12 @@ module Excon
|
|
250
247
|
datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
|
251
248
|
end
|
252
249
|
|
250
|
+
# RFC 7230, section 5.4, states that the Host header SHOULD be the first one # to be present.
|
251
|
+
# Some web servers will reject the request if it comes too late, so let's hoist it to the top.
|
252
|
+
if (host = datum[:headers].delete('Host'))
|
253
|
+
datum[:headers] = { 'Host' => host }.merge(datum[:headers])
|
254
|
+
end
|
255
|
+
|
253
256
|
# if path is empty or doesn't start with '/', insert one
|
254
257
|
unless datum[:path][0, 1] == '/'
|
255
258
|
datum[:path] = datum[:path].dup.insert(0, '/')
|
@@ -280,7 +283,7 @@ module Excon
|
|
280
283
|
@persistent_socket_reusable = true
|
281
284
|
|
282
285
|
if datum[:persistent]
|
283
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
286
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
|
284
287
|
if datum[:response][:headers][key].casecmp('close') == 0
|
285
288
|
reset
|
286
289
|
end
|
@@ -320,7 +323,7 @@ module Excon
|
|
320
323
|
end
|
321
324
|
|
322
325
|
if @data[:persistent]
|
323
|
-
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
326
|
+
if (key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 })
|
324
327
|
if responses.last[:headers][key].casecmp('close') == 0
|
325
328
|
reset
|
326
329
|
end
|
@@ -348,7 +351,7 @@ module Excon
|
|
348
351
|
end
|
349
352
|
|
350
353
|
def reset
|
351
|
-
if old_socket = sockets.delete(@socket_key)
|
354
|
+
if (old_socket = sockets.delete(@socket_key))
|
352
355
|
old_socket.close rescue nil
|
353
356
|
end
|
354
357
|
@persistent_socket_reusable = true
|
@@ -455,26 +458,27 @@ module Excon
|
|
455
458
|
end
|
456
459
|
end
|
457
460
|
|
458
|
-
def socket
|
459
|
-
unix_proxy =
|
460
|
-
sockets[@socket_key] ||= if
|
461
|
-
Excon::UnixSocket.new(
|
462
|
-
elsif
|
463
|
-
Excon::SSLSocket.new(
|
461
|
+
def socket(datum = @data)
|
462
|
+
unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
|
463
|
+
sockets[@socket_key] ||= if datum[:scheme] == UNIX || unix_proxy
|
464
|
+
Excon::UnixSocket.new(datum)
|
465
|
+
elsif datum[:ssl_uri_schemes].include?(datum[:scheme])
|
466
|
+
Excon::SSLSocket.new(datum)
|
464
467
|
else
|
465
|
-
Excon::Socket.new(
|
468
|
+
Excon::Socket.new(datum)
|
466
469
|
end
|
467
470
|
end
|
468
471
|
|
469
472
|
def sockets
|
470
473
|
@_excon_sockets ||= {}
|
474
|
+
@_excon_sockets.compare_by_identity
|
471
475
|
|
472
476
|
if @data[:thread_safe_sockets]
|
473
477
|
# In a multi-threaded world, if the same connection is used by multiple
|
474
478
|
# threads at the same time to connect to the same destination, they may
|
475
479
|
# stomp on each other's sockets. This ensures every thread gets their
|
476
480
|
# own socket cache, within the context of a single connection.
|
477
|
-
@_excon_sockets[Thread.current
|
481
|
+
@_excon_sockets[Thread.current] ||= {}
|
478
482
|
else
|
479
483
|
@_excon_sockets
|
480
484
|
end
|
@@ -488,6 +492,47 @@ module Excon
|
|
488
492
|
end
|
489
493
|
end
|
490
494
|
|
495
|
+
def proxy_match_host_port(host, port)
|
496
|
+
host_match = if host.is_a? IPAddr
|
497
|
+
begin
|
498
|
+
host.include? @data[:host]
|
499
|
+
rescue IPAddr::Error
|
500
|
+
false
|
501
|
+
end
|
502
|
+
else
|
503
|
+
/(^|\.)#{host}$/.match(@data[:host])
|
504
|
+
end
|
505
|
+
host_match && (port.nil? || port.to_i == @data[:port])
|
506
|
+
end
|
507
|
+
|
508
|
+
def proxy_from_env
|
509
|
+
if (no_proxy_env = ENV['no_proxy'] || ENV['NO_PROXY'])
|
510
|
+
no_proxy_list = no_proxy_env.scan(/\s*(?:\[([\dA-Fa-f:\/]+)\]|\*?\.?([^\s,:]+))(?::(\d+))?\s*/i).map { |e|
|
511
|
+
if e[0]
|
512
|
+
begin
|
513
|
+
[IPAddr.new(e[0]), e[2]]
|
514
|
+
rescue IPAddr::Error
|
515
|
+
nil
|
516
|
+
end
|
517
|
+
else
|
518
|
+
begin
|
519
|
+
[IPAddr.new(e[1]), e[2]]
|
520
|
+
rescue IPAddr::Error
|
521
|
+
[e[1], e[2]]
|
522
|
+
end
|
523
|
+
end
|
524
|
+
}.reject { |e| e.nil? || e[0].nil? }
|
525
|
+
end
|
526
|
+
|
527
|
+
unless no_proxy_env && no_proxy_list.index { |h| proxy_match_host_port(h[0], h[1]) }
|
528
|
+
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
|
529
|
+
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
|
530
|
+
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
|
531
|
+
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
491
536
|
def setup_proxy
|
492
537
|
if @data[:disable_proxy]
|
493
538
|
if @data[:proxy]
|
@@ -496,64 +541,57 @@ module Excon
|
|
496
541
|
return
|
497
542
|
end
|
498
543
|
|
499
|
-
|
500
|
-
|
501
|
-
|
544
|
+
return if @data[:scheme] == UNIX
|
545
|
+
|
546
|
+
proxy_from_env
|
547
|
+
|
548
|
+
case @data[:proxy]
|
549
|
+
when nil
|
550
|
+
@data.delete(:proxy)
|
551
|
+
when ''
|
552
|
+
@data.delete(:proxy)
|
553
|
+
when Hash
|
554
|
+
# no processing needed
|
555
|
+
when String, URI
|
556
|
+
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
|
557
|
+
@data[:proxy] = {
|
558
|
+
:host => uri.host,
|
559
|
+
:hostname => uri.hostname,
|
560
|
+
# path is only sensible for a Unix socket proxy
|
561
|
+
:path => uri.scheme == UNIX ? uri.path : nil,
|
562
|
+
:port => uri.port,
|
563
|
+
:scheme => uri.scheme,
|
564
|
+
}
|
565
|
+
if uri.password
|
566
|
+
@data[:proxy][:password] = uri.password
|
502
567
|
end
|
503
|
-
|
504
|
-
|
505
|
-
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
|
506
|
-
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
|
507
|
-
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
|
508
|
-
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
509
|
-
end
|
568
|
+
if uri.user
|
569
|
+
@data[:proxy][:user] = uri.user
|
510
570
|
end
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
# no processing needed
|
519
|
-
when String, URI
|
520
|
-
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
|
521
|
-
@data[:proxy] = {
|
522
|
-
:host => uri.host,
|
523
|
-
:hostname => uri.hostname,
|
524
|
-
# path is only sensible for a Unix socket proxy
|
525
|
-
:path => uri.scheme == UNIX ? uri.path : nil,
|
526
|
-
:port => uri.port,
|
527
|
-
:scheme => uri.scheme,
|
528
|
-
}
|
529
|
-
if uri.password
|
530
|
-
@data[:proxy][:password] = uri.password
|
531
|
-
end
|
532
|
-
if uri.user
|
533
|
-
@data[:proxy][:user] = uri.user
|
534
|
-
end
|
535
|
-
if @data[:proxy][:scheme] == UNIX
|
536
|
-
if @data[:proxy][:host]
|
537
|
-
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
|
538
|
-
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
|
539
|
-
end
|
540
|
-
else
|
541
|
-
unless uri.host && uri.port && uri.scheme
|
542
|
-
raise Excon::Errors::ProxyParse, "Proxy is invalid"
|
543
|
-
end
|
571
|
+
if @data[:ssl_proxy_headers] && !@data[:ssl_uri_schemes].include?(@data[:scheme])
|
572
|
+
raise ArgumentError, "The `:ssl_proxy_headers` parameter should only be used with HTTPS requests."
|
573
|
+
end
|
574
|
+
if @data[:proxy][:scheme] == UNIX
|
575
|
+
if @data[:proxy][:host]
|
576
|
+
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
|
577
|
+
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
|
544
578
|
end
|
545
579
|
else
|
546
|
-
|
580
|
+
unless uri.host && uri.port && uri.scheme
|
581
|
+
raise Excon::Errors::ProxyParse, "Proxy is invalid"
|
582
|
+
end
|
547
583
|
end
|
584
|
+
else
|
585
|
+
raise Excon::Errors::ProxyParse, "Proxy is invalid"
|
586
|
+
end
|
548
587
|
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
end
|
588
|
+
if @data.has_key?(:proxy) && @data[:scheme] == 'http'
|
589
|
+
@data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
|
590
|
+
# https credentials happen in handshake
|
591
|
+
if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
|
592
|
+
user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
|
593
|
+
auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
|
594
|
+
@data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
|
557
595
|
end
|
558
596
|
end
|
559
597
|
end
|
data/lib/excon/constants.rb
CHANGED
@@ -95,9 +95,12 @@ module Excon
|
|
95
95
|
:ssl_verify_callback,
|
96
96
|
:ssl_verify_peer,
|
97
97
|
:ssl_verify_peer_host,
|
98
|
+
:ssl_verify_hostname,
|
98
99
|
:ssl_version,
|
99
100
|
:ssl_min_version,
|
100
101
|
:ssl_max_version,
|
102
|
+
:ssl_security_level,
|
103
|
+
:ssl_proxy_headers,
|
101
104
|
:ssl_uri_schemes,
|
102
105
|
:tcp_nodelay,
|
103
106
|
:thread_safe_sockets,
|
data/lib/excon/error.rb
CHANGED
@@ -50,7 +50,17 @@ or:
|
|
50
50
|
class InvalidHeaderValue < Error; end
|
51
51
|
class Timeout < Error; end
|
52
52
|
class ResponseParse < Error; end
|
53
|
-
|
53
|
+
|
54
|
+
class ProxyConnectionError < Error
|
55
|
+
attr_reader :request, :response
|
56
|
+
|
57
|
+
def initialize(msg, request = nil, response = nil)
|
58
|
+
super(msg)
|
59
|
+
@request = request
|
60
|
+
@response = response
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
54
64
|
class ProxyParse < Error; end
|
55
65
|
class TooManyRedirects < Error; end
|
56
66
|
|
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)
|
@@ -3,7 +3,7 @@ require 'logger'
|
|
3
3
|
module Excon
|
4
4
|
class LoggingInstrumentor
|
5
5
|
|
6
|
-
def self.instrument(name, params = {}
|
6
|
+
def self.instrument(name, params = {})
|
7
7
|
params = params.dup
|
8
8
|
|
9
9
|
logger = params[:logger] || Logger.new($stderr)
|
@@ -28,7 +28,7 @@ module Excon
|
|
28
28
|
info << "?"
|
29
29
|
|
30
30
|
if params[:query].is_a?(Hash)
|
31
|
-
info << params.to_a.map{ |key,value| "#{key}=#{value}" }.join('&')
|
31
|
+
info << params[:query].to_a.map { |key,value| "#{key}=#{value}" }.join('&')
|
32
32
|
else
|
33
33
|
info << params[:query]
|
34
34
|
end
|
@@ -40,7 +40,7 @@ module Excon
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
logger.
|
43
|
+
logger.info(info) if info
|
44
44
|
|
45
45
|
yield if block_given?
|
46
46
|
end
|
@@ -2,6 +2,10 @@
|
|
2
2
|
module Excon
|
3
3
|
module Middleware
|
4
4
|
class Decompress < Excon::Middleware::Base
|
5
|
+
|
6
|
+
INFLATE_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
|
7
|
+
INFLATE_RAW = -15 # Zlib::MAX_WBITS * -1
|
8
|
+
|
5
9
|
def request_call(datum)
|
6
10
|
unless datum.has_key?(:response_block)
|
7
11
|
key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding') == 0 } || 'Accept-Encoding'
|
@@ -15,12 +19,15 @@ module Excon
|
|
15
19
|
def response_call(datum)
|
16
20
|
body = datum[:response][:body]
|
17
21
|
unless datum.has_key?(:response_block) || body.nil? || body.empty?
|
18
|
-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
|
22
|
+
if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 })
|
19
23
|
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
20
|
-
if encoding = encodings.last
|
24
|
+
if (encoding = encodings.last)
|
21
25
|
if encoding.casecmp('deflate') == 0
|
22
|
-
|
23
|
-
|
26
|
+
datum[:response][:body] = begin
|
27
|
+
Zlib::Inflate.new(INFLATE_ZLIB_OR_GZIP).inflate(body)
|
28
|
+
rescue Zlib::DataError # fallback to raw on error
|
29
|
+
Zlib::Inflate.new(INFLATE_RAW).inflate(body)
|
30
|
+
end
|
24
31
|
encodings.pop
|
25
32
|
elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
|
26
33
|
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
|
@@ -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
|
|