excon 0.99.0 → 1.0.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 +24 -2
- data/data/cacert.pem +483 -189
- data/excon.gemspec +1 -0
- data/lib/excon/connection.rb +18 -5
- data/lib/excon/constants.rb +121 -113
- data/lib/excon/middlewares/decompress.rb +17 -22
- data/lib/excon/socket.rb +114 -24
- data/lib/excon/ssl_socket.rb +1 -1
- data/lib/excon/test/server.rb +0 -2
- data/lib/excon/utils.rb +25 -3
- data/lib/excon/version.rb +2 -1
- data/lib/excon.rb +43 -47
- metadata +7 -7
data/excon.gemspec
CHANGED
data/lib/excon/connection.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'ipaddr'
|
|
3
2
|
|
|
4
3
|
module Excon
|
|
5
4
|
class Connection
|
|
@@ -230,6 +229,13 @@ module Excon
|
|
|
230
229
|
def request(params={}, &block)
|
|
231
230
|
# @data has defaults, merge in new params to override
|
|
232
231
|
datum = @data.merge(params)
|
|
232
|
+
|
|
233
|
+
# Set the deadline for the current request in order to determine when we have run out of time.
|
|
234
|
+
# Only set when a request timeout has been defined.
|
|
235
|
+
if datum[:timeout]
|
|
236
|
+
datum[:deadline] = Process.clock_gettime(Process::CLOCK_MONOTONIC) + datum[:timeout]
|
|
237
|
+
end
|
|
238
|
+
|
|
233
239
|
datum[:headers] = @data[:headers].merge(datum[:headers] || {})
|
|
234
240
|
|
|
235
241
|
validate_params(:request, params, datum[:middlewares])
|
|
@@ -244,16 +250,17 @@ module Excon
|
|
|
244
250
|
datum[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
|
|
245
251
|
end
|
|
246
252
|
|
|
253
|
+
host_key = datum[:headers].keys.detect {|k| k.casecmp('Host') == 0 } || 'Host'
|
|
247
254
|
if datum[:scheme] == UNIX
|
|
248
|
-
datum[:headers][
|
|
255
|
+
datum[:headers][host_key] ||= ''
|
|
249
256
|
else
|
|
250
|
-
datum[:headers][
|
|
257
|
+
datum[:headers][host_key] ||= datum[:host] + port_string(datum)
|
|
251
258
|
end
|
|
252
259
|
|
|
253
260
|
# RFC 7230, section 5.4, states that the Host header SHOULD be the first one # to be present.
|
|
254
261
|
# Some web servers will reject the request if it comes too late, so let's hoist it to the top.
|
|
255
|
-
if (host = datum[:headers].delete(
|
|
256
|
-
datum[:headers] = {
|
|
262
|
+
if (host = datum[:headers].delete(host_key))
|
|
263
|
+
datum[:headers] = { host_key => host }.merge(datum[:headers])
|
|
257
264
|
end
|
|
258
265
|
|
|
259
266
|
# default to GET if no method specified
|
|
@@ -439,6 +446,12 @@ module Excon
|
|
|
439
446
|
raise ArgumentError.new("Invalid validation type '#{validation}'")
|
|
440
447
|
end
|
|
441
448
|
|
|
449
|
+
if validation == :connection && params[:omit_default_port] != true
|
|
450
|
+
Excon.display_warning(
|
|
451
|
+
'The `omit_default_port` connection option is deprecated, please use `include_default_port` instead.'
|
|
452
|
+
)
|
|
453
|
+
end
|
|
454
|
+
|
|
442
455
|
invalid_keys = params.keys - valid_keys
|
|
443
456
|
unless invalid_keys.empty?
|
|
444
457
|
Excon.display_warning("Invalid Excon #{validation} keys: #{invalid_keys.map(&:inspect).join(', ')}")
|
data/lib/excon/constants.rb
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module Excon
|
|
3
|
-
|
|
4
3
|
CR_NL = "\r\n"
|
|
5
4
|
|
|
6
|
-
DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__),
|
|
5
|
+
DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'cacert.pem'))
|
|
7
6
|
|
|
8
|
-
DEFAULT_CHUNK_SIZE =
|
|
7
|
+
DEFAULT_CHUNK_SIZE = 1_048_576 # 1 megabyte
|
|
9
8
|
|
|
10
9
|
# avoid overwrite if somebody has redefined
|
|
11
|
-
unless const_defined?(:CHUNK_SIZE)
|
|
12
|
-
CHUNK_SIZE = DEFAULT_CHUNK_SIZE
|
|
13
|
-
end
|
|
10
|
+
CHUNK_SIZE = DEFAULT_CHUNK_SIZE unless const_defined?(:CHUNK_SIZE)
|
|
14
11
|
|
|
15
12
|
DEFAULT_REDIRECT_LIMIT = 10
|
|
16
13
|
|
|
17
14
|
DEFAULT_RETRY_LIMIT = 4
|
|
18
15
|
|
|
19
16
|
DEFAULT_RETRY_ERRORS = [
|
|
20
|
-
Excon::Error::
|
|
17
|
+
Excon::Error::RequestTimeout,
|
|
18
|
+
Excon::Error::Server,
|
|
21
19
|
Excon::Error::Socket,
|
|
22
|
-
Excon::Error::
|
|
23
|
-
|
|
20
|
+
Excon::Error::Timeout,
|
|
21
|
+
Excon::Error::TooManyRequests
|
|
22
|
+
].freeze
|
|
24
23
|
|
|
25
24
|
FORCE_ENC = CR_NL.respond_to?(:force_encoding)
|
|
26
25
|
|
|
27
26
|
HTTP_1_1 = " HTTP/1.1\r\n"
|
|
28
27
|
|
|
29
|
-
HTTP_VERBS = %w
|
|
28
|
+
HTTP_VERBS = %w[connect delete get head options patch post put trace].freeze
|
|
30
29
|
|
|
31
30
|
HTTPS = 'https'
|
|
32
31
|
|
|
@@ -40,86 +39,90 @@ module Excon
|
|
|
40
39
|
|
|
41
40
|
VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
|
|
42
41
|
|
|
43
|
-
VALID_REQUEST_KEYS = [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
42
|
+
VALID_REQUEST_KEYS = %i[
|
|
43
|
+
allow_unstubbed_requests
|
|
44
|
+
body
|
|
45
|
+
chunk_size
|
|
46
|
+
debug_request
|
|
47
|
+
debug_response
|
|
48
|
+
dns_timeouts
|
|
49
|
+
headers
|
|
50
|
+
instrumentor
|
|
51
|
+
logger
|
|
52
|
+
method
|
|
53
|
+
middlewares
|
|
54
|
+
password
|
|
55
|
+
path
|
|
56
|
+
persistent
|
|
57
|
+
pipeline
|
|
58
|
+
query
|
|
59
|
+
read_timeout
|
|
60
|
+
request_block
|
|
61
|
+
resolv_resolver
|
|
62
|
+
response_block
|
|
63
|
+
stubs
|
|
64
|
+
timeout
|
|
65
|
+
user
|
|
66
|
+
versions
|
|
67
|
+
write_timeout
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
70
|
+
VALID_CONNECTION_KEYS = VALID_REQUEST_KEYS + %i[
|
|
71
|
+
ciphers
|
|
72
|
+
client_key
|
|
73
|
+
client_key_data
|
|
74
|
+
client_key_pass
|
|
75
|
+
client_cert
|
|
76
|
+
client_cert_data
|
|
77
|
+
client_chain
|
|
78
|
+
client_chain_data
|
|
79
|
+
certificate
|
|
80
|
+
certificate_path
|
|
81
|
+
disable_proxy
|
|
82
|
+
private_key
|
|
83
|
+
private_key_path
|
|
84
|
+
connect_timeout
|
|
85
|
+
family
|
|
86
|
+
keepalive
|
|
87
|
+
host
|
|
88
|
+
hostname
|
|
89
|
+
include_default_port
|
|
90
|
+
omit_default_port
|
|
91
|
+
nonblock
|
|
92
|
+
reuseaddr
|
|
93
|
+
port
|
|
94
|
+
proxy
|
|
95
|
+
scheme
|
|
96
|
+
socket
|
|
97
|
+
ssl_ca_file
|
|
98
|
+
ssl_ca_path
|
|
99
|
+
ssl_cert_store
|
|
100
|
+
ssl_verify_callback
|
|
101
|
+
ssl_verify_peer
|
|
102
|
+
ssl_verify_peer_host
|
|
103
|
+
ssl_verify_hostname
|
|
104
|
+
ssl_version
|
|
105
|
+
ssl_min_version
|
|
106
|
+
ssl_max_version
|
|
107
|
+
ssl_security_level
|
|
108
|
+
ssl_proxy_headers
|
|
109
|
+
ssl_uri_schemes
|
|
110
|
+
tcp_nodelay
|
|
111
|
+
thread_safe_sockets
|
|
112
|
+
uri_parser
|
|
110
113
|
]
|
|
111
114
|
|
|
112
115
|
DEPRECATED_VALID_REQUEST_KEYS = {
|
|
113
|
-
:
|
|
114
|
-
:
|
|
115
|
-
:
|
|
116
|
-
:
|
|
117
|
-
:
|
|
118
|
-
:
|
|
119
|
-
:
|
|
120
|
-
:
|
|
121
|
-
:
|
|
122
|
-
}
|
|
116
|
+
captures: 'Mock',
|
|
117
|
+
expects: 'Expects',
|
|
118
|
+
idempotent: 'Idempotent',
|
|
119
|
+
instrumentor_name: 'Instrumentor',
|
|
120
|
+
mock: 'Mock',
|
|
121
|
+
retries_remaining: 'Idempotent', # referenced in Instrumentor, but only relevant with Idempotent
|
|
122
|
+
retry_errors: 'Idempotent',
|
|
123
|
+
retry_interval: 'Idempotent',
|
|
124
|
+
retry_limit: 'Idempotent' # referenced in Instrumentor, but only relevant with Idempotent
|
|
125
|
+
}.freeze
|
|
123
126
|
|
|
124
127
|
unless ::IO.const_defined?(:WaitReadable)
|
|
125
128
|
class ::IO
|
|
@@ -132,43 +135,48 @@ module Excon
|
|
|
132
135
|
module WaitWritable; end
|
|
133
136
|
end
|
|
134
137
|
end
|
|
138
|
+
|
|
135
139
|
# these come last as they rely on the above
|
|
136
140
|
DEFAULTS = {
|
|
137
|
-
:
|
|
141
|
+
chunk_size: CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
|
|
138
142
|
# see https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
|
|
139
143
|
# list provided then had DES related things sorted to the end
|
|
140
|
-
:
|
|
141
|
-
:
|
|
142
|
-
:
|
|
143
|
-
:
|
|
144
|
-
:
|
|
144
|
+
ciphers: 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA:!DSS',
|
|
145
|
+
connect_timeout: 60,
|
|
146
|
+
debug_request: false,
|
|
147
|
+
debug_response: false,
|
|
148
|
+
dns_timeouts: nil, # nil allows Resolv::DNS default timeout value (see https://ruby-doc.org/3.2.2/stdlibs/resolv/Resolv/DNS.html#method-i-timeouts-3D)
|
|
149
|
+
headers: {
|
|
145
150
|
'User-Agent' => USER_AGENT,
|
|
146
|
-
'Accept'
|
|
151
|
+
'Accept' => '*/*'
|
|
147
152
|
},
|
|
148
|
-
:
|
|
149
|
-
:
|
|
150
|
-
:
|
|
153
|
+
idempotent: false,
|
|
154
|
+
instrumentor_name: 'excon',
|
|
155
|
+
middlewares: [
|
|
151
156
|
Excon::Middleware::ResponseParser,
|
|
157
|
+
Excon::Middleware::Decompress,
|
|
152
158
|
Excon::Middleware::Expects,
|
|
153
159
|
Excon::Middleware::Idempotent,
|
|
154
160
|
Excon::Middleware::Instrumentor,
|
|
155
161
|
Excon::Middleware::Mock
|
|
156
162
|
],
|
|
157
|
-
:
|
|
158
|
-
:
|
|
159
|
-
:
|
|
160
|
-
:
|
|
161
|
-
:
|
|
162
|
-
:
|
|
163
|
-
:
|
|
164
|
-
:
|
|
165
|
-
:
|
|
166
|
-
:
|
|
167
|
-
:
|
|
168
|
-
:
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
:
|
|
163
|
+
mock: false,
|
|
164
|
+
include_default_port: false,
|
|
165
|
+
nonblock: true,
|
|
166
|
+
omit_default_port: true,
|
|
167
|
+
persistent: false,
|
|
168
|
+
read_timeout: 60,
|
|
169
|
+
resolv_resolver: nil,
|
|
170
|
+
retry_errors: DEFAULT_RETRY_ERRORS,
|
|
171
|
+
retry_limit: DEFAULT_RETRY_LIMIT,
|
|
172
|
+
ssl_verify_peer: true,
|
|
173
|
+
ssl_uri_schemes: [HTTPS],
|
|
174
|
+
stubs: :global,
|
|
175
|
+
tcp_nodelay: false,
|
|
176
|
+
thread_safe_sockets: true,
|
|
177
|
+
timeout: nil,
|
|
178
|
+
uri_parser: URI,
|
|
179
|
+
versions: VERSIONS,
|
|
180
|
+
write_timeout: 60
|
|
172
181
|
}
|
|
173
|
-
|
|
174
182
|
end
|
|
@@ -1,41 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Excon
|
|
3
4
|
module Middleware
|
|
4
5
|
class Decompress < Excon::Middleware::Base
|
|
5
|
-
|
|
6
6
|
INFLATE_ZLIB_OR_GZIP = 47 # Zlib::MAX_WBITS + 32
|
|
7
7
|
INFLATE_RAW = -15 # Zlib::MAX_WBITS * -1
|
|
8
8
|
|
|
9
9
|
def request_call(datum)
|
|
10
|
-
unless datum.
|
|
11
|
-
key = datum[:headers].keys.detect {|k| k.to_s.casecmp('Accept-Encoding')
|
|
12
|
-
if datum[:headers][key].to_s.empty?
|
|
13
|
-
datum[:headers][key] = 'deflate, gzip'
|
|
14
|
-
end
|
|
10
|
+
unless datum.key?(:response_block)
|
|
11
|
+
key = datum[:headers].keys.detect { |k| k.to_s.casecmp('Accept-Encoding').zero? } || 'Accept-Encoding'
|
|
12
|
+
datum[:headers][key] = 'deflate, gzip' if datum[:headers][key].to_s.empty?
|
|
15
13
|
end
|
|
16
14
|
@stack.request_call(datum)
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def response_call(datum)
|
|
20
18
|
body = datum[:response][:body]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Zlib::Inflate.new(INFLATE_RAW).inflate(body)
|
|
30
|
-
end
|
|
31
|
-
encodings.pop
|
|
32
|
-
elsif encoding.casecmp('gzip') == 0 || encoding.casecmp('x-gzip') == 0
|
|
33
|
-
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
|
|
34
|
-
encodings.pop
|
|
35
|
-
end
|
|
36
|
-
datum[:response][:headers][key] = encodings.join(', ')
|
|
19
|
+
if !(datum.key?(:response_block) || body.nil? || body.empty?) &&
|
|
20
|
+
(key = datum[:response][:headers].keys.detect { |k| k.casecmp('Content-Encoding').zero? })
|
|
21
|
+
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
|
22
|
+
if encodings.last.casecmp('deflate').zero?
|
|
23
|
+
datum[:response][:body] = begin
|
|
24
|
+
Zlib::Inflate.new(INFLATE_ZLIB_OR_GZIP).inflate(body)
|
|
25
|
+
rescue Zlib::DataError # fallback to raw on error
|
|
26
|
+
Zlib::Inflate.new(INFLATE_RAW).inflate(body)
|
|
37
27
|
end
|
|
28
|
+
encodings.pop
|
|
29
|
+
elsif encodings.last.casecmp('gzip').zero? || encodings.last.casecmp('x-gzip').zero?
|
|
30
|
+
datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(body)).read
|
|
31
|
+
encodings.pop
|
|
38
32
|
end
|
|
33
|
+
datum[:response][:headers][key] = encodings.join(', ')
|
|
39
34
|
end
|
|
40
35
|
@stack.response_call(datum)
|
|
41
36
|
end
|
data/lib/excon/socket.rb
CHANGED
|
@@ -25,6 +25,13 @@ module Excon
|
|
|
25
25
|
else # Ruby <= 2.0
|
|
26
26
|
[Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable]
|
|
27
27
|
end
|
|
28
|
+
# Maps a socket operation to a timeout property.
|
|
29
|
+
OPERATION_TO_TIMEOUT = {
|
|
30
|
+
:connect_read => :connect_timeout,
|
|
31
|
+
:connect_write => :connect_timeout,
|
|
32
|
+
:read => :read_timeout,
|
|
33
|
+
:write => :write_timeout
|
|
34
|
+
}.freeze
|
|
28
35
|
|
|
29
36
|
def params
|
|
30
37
|
Excon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')
|
|
@@ -46,14 +53,16 @@ module Excon
|
|
|
46
53
|
@nonblock = data[:nonblock]
|
|
47
54
|
@port ||= @data[:port] || 80
|
|
48
55
|
@read_buffer = String.new
|
|
56
|
+
@read_offset = 0
|
|
49
57
|
@eof = false
|
|
50
58
|
@backend_eof = false
|
|
59
|
+
|
|
51
60
|
connect
|
|
52
61
|
end
|
|
53
62
|
|
|
54
63
|
def read(max_length = nil)
|
|
55
64
|
if @eof
|
|
56
|
-
|
|
65
|
+
max_length ? nil : ''
|
|
57
66
|
elsif @nonblock
|
|
58
67
|
read_nonblock(max_length)
|
|
59
68
|
else
|
|
@@ -62,22 +71,24 @@ module Excon
|
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def readline
|
|
65
|
-
if @nonblock
|
|
74
|
+
if @nonblock
|
|
66
75
|
result = String.new
|
|
67
|
-
block =
|
|
68
|
-
@read_buffer = String.new
|
|
76
|
+
block = consume_read_buffer
|
|
69
77
|
|
|
70
78
|
loop do
|
|
71
79
|
idx = block.index("\n")
|
|
80
|
+
|
|
72
81
|
if idx.nil?
|
|
73
82
|
result << block
|
|
74
83
|
else
|
|
75
|
-
result << block
|
|
76
|
-
|
|
84
|
+
result << block[0..idx]
|
|
85
|
+
rewind_read_buffer(block, idx)
|
|
77
86
|
break
|
|
78
87
|
end
|
|
88
|
+
|
|
79
89
|
block = read_nonblock(@data[:chunk_size]) || raise(EOFError)
|
|
80
90
|
end
|
|
91
|
+
|
|
81
92
|
result
|
|
82
93
|
else # nonblock/legacy
|
|
83
94
|
begin
|
|
@@ -121,7 +132,17 @@ module Excon
|
|
|
121
132
|
family = @data[:proxy][:family]
|
|
122
133
|
end
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
resolver = @data[:resolv_resolver] || Resolv::DefaultResolver
|
|
136
|
+
|
|
137
|
+
# Deprecated
|
|
138
|
+
if @data[:dns_timeouts]
|
|
139
|
+
Excon.display_warning('dns_timeouts is deprecated, use resolv_resolver instead.')
|
|
140
|
+
dns_resolver = Resolv::DNS.new
|
|
141
|
+
dns_resolver.timeouts = @data[:dns_timeouts]
|
|
142
|
+
resolver = Resolv.new([Resolv::Hosts.new, dns_resolver])
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
resolver.each_address(hostname) do |ip|
|
|
125
146
|
# already succeeded on previous addrinfo
|
|
126
147
|
if @socket
|
|
127
148
|
break
|
|
@@ -187,20 +208,48 @@ module Excon
|
|
|
187
208
|
end
|
|
188
209
|
end
|
|
189
210
|
|
|
190
|
-
|
|
191
|
-
|
|
211
|
+
# Consume any bytes remaining in the read buffer before making a system call.
|
|
212
|
+
def consume_read_buffer
|
|
213
|
+
block = @read_buffer[@read_offset..-1]
|
|
214
|
+
|
|
215
|
+
@read_offset = @read_buffer.length
|
|
216
|
+
|
|
217
|
+
block
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Rewind the read buffer to just after the given index.
|
|
221
|
+
# The offset is moved back to the start of the current chunk and then forward until just after the index.
|
|
222
|
+
def rewind_read_buffer(chunk, idx)
|
|
223
|
+
@read_offset = @read_offset - chunk.length + (idx + 1)
|
|
192
224
|
@eof = false
|
|
193
225
|
end
|
|
194
226
|
|
|
195
227
|
def read_nonblock(max_length)
|
|
196
228
|
begin
|
|
229
|
+
if @read_offset != 0 && @read_offset >= @read_buffer.length
|
|
230
|
+
# Clear the buffer so we can test for emptiness below
|
|
231
|
+
@read_buffer.clear
|
|
232
|
+
# Reset the offset so it matches the length of the buffer when empty.
|
|
233
|
+
@read_offset = 0
|
|
234
|
+
end
|
|
235
|
+
|
|
197
236
|
if max_length
|
|
198
|
-
until @backend_eof ||
|
|
199
|
-
|
|
237
|
+
until @backend_eof || readable_bytes >= max_length
|
|
238
|
+
if @read_buffer.empty?
|
|
239
|
+
# Avoid allocating a new buffer string when the read buffer is empty
|
|
240
|
+
@read_buffer = @socket.read_nonblock(max_length, @read_buffer)
|
|
241
|
+
else
|
|
242
|
+
@read_buffer << @socket.read_nonblock(max_length - readable_bytes)
|
|
243
|
+
end
|
|
200
244
|
end
|
|
201
245
|
else
|
|
202
|
-
|
|
203
|
-
@read_buffer
|
|
246
|
+
until @backend_eof
|
|
247
|
+
if @read_buffer.empty?
|
|
248
|
+
# Avoid allocating a new buffer string when the read buffer is empty
|
|
249
|
+
@read_buffer = @socket.read_nonblock(@data[:chunk_size], @read_buffer)
|
|
250
|
+
else
|
|
251
|
+
@read_buffer << @socket.read_nonblock(@data[:chunk_size])
|
|
252
|
+
end
|
|
204
253
|
end
|
|
205
254
|
end
|
|
206
255
|
rescue OpenSSL::SSL::SSLError => error
|
|
@@ -220,18 +269,32 @@ module Excon
|
|
|
220
269
|
@backend_eof = true
|
|
221
270
|
end
|
|
222
271
|
|
|
223
|
-
|
|
272
|
+
if max_length
|
|
224
273
|
if @read_buffer.empty?
|
|
225
|
-
|
|
274
|
+
# EOF met at beginning
|
|
275
|
+
@eof = @backend_eof
|
|
276
|
+
nil
|
|
226
277
|
else
|
|
227
|
-
@
|
|
278
|
+
start = @read_offset
|
|
279
|
+
|
|
280
|
+
# Ensure that we can seek backwards when reading until a terminator string.
|
|
281
|
+
# The read offset must never point past the end of the read buffer.
|
|
282
|
+
@read_offset += max_length > readable_bytes ? readable_bytes : max_length
|
|
283
|
+
@read_buffer[start...@read_offset]
|
|
228
284
|
end
|
|
229
285
|
else
|
|
230
286
|
# read until EOFError, so return everything
|
|
231
|
-
|
|
287
|
+
start = @read_offset
|
|
288
|
+
|
|
289
|
+
@read_offset = @read_buffer.length
|
|
290
|
+
@eof = @backend_eof
|
|
291
|
+
|
|
292
|
+
@read_buffer[start..-1]
|
|
232
293
|
end
|
|
233
|
-
|
|
234
|
-
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def readable_bytes
|
|
297
|
+
@read_buffer.length - @read_offset
|
|
235
298
|
end
|
|
236
299
|
|
|
237
300
|
def read_block(max_length)
|
|
@@ -294,17 +357,33 @@ module Excon
|
|
|
294
357
|
end
|
|
295
358
|
|
|
296
359
|
def select_with_timeout(socket, type)
|
|
360
|
+
timeout_kind = type
|
|
361
|
+
timeout = @data[OPERATION_TO_TIMEOUT[type]]
|
|
362
|
+
|
|
363
|
+
# Check whether the request has a timeout configured.
|
|
364
|
+
if @data.include?(:deadline)
|
|
365
|
+
request_timeout = request_time_remaining
|
|
366
|
+
|
|
367
|
+
# If the time remaining until the request times out is less than the timeout for the type of select,
|
|
368
|
+
# use the time remaining as the timeout instead.
|
|
369
|
+
if request_timeout < timeout
|
|
370
|
+
timeout_kind = :request
|
|
371
|
+
timeout = request_timeout
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
297
375
|
select = case type
|
|
298
376
|
when :connect_read
|
|
299
|
-
IO.select([socket], nil, nil,
|
|
377
|
+
IO.select([socket], nil, nil, timeout)
|
|
300
378
|
when :connect_write
|
|
301
|
-
IO.select(nil, [socket], nil,
|
|
379
|
+
IO.select(nil, [socket], nil, timeout)
|
|
302
380
|
when :read
|
|
303
|
-
IO.select([socket], nil, nil,
|
|
381
|
+
IO.select([socket], nil, nil, timeout)
|
|
304
382
|
when :write
|
|
305
|
-
IO.select(nil, [socket], nil,
|
|
383
|
+
IO.select(nil, [socket], nil, timeout)
|
|
306
384
|
end
|
|
307
|
-
|
|
385
|
+
|
|
386
|
+
select || raise(Excon::Errors::Timeout.new("#{timeout_kind} timeout reached"))
|
|
308
387
|
end
|
|
309
388
|
|
|
310
389
|
def unpacked_sockaddr
|
|
@@ -314,5 +393,16 @@ module Excon
|
|
|
314
393
|
raise
|
|
315
394
|
end
|
|
316
395
|
end
|
|
396
|
+
|
|
397
|
+
# Returns the remaining time in seconds until we reach the deadline for the request timeout.
|
|
398
|
+
# Raises an exception if we have exceeded the request timeout's deadline.
|
|
399
|
+
def request_time_remaining
|
|
400
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
401
|
+
deadline = @data[:deadline]
|
|
402
|
+
|
|
403
|
+
raise(Excon::Errors::Timeout.new('request timeout reached')) if now >= deadline
|
|
404
|
+
|
|
405
|
+
deadline - now
|
|
406
|
+
end
|
|
317
407
|
end
|
|
318
408
|
end
|
data/lib/excon/ssl_socket.rb
CHANGED
|
@@ -108,7 +108,7 @@ module Excon
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
if @data[:proxy]
|
|
111
|
-
request = "CONNECT #{@data[:host]}#{
|
|
111
|
+
request = "CONNECT #{@data[:host]}#{Excon::HTTP_1_1}" \
|
|
112
112
|
"Host: #{@data[:host]}#{port_string(@data)}#{Excon::CR_NL}"
|
|
113
113
|
|
|
114
114
|
if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
|
data/lib/excon/test/server.rb
CHANGED
|
@@ -42,7 +42,6 @@ module Excon
|
|
|
42
42
|
if RUBY_PLATFORM == 'java'
|
|
43
43
|
@pid, @write, @read, @error = IO.popen4(*args)
|
|
44
44
|
else
|
|
45
|
-
GC.disable if RUBY_VERSION < '1.9'
|
|
46
45
|
@pid, @write, @read, @error = Open4.popen4(*args)
|
|
47
46
|
end
|
|
48
47
|
@started_at = Time.now
|
|
@@ -57,7 +56,6 @@ module Excon
|
|
|
57
56
|
Process.kill('USR1', pid)
|
|
58
57
|
else
|
|
59
58
|
Process.kill(9, pid)
|
|
60
|
-
GC.enable if RUBY_VERSION < '1.9'
|
|
61
59
|
Process.wait(pid)
|
|
62
60
|
end
|
|
63
61
|
|