httpx 1.8.3 → 1.9.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/doc/release_notes/1_8_4.md +13 -0
- data/doc/release_notes/1_9_0.md +75 -0
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/altsvc.rb +29 -15
- data/lib/httpx/callbacks.rb +2 -2
- data/lib/httpx/chainable.rb +6 -3
- data/lib/httpx/connection/http1.rb +11 -19
- data/lib/httpx/connection/http2.rb +112 -27
- data/lib/httpx/connection.rb +184 -46
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/tcp.rb +18 -14
- data/lib/httpx/options.rb +86 -35
- data/lib/httpx/parser/http1.rb +3 -6
- data/lib/httpx/plugins/auth/digest.rb +1 -2
- data/lib/httpx/plugins/auth.rb +11 -2
- data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
- data/lib/httpx/plugins/aws_sigv4.rb +1 -0
- data/lib/httpx/plugins/basic_auth.rb +2 -0
- data/lib/httpx/plugins/callbacks.rb +7 -6
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/cookies/cookie.rb +1 -1
- data/lib/httpx/plugins/cookies.rb +2 -2
- data/lib/httpx/plugins/digest_auth.rb +2 -0
- data/lib/httpx/plugins/expect.rb +2 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +6 -0
- data/lib/httpx/plugins/follow_redirects.rb +8 -6
- data/lib/httpx/plugins/grpc/call.rb +4 -4
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +21 -0
- data/lib/httpx/plugins/hedging.rb +284 -0
- data/lib/httpx/plugins/ntlm_auth.rb +1 -0
- data/lib/httpx/plugins/ntlm_v2_auth.rb +1 -0
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +71 -16
- data/lib/httpx/plugins/proxy/ssh.rb +30 -11
- data/lib/httpx/plugins/proxy.rb +66 -45
- data/lib/httpx/plugins/push_promise.rb +4 -2
- data/lib/httpx/plugins/retries.rb +13 -3
- data/lib/httpx/plugins/server_sent_events.rb +2 -1
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +2 -2
- data/lib/httpx/plugins/tracing.rb +61 -14
- data/lib/httpx/plugins/upgrade/h2.rb +1 -1
- data/lib/httpx/plugins/upgrade.rb +2 -0
- data/lib/httpx/plugins/webdav.rb +8 -0
- data/lib/httpx/request/body.rb +2 -1
- data/lib/httpx/request.rb +10 -7
- data/lib/httpx/resolver/multi.rb +2 -2
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/resolver.rb +5 -1
- data/lib/httpx/response/body.rb +2 -2
- data/lib/httpx/response.rb +19 -11
- data/lib/httpx/selector.rb +3 -3
- data/lib/httpx/session.rb +38 -11
- data/lib/httpx/transcoder/chunker.rb +2 -2
- data/lib/httpx/transcoder/json.rb +7 -7
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +3 -3
- data/sig/callbacks.rbs +1 -0
- data/sig/chainable.rbs +1 -0
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection/http2.rbs +30 -16
- data/sig/connection.rbs +19 -1
- data/sig/headers.rbs +1 -1
- data/sig/httpx.rbs +2 -0
- data/sig/options.rbs +43 -12
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +0 -1
- data/sig/plugins/auth.rbs +12 -5
- data/sig/plugins/hedging.rbs +44 -0
- data/sig/plugins/proxy/http.rbs +8 -1
- data/sig/plugins/proxy.rbs +3 -2
- data/sig/plugins/retries.rbs +2 -2
- data/sig/plugins/tracing.rbs +14 -5
- data/sig/request/body.rbs +1 -1
- data/sig/request.rbs +2 -1
- data/sig/resolver/entry.rbs +2 -2
- data/sig/response.rbs +1 -2
- data/sig/selector.rbs +2 -2
- data/sig/session.rbs +7 -5
- metadata +7 -1
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -11,7 +11,7 @@ module HTTPX
|
|
|
11
11
|
#
|
|
12
12
|
# * HTTP proxies
|
|
13
13
|
# * HTTPS proxies
|
|
14
|
-
# * Socks4/
|
|
14
|
+
# * Socks4 /Socks4a proxies
|
|
15
15
|
# * Socks5 proxies
|
|
16
16
|
#
|
|
17
17
|
# https://gitlab.com/os85/httpx/wikis/Proxy
|
|
@@ -19,6 +19,7 @@ module HTTPX
|
|
|
19
19
|
module Proxy
|
|
20
20
|
class ProxyConnectionError < ProxyError; end
|
|
21
21
|
|
|
22
|
+
# :nodoc:
|
|
22
23
|
PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
|
|
23
24
|
|
|
24
25
|
class << self
|
|
@@ -39,21 +40,24 @@ module HTTPX
|
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
|
|
43
|
+
# Manages the proxy parameters and the proxy authentication flow.
|
|
42
44
|
class Parameters
|
|
43
45
|
attr_reader :uri, :username, :password, :scheme, :no_proxy
|
|
44
46
|
|
|
45
|
-
def initialize(uri: nil, scheme: nil, username: nil, password: nil, no_proxy: nil, **extra)
|
|
47
|
+
def initialize(uri: nil, ns: 0, scheme: nil, username: nil, password: nil, no_proxy: nil, **extra)
|
|
46
48
|
@no_proxy = Array(no_proxy) if no_proxy
|
|
47
49
|
@uris = Array(uri)
|
|
48
|
-
uri = @uris
|
|
50
|
+
uri = @uris[ns]
|
|
49
51
|
|
|
50
52
|
@username = username
|
|
51
53
|
@password = password
|
|
52
54
|
|
|
53
|
-
@
|
|
55
|
+
@initial_credentials = @username && @password
|
|
56
|
+
|
|
57
|
+
@ns = ns
|
|
54
58
|
|
|
55
59
|
if uri
|
|
56
|
-
@uri =
|
|
60
|
+
@uri = URI(uri)
|
|
57
61
|
@username ||= @uri.user
|
|
58
62
|
@password ||= @uri.password
|
|
59
63
|
end
|
|
@@ -74,34 +78,27 @@ module HTTPX
|
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def shift
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return unless scheme != @scheme
|
|
88
|
-
|
|
89
|
-
@scheme = scheme
|
|
90
|
-
@username = username || @uri.user
|
|
91
|
-
@password = password || @uri.password
|
|
92
|
-
@authenticator = load_authenticator(scheme, @username, @password)
|
|
81
|
+
return if @ns >= @uris.size - 1
|
|
82
|
+
|
|
83
|
+
Parameters.new(
|
|
84
|
+
uri: @uris,
|
|
85
|
+
ns: @ns + 1,
|
|
86
|
+
username: (@username if @initial_credentials),
|
|
87
|
+
password: (@password if @initial_credentials),
|
|
88
|
+
no_proxy: @no_proxy
|
|
89
|
+
)
|
|
93
90
|
end
|
|
94
91
|
|
|
95
|
-
def can_authenticate?(
|
|
92
|
+
def can_authenticate?(...)
|
|
96
93
|
return false unless @authenticator && @authenticator.respond_to?(:can_authenticate?)
|
|
97
94
|
|
|
98
|
-
@authenticator.can_authenticate?(
|
|
95
|
+
@authenticator.can_authenticate?(...)
|
|
99
96
|
end
|
|
100
97
|
|
|
101
|
-
def authenticate(
|
|
98
|
+
def authenticate(...)
|
|
102
99
|
return unless @authenticator
|
|
103
100
|
|
|
104
|
-
@authenticator.authenticate(
|
|
101
|
+
@authenticator.authenticate(...)
|
|
105
102
|
end
|
|
106
103
|
|
|
107
104
|
def ==(other)
|
|
@@ -144,8 +141,26 @@ module HTTPX
|
|
|
144
141
|
|
|
145
142
|
# adds support for the following options:
|
|
146
143
|
#
|
|
147
|
-
# :proxy :: proxy options
|
|
148
|
-
#
|
|
144
|
+
# :proxy :: hash of proxy options, where a URI an be passed containing most of the requred information,
|
|
145
|
+
# or instead the different proxy options can be passed independently.
|
|
146
|
+
#
|
|
147
|
+
# The following proxy options are supported:
|
|
148
|
+
#
|
|
149
|
+
# :uri :: proxy origin URI, or list of URIs; in the case of the latter, any failed attempt to connect to a list item
|
|
150
|
+
# will retry for the next item of the list, until all elements are exhausted or it times out meanwhile.
|
|
151
|
+
# <tt>:username</tt> and <tt>:password</tt> will be inferred from it when available. The URI scheme will identify
|
|
152
|
+
# the proxy type. (ex: <tt>"http://proxi-uri:8980"</tt>, <tt>"http://user:pass@proxy-uri:8181"</tt>,
|
|
153
|
+
# <tt>"socks5://user:pass@socks-proxy-uri:10300"</tt>, <tt>["http://proxi-uri:8980", "http://user:pass@proxy-uri:8181"]</tt>).
|
|
154
|
+
# :scheme :: identifies the authentication scheme to use with the proxy, if necessary; defaults to <tt>"basic"</tt>, for basic auth,
|
|
155
|
+
# can also be <tt>"digest"</tt> or <tt>"ntml"</tt>). Your recommended to use session methods like HTTP::InstanceMethods#with_proxy_basic_auth
|
|
156
|
+
# to set these instead.
|
|
157
|
+
# :username :: the username to use when authenticating with the proxy
|
|
158
|
+
# :password :: the password to use when authenticating with the proxy
|
|
159
|
+
# :no_proxy :: A list of hostnames (or regex patterns) for which the proxy should be bypassed
|
|
160
|
+
# (ex: <tt>["example.com", "localhost", /.*\.google\.com/]</tt>).
|
|
161
|
+
#
|
|
162
|
+
# if you systems setting environment variables like `HTTP_PROXY` or `NO_PROXY` though, you won't need to set
|
|
163
|
+
# most of the above.
|
|
149
164
|
module OptionsMethods
|
|
150
165
|
private
|
|
151
166
|
|
|
@@ -161,25 +176,31 @@ module HTTPX
|
|
|
161
176
|
end
|
|
162
177
|
|
|
163
178
|
module InstanceMethods
|
|
164
|
-
def
|
|
165
|
-
|
|
179
|
+
def initialize(*)
|
|
180
|
+
super
|
|
181
|
+
@proxy_parameters = @options.proxy
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def initialize_dup(other)
|
|
185
|
+
@proxy_parameters = other.instance_variable_get(:@proxy_parameters)
|
|
186
|
+
super
|
|
187
|
+
end
|
|
166
188
|
|
|
189
|
+
def find_connection(request_uri, selector, options)
|
|
167
190
|
if (next_proxy = request_uri.find_proxy)
|
|
168
191
|
return super(request_uri, selector, options.merge(proxy: Parameters.new(uri: next_proxy)))
|
|
169
192
|
end
|
|
170
193
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return super unless proxy
|
|
194
|
+
return super unless @proxy_parameters
|
|
174
195
|
|
|
175
|
-
next_proxy =
|
|
196
|
+
next_proxy = @proxy_parameters.uri
|
|
176
197
|
|
|
177
198
|
raise ProxyError, "Failed to connect to proxy" unless next_proxy
|
|
178
199
|
|
|
179
200
|
raise ProxyError,
|
|
180
201
|
"#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
|
|
181
202
|
|
|
182
|
-
if (no_proxy =
|
|
203
|
+
if (no_proxy = @proxy_parameters.no_proxy)
|
|
183
204
|
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
|
184
205
|
|
|
185
206
|
unless no_proxy != "*" && # NO_PROXY=* bypasses proxy use
|
|
@@ -189,7 +210,7 @@ module HTTPX
|
|
|
189
210
|
end
|
|
190
211
|
end
|
|
191
212
|
|
|
192
|
-
super(request_uri, selector, options.merge(proxy:
|
|
213
|
+
super(request_uri, selector, options.merge(proxy: @proxy_parameters))
|
|
193
214
|
end
|
|
194
215
|
|
|
195
216
|
private
|
|
@@ -201,10 +222,10 @@ module HTTPX
|
|
|
201
222
|
response = super
|
|
202
223
|
|
|
203
224
|
if response.is_a?(ErrorResponse) && proxy_error?(request, response, options)
|
|
204
|
-
options.proxy.shift
|
|
225
|
+
@proxy_parameters = options.proxy.shift
|
|
205
226
|
|
|
206
227
|
# return last error response if no more proxies to try
|
|
207
|
-
return response
|
|
228
|
+
return response unless @proxy_parameters
|
|
208
229
|
|
|
209
230
|
log { "failed connecting to proxy, trying next..." }
|
|
210
231
|
request.transition(:idle)
|
|
@@ -222,22 +243,22 @@ module HTTPX
|
|
|
222
243
|
end
|
|
223
244
|
end
|
|
224
245
|
|
|
225
|
-
def proxy_error?(_request, response,
|
|
226
|
-
|
|
246
|
+
def proxy_error?(_request, response, _options)
|
|
247
|
+
proxy_parameters = @proxy_parameters
|
|
248
|
+
|
|
249
|
+
return false unless proxy_parameters
|
|
227
250
|
|
|
228
251
|
error = response.error
|
|
229
252
|
case error
|
|
230
253
|
when NativeResolveError
|
|
231
|
-
proxy_uri =
|
|
254
|
+
proxy_uri = proxy_parameters.uri
|
|
232
255
|
|
|
233
256
|
unresolved_host = error.host
|
|
234
257
|
|
|
235
258
|
# failed resolving proxy domain
|
|
236
259
|
unresolved_host == proxy_uri.host
|
|
237
260
|
when ResolveError
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
error.message.end_with?(proxy_uri.to_s)
|
|
261
|
+
error.message.end_with?(proxy_parameters.uri.to_s)
|
|
241
262
|
when ProxyConnectionError
|
|
242
263
|
# timeout errors connecting to proxy
|
|
243
264
|
true
|
|
@@ -256,7 +277,7 @@ module HTTPX
|
|
|
256
277
|
|
|
257
278
|
# redefining the connection origin as the proxy's URI,
|
|
258
279
|
# as this will be used as the tcp peer ip.
|
|
259
|
-
@proxy_uri =
|
|
280
|
+
@proxy_uri = @options.proxy.uri
|
|
260
281
|
end
|
|
261
282
|
|
|
262
283
|
def peer
|
|
@@ -312,7 +333,7 @@ module HTTPX
|
|
|
312
333
|
when :idle
|
|
313
334
|
transition(:connecting)
|
|
314
335
|
when :connected
|
|
315
|
-
|
|
336
|
+
super
|
|
316
337
|
end
|
|
317
338
|
end
|
|
318
339
|
|
|
@@ -12,8 +12,10 @@ module HTTPX
|
|
|
12
12
|
#
|
|
13
13
|
module PushPromise
|
|
14
14
|
def self.extra_options(options)
|
|
15
|
-
options.merge(
|
|
16
|
-
|
|
15
|
+
options.merge(
|
|
16
|
+
http2_settings: { settings_enable_push: 1 },
|
|
17
|
+
max_concurrent_requests: 1
|
|
18
|
+
)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
module ResponseMethods
|
|
@@ -31,11 +31,21 @@ module HTTPX
|
|
|
31
31
|
TLSError,
|
|
32
32
|
Zlib::BufError,
|
|
33
33
|
PingTimeoutError,
|
|
34
|
+
# HTTP/2 GOAWAY or RST_STREAM errors guaranteed to be retriable for all
|
|
35
|
+
# types of requests.
|
|
36
|
+
Connection::HTTP2::RefusedStreamError,
|
|
34
37
|
Connection::HTTP2::GoawayError,
|
|
35
38
|
Connection::HTTP2::PingError,
|
|
39
|
+
|
|
40
|
+
# happens if connections were somehow sent to a closed or closing
|
|
41
|
+
# HTTP2::Connection, request guaranteed to not have been processed.
|
|
42
|
+
::HTTP2::Error::ConnectionClosed,
|
|
36
43
|
].freeze
|
|
37
44
|
|
|
38
45
|
RETRYABLE_ERRORS = (RECONNECTABLE_ERRORS + [
|
|
46
|
+
# most RST_STREAM errors aren't safely retriable, as request may have
|
|
47
|
+
# been partially processed.
|
|
48
|
+
Connection::HTTP2::RstStreamError,
|
|
39
49
|
Parser::Error,
|
|
40
50
|
TimeoutError,
|
|
41
51
|
]).freeze
|
|
@@ -155,7 +165,7 @@ module HTTPX
|
|
|
155
165
|
# retry-after must be calculated before prepare_to_retry, as it relies on
|
|
156
166
|
# state changed byit.
|
|
157
167
|
retry_after = when_to_retry(request, response, options)
|
|
158
|
-
prepare_to_retry(request, response)
|
|
168
|
+
prepare_to_retry(request, response, retry_after)
|
|
159
169
|
|
|
160
170
|
if retry_after&.positive?
|
|
161
171
|
retry_start = Utils.now
|
|
@@ -197,7 +207,7 @@ module HTTPX
|
|
|
197
207
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) } && !ex.is_a?(TotalRequestTimeoutError)
|
|
198
208
|
end
|
|
199
209
|
|
|
200
|
-
def prepare_to_retry(request, response)
|
|
210
|
+
def prepare_to_retry(request, response, _retry_after)
|
|
201
211
|
request.retries -= 1 unless can_reconnect?(request, response)
|
|
202
212
|
request.transition(:idle)
|
|
203
213
|
end
|
|
@@ -256,7 +266,7 @@ module HTTPX
|
|
|
256
266
|
attr_writer :partial_response
|
|
257
267
|
|
|
258
268
|
# initializes the request instance, sets the number of retries for the request.
|
|
259
|
-
def initialize(*
|
|
269
|
+
def initialize(*)
|
|
260
270
|
super
|
|
261
271
|
@retries = @options.max_retries
|
|
262
272
|
@partial_response = nil
|
data/lib/httpx/plugins/stream.rb
CHANGED
|
@@ -111,10 +111,10 @@ module HTTPX
|
|
|
111
111
|
end || super
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
-
def method_missing(meth,
|
|
114
|
+
def method_missing(meth, ...)
|
|
115
115
|
return super unless response.respond_to?(meth)
|
|
116
116
|
|
|
117
|
-
response.__send__(meth,
|
|
117
|
+
response.__send__(meth, ...)
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
@@ -36,9 +36,9 @@ module HTTPX::Plugins
|
|
|
36
36
|
%i[start finish reset enabled?].each do |callback|
|
|
37
37
|
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
38
38
|
# proxies ##{callback} calls to wrapper tracers.
|
|
39
|
-
def #{callback}(
|
|
40
|
-
@tracers.each { |t| t.#{callback}(
|
|
41
|
-
end
|
|
39
|
+
def #{callback}(...) # def start(...)
|
|
40
|
+
@tracers.each { |t| t.#{callback}(...) } # @tracers.each { |t| t.start(....) }
|
|
41
|
+
end # end
|
|
42
42
|
OUT
|
|
43
43
|
end
|
|
44
44
|
end
|
|
@@ -63,13 +63,18 @@ module HTTPX::Plugins
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
module RequestMethods
|
|
66
|
+
# time at which a request started being buffered for sending.
|
|
66
67
|
attr_accessor :init_time
|
|
67
68
|
|
|
69
|
+
# when not nil, it means this request was sent to a non-open connection, and contains how much time it took
|
|
70
|
+
# the connection to connect.
|
|
71
|
+
attr_accessor :handshake_time
|
|
72
|
+
|
|
68
73
|
# intercepts request initialization to inject the tracing logic.
|
|
69
74
|
def initialize(*)
|
|
70
75
|
super
|
|
71
76
|
|
|
72
|
-
@init_time = nil
|
|
77
|
+
@init_time = @handshake_time = nil
|
|
73
78
|
|
|
74
79
|
tracer = @options.tracer
|
|
75
80
|
|
|
@@ -79,12 +84,13 @@ module HTTPX::Plugins
|
|
|
79
84
|
tracer.reset(self)
|
|
80
85
|
|
|
81
86
|
# request is reset when it's retried.
|
|
82
|
-
@init_time = nil
|
|
87
|
+
@init_time = @handshake_time = nil
|
|
83
88
|
end
|
|
84
89
|
on(:headers) do
|
|
85
90
|
# the usual request init time (when not including the connection handshake)
|
|
86
91
|
# should be the time the request is buffered the first time.
|
|
87
|
-
@init_time
|
|
92
|
+
@init_time = ::Time.now.utc
|
|
93
|
+
@init_time -= @handshake_time if @handshake_time
|
|
88
94
|
|
|
89
95
|
tracer.start(self)
|
|
90
96
|
end
|
|
@@ -92,12 +98,15 @@ module HTTPX::Plugins
|
|
|
92
98
|
end
|
|
93
99
|
|
|
94
100
|
def response=(*)
|
|
95
|
-
#
|
|
96
|
-
# However, there are situations where connection initialization fails.
|
|
101
|
+
# There are situations where connection initialization fails.
|
|
97
102
|
# Example is the :ssrf_filter plugin, which raises an error on
|
|
98
103
|
# initialize if the host is an IP which matches against the known set.
|
|
99
104
|
# in such cases, we'll just set here right here.
|
|
100
|
-
@init_time
|
|
105
|
+
unless @init_time
|
|
106
|
+
@init_time = ::Time.now.utc
|
|
107
|
+
|
|
108
|
+
@init_time -= @handshake_time if @handshake_time
|
|
109
|
+
end
|
|
101
110
|
|
|
102
111
|
super
|
|
103
112
|
end
|
|
@@ -112,11 +121,11 @@ module HTTPX::Plugins
|
|
|
112
121
|
end
|
|
113
122
|
|
|
114
123
|
def send_request_to_parser(request)
|
|
115
|
-
if connecting?
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
request.
|
|
124
|
+
if connecting? && @init_time
|
|
125
|
+
# at this point, the connection has connected, but hasn't yet transitioned to :open,
|
|
126
|
+
# so it's the time the requests which caused this connection to connect, are finally being
|
|
127
|
+
# sent to the parser, so handshake time should factor into the span time.
|
|
128
|
+
request.handshake_time ||= ::Time.now.utc - @init_time
|
|
120
129
|
end
|
|
121
130
|
|
|
122
131
|
super
|
|
@@ -137,6 +146,17 @@ module HTTPX::Plugins
|
|
|
137
146
|
|
|
138
147
|
private
|
|
139
148
|
|
|
149
|
+
def handle_error(er, request = nil)
|
|
150
|
+
if connecting? && @init_time
|
|
151
|
+
# bookkeep handshake time back into pending requests, which were never initialized with it.
|
|
152
|
+
handshake_time = ::Time.now.utc - @init_time
|
|
153
|
+
request.handshake_time ||= handshake_time if request
|
|
154
|
+
@pending.each { |req| req.handshake_time ||= handshake_time }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
super
|
|
158
|
+
end
|
|
159
|
+
|
|
140
160
|
def connect
|
|
141
161
|
@init_time ||= ::Time.now.utc
|
|
142
162
|
|
|
@@ -151,6 +171,33 @@ module HTTPX::Plugins
|
|
|
151
171
|
super
|
|
152
172
|
end
|
|
153
173
|
end
|
|
174
|
+
|
|
175
|
+
module HTTP2Methods
|
|
176
|
+
def initialize(*)
|
|
177
|
+
super
|
|
178
|
+
|
|
179
|
+
@handshake_time = nil
|
|
180
|
+
@handshake_init_time = ::Time.now.utc
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def send(request, head = false)
|
|
184
|
+
if head
|
|
185
|
+
# only true for pending requests waiting on the handshake, so handshake time accrues.
|
|
186
|
+
request.handshake_time ||= 0.0
|
|
187
|
+
request.handshake_time += @handshake_time
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
super
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private
|
|
194
|
+
|
|
195
|
+
def on_settings(*)
|
|
196
|
+
@handshake_time = ::Time.now.utc - @handshake_init_time unless @handshake_completed
|
|
197
|
+
|
|
198
|
+
super
|
|
199
|
+
end
|
|
200
|
+
end
|
|
154
201
|
end
|
|
155
202
|
register_plugin :tracing, Tracing
|
|
156
203
|
end
|
data/lib/httpx/plugins/webdav.rb
CHANGED
|
@@ -13,14 +13,18 @@ module HTTPX
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
module InstanceMethods
|
|
16
|
+
# sends a COPY WebDav request for +src+ and +dest+.
|
|
16
17
|
def copy(src, dest)
|
|
17
18
|
request("COPY", src, headers: { "destination" => @options.origin.merge(dest) })
|
|
18
19
|
end
|
|
19
20
|
|
|
21
|
+
# sends a MOVE WebDav request for +src+ and +dest+.
|
|
20
22
|
def move(src, dest)
|
|
21
23
|
request("MOVE", src, headers: { "destination" => @options.origin.merge(dest) })
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
# sends a LOCK WebDav request for +path+ (for the time set by +timeout+, when passed).
|
|
27
|
+
# it calls #unlock after the block is executed.
|
|
24
28
|
def lock(path, timeout: nil, &blk)
|
|
25
29
|
headers = {}
|
|
26
30
|
headers["timeout"] = if timeout && timeout.positive?
|
|
@@ -51,14 +55,17 @@ module HTTPX
|
|
|
51
55
|
response
|
|
52
56
|
end
|
|
53
57
|
|
|
58
|
+
# sends a UNLOCK WebDav request for +path+, using the given +lock_token+.
|
|
54
59
|
def unlock(path, lock_token)
|
|
55
60
|
request("UNLOCK", path, headers: { "lock-token" => lock_token })
|
|
56
61
|
end
|
|
57
62
|
|
|
63
|
+
# sends a MKCOL WebDav request for +dir+.
|
|
58
64
|
def mkcol(dir)
|
|
59
65
|
request("MKCOL", dir)
|
|
60
66
|
end
|
|
61
67
|
|
|
68
|
+
# sends a PROPFIND WebDav request for +path+.
|
|
62
69
|
def propfind(path, xml = nil)
|
|
63
70
|
body = case xml
|
|
64
71
|
when :acl
|
|
@@ -73,6 +80,7 @@ module HTTPX
|
|
|
73
80
|
request("PROPFIND", path, headers: { "depth" => "1" }, xml: body)
|
|
74
81
|
end
|
|
75
82
|
|
|
83
|
+
# sends a PROPPATCH WebDav request for +path+.
|
|
76
84
|
def proppatch(path, xml)
|
|
77
85
|
body = "<?xml version=\"1.0\"?>" \
|
|
78
86
|
"<D:propertyupdate xmlns:D=\"DAV:\" xmlns:Z=\"http://ns.example.com/standards/z39.50/\">#{xml}</D:propertyupdate>"
|
data/lib/httpx/request/body.rb
CHANGED
|
@@ -32,6 +32,7 @@ module HTTPX
|
|
|
32
32
|
@headers = h
|
|
33
33
|
@body = self.class.initialize_body(params)
|
|
34
34
|
@options = options.merge(params)
|
|
35
|
+
@unbounded_body = nil
|
|
35
36
|
|
|
36
37
|
if @body
|
|
37
38
|
if @options.compress_request_body && @headers.key?("content-encoding")
|
|
@@ -102,7 +103,7 @@ module HTTPX
|
|
|
102
103
|
|
|
103
104
|
# returns whether the body yields infinitely.
|
|
104
105
|
def unbounded_body?
|
|
105
|
-
return @unbounded_body
|
|
106
|
+
return @unbounded_body unless @unbounded_body.nil?
|
|
106
107
|
|
|
107
108
|
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
|
108
109
|
end
|
data/lib/httpx/request.rb
CHANGED
|
@@ -107,7 +107,7 @@ module HTTPX
|
|
|
107
107
|
@connection = @response =
|
|
108
108
|
@drainer = @peer_address = @callbacks =
|
|
109
109
|
@informational_status = @on_response_arrived = nil
|
|
110
|
-
@ping = @started = false
|
|
110
|
+
@ping = @started = @complete = false
|
|
111
111
|
@persistent = @options.persistent
|
|
112
112
|
@active_timeouts = []
|
|
113
113
|
end
|
|
@@ -121,8 +121,11 @@ module HTTPX
|
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def complete!(response = @response)
|
|
124
|
+
return false if @complete
|
|
125
|
+
|
|
124
126
|
emit(:complete, response)
|
|
125
127
|
reset_timers(true)
|
|
128
|
+
@complete = true
|
|
126
129
|
end
|
|
127
130
|
|
|
128
131
|
# whether request has been buffered with a ping
|
|
@@ -137,22 +140,22 @@ module HTTPX
|
|
|
137
140
|
|
|
138
141
|
# the read timeout defined for this request.
|
|
139
142
|
def read_timeout
|
|
140
|
-
@options.timeout
|
|
143
|
+
@options.timeout.read_timeout
|
|
141
144
|
end
|
|
142
145
|
|
|
143
146
|
# the write timeout defined for this request.
|
|
144
147
|
def write_timeout
|
|
145
|
-
@options.timeout
|
|
148
|
+
@options.timeout.write_timeout
|
|
146
149
|
end
|
|
147
150
|
|
|
148
151
|
# the request timeout defined for this request.
|
|
149
152
|
def request_timeout
|
|
150
|
-
@options.timeout
|
|
153
|
+
@options.timeout.request_timeout
|
|
151
154
|
end
|
|
152
155
|
|
|
153
156
|
# the total request timeout defined for this request.
|
|
154
157
|
def total_request_timeout
|
|
155
|
-
@options.timeout
|
|
158
|
+
@options.timeout.total_request_timeout
|
|
156
159
|
end
|
|
157
160
|
|
|
158
161
|
def persistent?
|
|
@@ -296,7 +299,7 @@ module HTTPX
|
|
|
296
299
|
case nextstate
|
|
297
300
|
when :idle
|
|
298
301
|
@body.rewind
|
|
299
|
-
@ping = false
|
|
302
|
+
@ping = @complete = false
|
|
300
303
|
@response = @drainer = nil
|
|
301
304
|
|
|
302
305
|
# request may be sent to a different connection and will be
|
|
@@ -336,7 +339,7 @@ module HTTPX
|
|
|
336
339
|
|
|
337
340
|
# whether the request supports the 100-continue handshake and already processed the 100 response.
|
|
338
341
|
def expects?
|
|
339
|
-
@headers
|
|
342
|
+
@headers.key?("expect") && @headers.get("expect").include?("100-continue") && @informational_status == 100 && !@response
|
|
340
343
|
end
|
|
341
344
|
|
|
342
345
|
def set_timeout_callback(event, &callback)
|
data/lib/httpx/resolver/multi.rb
CHANGED
data/lib/httpx/resolver.rb
CHANGED
|
@@ -57,7 +57,11 @@ module HTTPX
|
|
|
57
57
|
|
|
58
58
|
if message.rcode != Resolv::DNS::RCode::NoError
|
|
59
59
|
case message.rcode
|
|
60
|
-
when Resolv::DNS::RCode::ServFail
|
|
60
|
+
when Resolv::DNS::RCode::ServFail,
|
|
61
|
+
# getaddrinfo also retries these errors, as they're not about the hostname being resolved,
|
|
62
|
+
# but the DNS resolver state.
|
|
63
|
+
Resolv::DNS::RCode::NotImp,
|
|
64
|
+
Resolv::DNS::RCode::Refused
|
|
61
65
|
return :retriable_error, message.rcode
|
|
62
66
|
else
|
|
63
67
|
return :dns_error, message.rcode
|
data/lib/httpx/response/body.rb
CHANGED
|
@@ -69,7 +69,7 @@ module HTTPX
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
# reads a chunk from the payload (implementation of the IO reader protocol).
|
|
72
|
-
def read(
|
|
72
|
+
def read(...)
|
|
73
73
|
return unless @buffer
|
|
74
74
|
|
|
75
75
|
unless @reader
|
|
@@ -77,7 +77,7 @@ module HTTPX
|
|
|
77
77
|
@reader = @buffer
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
@reader.read(
|
|
80
|
+
@reader.read(...)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# size of the decoded response payload. May differ from "content-length" header if
|