httpx 1.7.0 → 1.7.1
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_7_1.md +21 -0
- data/lib/httpx/adapters/webmock.rb +18 -9
- data/lib/httpx/altsvc.rb +1 -1
- data/lib/httpx/connection/http1.rb +4 -3
- data/lib/httpx/connection.rb +4 -1
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/options.rb +78 -5
- data/lib/httpx/parser/http1.rb +1 -0
- data/lib/httpx/plugins/auth.rb +28 -2
- data/lib/httpx/plugins/oauth.rb +11 -18
- data/lib/httpx/plugins/persistent.rb +3 -5
- data/lib/httpx/plugins/proxy/http.rb +0 -4
- data/lib/httpx/plugins/proxy.rb +3 -1
- data/lib/httpx/plugins/query.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +20 -15
- data/lib/httpx/plugins/retries.rb +8 -11
- data/lib/httpx/plugins/stream.rb +2 -2
- data/lib/httpx/plugins/stream_bidi.rb +13 -1
- data/lib/httpx/pool.rb +0 -1
- data/lib/httpx/request/body.rb +1 -1
- data/lib/httpx/resolver/cache/base.rb +136 -0
- data/lib/httpx/resolver/cache/memory.rb +42 -0
- data/lib/httpx/resolver/cache.rb +18 -0
- data/lib/httpx/resolver/https.rb +7 -3
- data/lib/httpx/resolver/multi.rb +7 -3
- data/lib/httpx/resolver/native.rb +6 -2
- data/lib/httpx/resolver/resolver.rb +1 -1
- data/lib/httpx/resolver.rb +3 -149
- data/lib/httpx/response/body.rb +3 -3
- data/lib/httpx/selector.rb +5 -3
- data/lib/httpx/session.rb +1 -1
- data/lib/httpx/timers.rb +6 -12
- data/lib/httpx/transcoder/gzip.rb +7 -2
- data/lib/httpx/transcoder/multipart/decoder.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +1 -1
- data/lib/httpx/utils.rb +13 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +7 -4
- data/sig/loggable.rbs +1 -1
- data/sig/options.rbs +11 -3
- data/sig/plugins/auth.rbs +10 -1
- data/sig/plugins/oauth.rbs +0 -2
- data/sig/plugins/rate_limiter.rbs +4 -2
- data/sig/plugins/retries.rbs +4 -2
- data/sig/resolver/cache/base.rbs +28 -0
- data/sig/resolver/cache/memory.rbs +13 -0
- data/sig/resolver/cache.rbs +16 -0
- data/sig/resolver/https.rbs +19 -0
- data/sig/resolver/multi.rbs +6 -0
- data/sig/resolver.rbs +0 -24
- data/sig/timers.rbs +1 -1
- data/sig/utils.rbs +2 -0
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9c30e22a2d406a61ef87a58fddd607bb2b1fad7b50d837d8db062d4a538a2d2
|
|
4
|
+
data.tar.gz: 9c5e1997b9c03434071c59b2deb2b004f7b1a1077c2ccaed03b9e3a1db7aafe5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87d99c4971b99f086f7811be81a8453e71bd2535120b5e560a2fc837d50e8a040e70ab1b2b191beee4b481fcea5063576d8895d318a0089183c21cb59fdd0f24
|
|
7
|
+
data.tar.gz: 85469cf5b5822990367f1e5591798a1512de90a60226853da4b4e6b00dde8816d6218dd2aacc9b5d85f84f6cb1637dd4f7b37836863f44144f05329e4fbdf7f5
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 1.7.1
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* fixed timers handling in the selector loop which caused them to be traversed-for-drop twice on each tick.
|
|
6
|
+
* connection: take proxy connecting states when transitioning to `:closing` state.
|
|
7
|
+
* refactored name resolution cache to a cache adapter API with a default memory cache which keeps the current behaviour and will allow to add others.
|
|
8
|
+
* a new option, `:resolver_cache`, was added, which is `:memory` by default.
|
|
9
|
+
* this fixes an issue introduced for multi-ractor support where the cache store would not be thread-safe when used in a non main ractor.
|
|
10
|
+
* `:auth` plugin: when loaded with the `:retries` plugin, and the auth value method is dynamic/callable, will recover out-of-the-box from 401 HTTP responses by retrying the request with a newly-generated token.
|
|
11
|
+
* the `:rate_limiter` plugin will use this work to retry rate-limited responses without having to set setting `:retry_change_requests` to true, thereby eliminating a potential issue with non-idempotent requests.
|
|
12
|
+
|
|
13
|
+
## Bugfixes
|
|
14
|
+
|
|
15
|
+
* HTTP1 parser: clear buffer on reset.
|
|
16
|
+
* http1 fix: handle the case in `#handle_error` where the response is an error response
|
|
17
|
+
* `:stream_bidi` plugin: fix internal state preventing bidi requests from being retried.
|
|
18
|
+
* `:stream_bidi` plugin: will only allow initial request body being passed using `:body` param (others, like `:json`, will raise an exception)
|
|
19
|
+
* https resolver: return `:idle` on `#state` calls when no connection is available (sometimes called in internal log messages).
|
|
20
|
+
* selector loop fix: when there are no selectables and an interval is passed, sleep instead of returning (thereby avoiding potential busy loop).
|
|
21
|
+
*
|
|
@@ -38,12 +38,15 @@ module WebMock
|
|
|
38
38
|
|
|
39
39
|
return build_error_response(request, webmock_response.exception) if webmock_response.exception
|
|
40
40
|
|
|
41
|
-
request
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
request
|
|
42
|
+
.options
|
|
43
|
+
.response_class
|
|
44
|
+
.new(
|
|
45
|
+
request,
|
|
46
|
+
webmock_response.status[0],
|
|
47
|
+
"2.0",
|
|
48
|
+
webmock_response.headers
|
|
49
|
+
).tap(&:mock!)
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def build_error_response(request, exception)
|
|
@@ -72,17 +75,23 @@ module WebMock
|
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
module ResponseMethods
|
|
75
|
-
attr_accessor :mocked
|
|
76
|
-
|
|
77
78
|
def initialize(*)
|
|
78
79
|
super
|
|
79
80
|
@mocked = false
|
|
80
81
|
end
|
|
82
|
+
|
|
83
|
+
def mock!
|
|
84
|
+
@mocked = true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def mocked?
|
|
88
|
+
@mocked
|
|
89
|
+
end
|
|
81
90
|
end
|
|
82
91
|
|
|
83
92
|
module ResponseBodyMethods
|
|
84
93
|
def decode_chunk(chunk)
|
|
85
|
-
return chunk if @response.mocked
|
|
94
|
+
return chunk if @response.mocked?
|
|
86
95
|
|
|
87
96
|
super
|
|
88
97
|
end
|
data/lib/httpx/altsvc.rb
CHANGED
|
@@ -197,9 +197,10 @@ module HTTPX
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
def handle_error(ex, request = nil)
|
|
200
|
-
if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request &&
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request &&
|
|
201
|
+
(response = @request.response) && response.is_a?(Response) &&
|
|
202
|
+
!response.headers.key?("content-length") &&
|
|
203
|
+
!response.headers.key?("transfer-encoding")
|
|
203
204
|
# if the response does not contain a content-length header, the server closing the
|
|
204
205
|
# connnection is the indicator of response consumed.
|
|
205
206
|
# https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.4
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -724,7 +724,7 @@ module HTTPX
|
|
|
724
724
|
|
|
725
725
|
disconnect
|
|
726
726
|
when :closing
|
|
727
|
-
return unless
|
|
727
|
+
return unless connecting? || @state == :open
|
|
728
728
|
|
|
729
729
|
unless @write_buffer.empty?
|
|
730
730
|
# preset state before handshake, as error callbacks
|
|
@@ -850,6 +850,9 @@ module HTTPX
|
|
|
850
850
|
end
|
|
851
851
|
end
|
|
852
852
|
|
|
853
|
+
# recover internal state and emit all relevant error responses when +error+ was raised.
|
|
854
|
+
# this takes an optiona +request+ which may have already been handled and can be opted out
|
|
855
|
+
# in the state recovery process.
|
|
853
856
|
def handle_error(error, request = nil)
|
|
854
857
|
parser.handle_error(error, request) if @parser && @parser.respond_to?(:handle_error)
|
|
855
858
|
while (req = @pending.shift)
|
data/lib/httpx/io/tcp.rb
CHANGED
|
@@ -111,7 +111,7 @@ module HTTPX
|
|
|
111
111
|
raise e if @ip_index.negative?
|
|
112
112
|
|
|
113
113
|
log { "failed connecting to #{@ip} (#{e.message}), evict from cache and trying next..." }
|
|
114
|
-
|
|
114
|
+
@options.resolver_cache.evict(@hostname, @ip)
|
|
115
115
|
|
|
116
116
|
@io = build_socket
|
|
117
117
|
retry
|
data/lib/httpx/options.rb
CHANGED
|
@@ -12,6 +12,7 @@ module HTTPX
|
|
|
12
12
|
CLOSE_HANDSHAKE_TIMEOUT = 10
|
|
13
13
|
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
|
14
14
|
REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
|
|
15
|
+
RESOLVER_TYPES = %i[memory file].freeze
|
|
15
16
|
|
|
16
17
|
# default value used for "user-agent" header, when not overridden.
|
|
17
18
|
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
@@ -44,7 +45,7 @@ module HTTPX
|
|
|
44
45
|
|
|
45
46
|
return unless meth =~ /^option_(.+)$/
|
|
46
47
|
|
|
47
|
-
optname = Regexp.last_match(1)
|
|
48
|
+
optname = Regexp.last_match(1) #: String
|
|
48
49
|
|
|
49
50
|
if optname =~ /^(.+[^_])_+with/
|
|
50
51
|
# ignore alias method chain generated methods.
|
|
@@ -52,14 +53,14 @@ module HTTPX
|
|
|
52
53
|
# it relies on the "_with/_without" separator, which is the most used convention,
|
|
53
54
|
# however it shouldn't be used in practice in httpx given the plugin architecture
|
|
54
55
|
# as the main extension API.
|
|
55
|
-
orig_name = Regexp.last_match(1)
|
|
56
|
+
orig_name = Regexp.last_match(1) #: String
|
|
56
57
|
|
|
57
58
|
return if @options_names.include?(orig_name.to_sym)
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
optname = optname.to_sym
|
|
61
62
|
|
|
62
|
-
attr_reader(optname)
|
|
63
|
+
attr_reader(optname) unless method_defined?(optname)
|
|
63
64
|
|
|
64
65
|
@options_names << optname unless @options_names.include?(optname)
|
|
65
66
|
end
|
|
@@ -103,7 +104,10 @@ module HTTPX
|
|
|
103
104
|
# :io :: open socket, or domain/ip-to-socket hash, which requests should be sent to
|
|
104
105
|
# :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
|
|
105
106
|
# :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
|
|
106
|
-
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class
|
|
107
|
+
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class inheriting
|
|
108
|
+
# from HTTPX::Resolver::Resolver)
|
|
109
|
+
# :resolver_cache :: strategy to cache DNS results, ignored by the <tt>:system</tt> resolver, can be set to <tt>:memory<tt>
|
|
110
|
+
# or an instance of a custom class inheriting from HTTPX::Resolver::Cache::Base
|
|
107
111
|
# :resolver_options :: hash of options passed to the resolver. Accepted keys depend on the resolver type.
|
|
108
112
|
# :pool_options :: hash of options passed to the connection pool (See Pool#initialize).
|
|
109
113
|
# :ip_families :: which socket families are supported (system-dependent)
|
|
@@ -151,6 +155,36 @@ module HTTPX
|
|
|
151
155
|
freeze
|
|
152
156
|
end
|
|
153
157
|
|
|
158
|
+
# returns the class with which to instantiate the DNS resolver.
|
|
159
|
+
def resolver_class
|
|
160
|
+
case @resolver_class
|
|
161
|
+
when Symbol
|
|
162
|
+
public_send(:"resolver_#{@resolver_class}_class")
|
|
163
|
+
else
|
|
164
|
+
@resolver_class
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def resolver_cache
|
|
169
|
+
cache_type = @resolver_cache
|
|
170
|
+
|
|
171
|
+
case cache_type
|
|
172
|
+
when :memory
|
|
173
|
+
Resolver::Cache::Memory.cache(cache_type)
|
|
174
|
+
when :file
|
|
175
|
+
Resolver::Cache::File.cache(cache_type)
|
|
176
|
+
else
|
|
177
|
+
unless cache_type.respond_to?(:resolve) &&
|
|
178
|
+
cache_type.respond_to?(:get) &&
|
|
179
|
+
cache_type.respond_to?(:set) &&
|
|
180
|
+
cache_type.respond_to?(:evict)
|
|
181
|
+
raise TypeError, ":resolver_cache must be a compatible resolver cache and implement #get, #set and #evict"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
cache_type #: Object & Resolver::_Cache
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
154
188
|
def freeze
|
|
155
189
|
self.class.options_names.each do |ivar|
|
|
156
190
|
# avoid freezing debug option, as when it's set, it's usually an
|
|
@@ -172,6 +206,7 @@ module HTTPX
|
|
|
172
206
|
super || options_equals?(other)
|
|
173
207
|
end
|
|
174
208
|
|
|
209
|
+
# checks whether +other+ is equal by comparing the session options
|
|
175
210
|
def options_equals?(other, ignore_ivars = REQUEST_BODY_IVARS)
|
|
176
211
|
# headers and other request options do not play a role, as they are
|
|
177
212
|
# relevant only for the request.
|
|
@@ -187,6 +222,8 @@ module HTTPX
|
|
|
187
222
|
end
|
|
188
223
|
end
|
|
189
224
|
|
|
225
|
+
# returns a HTTPX::Options instance resulting of the merging of +other+ with self.
|
|
226
|
+
# it may return self if +other+ is self or equal to self.
|
|
190
227
|
def merge(other)
|
|
191
228
|
if (is_options = other.is_a?(Options))
|
|
192
229
|
|
|
@@ -361,7 +398,7 @@ module HTTPX
|
|
|
361
398
|
request_class response_class headers_class request_body_class
|
|
362
399
|
response_body_class connection_class http1_class http2_class
|
|
363
400
|
resolver_native_class resolver_system_class resolver_https_class options_class pool_class
|
|
364
|
-
io fallback_protocol debug debug_redact
|
|
401
|
+
io fallback_protocol debug debug_redact
|
|
365
402
|
compress_request_body decompress_response_body
|
|
366
403
|
persistent close_on_fork
|
|
367
404
|
].each do |method_name|
|
|
@@ -421,6 +458,41 @@ module HTTPX
|
|
|
421
458
|
Array(value)
|
|
422
459
|
end
|
|
423
460
|
|
|
461
|
+
def option_resolver_class(resolver_type)
|
|
462
|
+
case resolver_type
|
|
463
|
+
when Symbol
|
|
464
|
+
meth = :"resolver_#{resolver_type}_class"
|
|
465
|
+
|
|
466
|
+
raise TypeError, ":resolver_class must be a supported type" unless respond_to?(meth)
|
|
467
|
+
|
|
468
|
+
resolver_type
|
|
469
|
+
when Class
|
|
470
|
+
raise TypeError, ":resolver_class must be a subclass of `#{Resolver::Resolver}`" unless resolver_type < Resolver::Resolver
|
|
471
|
+
|
|
472
|
+
resolver_type
|
|
473
|
+
else
|
|
474
|
+
raise TypeError, ":resolver_class must be a supported type"
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def option_resolver_cache(cache_type)
|
|
479
|
+
if cache_type.is_a?(Symbol)
|
|
480
|
+
raise TypeError, ":resolver_cache: #{cache_type} is invalid" unless RESOLVER_TYPES.include?(cache_type)
|
|
481
|
+
|
|
482
|
+
require "httpx/resolver/cache/file" if cache_type == :file
|
|
483
|
+
|
|
484
|
+
else
|
|
485
|
+
unless cache_type.respond_to?(:resolve) &&
|
|
486
|
+
cache_type.respond_to?(:get) &&
|
|
487
|
+
cache_type.respond_to?(:set) &&
|
|
488
|
+
cache_type.respond_to?(:evict)
|
|
489
|
+
raise TypeError, ":resolver_cache must be a compatible resolver cache and implement #resolve, #get, #set and #evict"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
cache_type
|
|
494
|
+
end
|
|
495
|
+
|
|
424
496
|
# called after all options are initialized
|
|
425
497
|
def do_initialize
|
|
426
498
|
hs = @headers
|
|
@@ -496,6 +568,7 @@ module HTTPX
|
|
|
496
568
|
:addresses => nil,
|
|
497
569
|
:persistent => false,
|
|
498
570
|
:resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
|
|
571
|
+
:resolver_cache => (ENV["HTTPX_RESOLVER_CACHE"] || :memory).to_sym,
|
|
499
572
|
:resolver_options => { cache: true }.freeze,
|
|
500
573
|
:pool_options => EMPTY_HASH,
|
|
501
574
|
:ip_families => nil,
|
data/lib/httpx/parser/http1.rb
CHANGED
data/lib/httpx/plugins/auth.rb
CHANGED
|
@@ -16,6 +16,13 @@ module HTTPX
|
|
|
16
16
|
}
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# adds support for the following options:
|
|
20
|
+
#
|
|
21
|
+
# :auth_header_value :: the token to use as a string, or a callable which returns a string when called.
|
|
22
|
+
# :auth_header_type :: the authentication type to use in the "authorization" header value (i.e. "Bearer", "Digest"...)
|
|
23
|
+
# :generate_auth_value_on_retry :: callable which returns whether the request should regenerate the auth_header_value
|
|
24
|
+
# when the request is retried (this option will only work if the session also loads the
|
|
25
|
+
# <tt>:retries</tt> plugin).
|
|
19
26
|
module OptionsMethods
|
|
20
27
|
def option_auth_header_value(value)
|
|
21
28
|
value
|
|
@@ -74,10 +81,14 @@ module HTTPX
|
|
|
74
81
|
def generate_auth_token
|
|
75
82
|
return unless (auth_value = @options.auth_header_value)
|
|
76
83
|
|
|
77
|
-
auth_value = auth_value.call(self) if
|
|
84
|
+
auth_value = auth_value.call(self) if dynamic_auth_token?(auth_value)
|
|
78
85
|
|
|
79
86
|
auth_value
|
|
80
87
|
end
|
|
88
|
+
|
|
89
|
+
def dynamic_auth_token?(auth_header_value)
|
|
90
|
+
auth_header_value&.respond_to?(:call)
|
|
91
|
+
end
|
|
81
92
|
end
|
|
82
93
|
|
|
83
94
|
module RequestMethods
|
|
@@ -92,14 +103,29 @@ module HTTPX
|
|
|
92
103
|
|
|
93
104
|
module AuthRetries
|
|
94
105
|
module InstanceMethods
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def retryable_request?(request, response, options)
|
|
109
|
+
super || auth_error?(response, options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def retryable_response?(response, options)
|
|
113
|
+
auth_error?(response, options) || super
|
|
114
|
+
end
|
|
115
|
+
|
|
95
116
|
def prepare_to_retry(request, response)
|
|
96
117
|
super
|
|
97
118
|
|
|
98
|
-
return unless
|
|
119
|
+
return unless auth_error?(response, request.options) ||
|
|
120
|
+
(@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))
|
|
99
121
|
|
|
100
122
|
request.headers.get("authorization").pop
|
|
101
123
|
@auth_header_value = generate_auth_token
|
|
102
124
|
end
|
|
125
|
+
|
|
126
|
+
def auth_error?(response, options)
|
|
127
|
+
response.is_a?(Response) && response.status == 401 && dynamic_auth_token?(options.auth_header_value)
|
|
128
|
+
end
|
|
103
129
|
end
|
|
104
130
|
end
|
|
105
131
|
end
|
data/lib/httpx/plugins/oauth.rb
CHANGED
|
@@ -180,6 +180,10 @@ module HTTPX
|
|
|
180
180
|
end
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
+
# adds support for the following options:
|
|
184
|
+
#
|
|
185
|
+
# :oauth_options :: an hash of options to be used during session management.
|
|
186
|
+
# check the parameters to initialize the OAuthSession class.
|
|
183
187
|
module OptionsMethods
|
|
184
188
|
private
|
|
185
189
|
|
|
@@ -255,29 +259,18 @@ module HTTPX
|
|
|
255
259
|
|
|
256
260
|
@oauth_session.fetch_access_token(self)
|
|
257
261
|
end
|
|
258
|
-
end
|
|
259
262
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def extra_options(options)
|
|
263
|
-
options.merge(
|
|
264
|
-
retry_on: method(:response_oauth_error?),
|
|
265
|
-
generate_auth_value_on_retry: method(:response_oauth_error?)
|
|
266
|
-
)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def response_oauth_error?(res)
|
|
270
|
-
res.is_a?(Response) && res.status == 401
|
|
271
|
-
end
|
|
263
|
+
def dynamic_auth_token?(_)
|
|
264
|
+
@oauth_session
|
|
272
265
|
end
|
|
266
|
+
end
|
|
273
267
|
|
|
268
|
+
module OAuthRetries
|
|
274
269
|
module InstanceMethods
|
|
275
|
-
|
|
276
|
-
unless @oauth_session && @options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response)
|
|
277
|
-
return super
|
|
278
|
-
end
|
|
270
|
+
private
|
|
279
271
|
|
|
280
|
-
|
|
272
|
+
def prepare_to_retry(_request, response)
|
|
273
|
+
@oauth_session.reset! if @oauth_session
|
|
281
274
|
|
|
282
275
|
super
|
|
283
276
|
end
|
|
@@ -55,10 +55,8 @@ module HTTPX
|
|
|
55
55
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
|
-
def
|
|
58
|
+
def retryable_request?(request, response, *)
|
|
59
59
|
super || begin
|
|
60
|
-
response = request.response
|
|
61
|
-
|
|
62
60
|
return false unless response && response.is_a?(ErrorResponse)
|
|
63
61
|
|
|
64
62
|
error = response.error
|
|
@@ -67,13 +65,13 @@ module HTTPX
|
|
|
67
65
|
end
|
|
68
66
|
end
|
|
69
67
|
|
|
70
|
-
def retryable_error?(ex)
|
|
68
|
+
def retryable_error?(ex, options)
|
|
71
69
|
super &&
|
|
72
70
|
# under the persistent plugin rules, requests are only retried for connection related errors,
|
|
73
71
|
# which do not include request timeout related errors. This only gets overriden if the end user
|
|
74
72
|
# manually changed +:max_retries+ to something else, which means it is aware of the
|
|
75
73
|
# consequences.
|
|
76
|
-
(!ex.is_a?(RequestTimeoutError) ||
|
|
74
|
+
(!ex.is_a?(RequestTimeoutError) || options.max_retries != 1)
|
|
77
75
|
end
|
|
78
76
|
end
|
|
79
77
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
data/lib/httpx/plugins/query.rb
CHANGED
|
@@ -12,22 +12,11 @@ module HTTPX
|
|
|
12
12
|
# https://gitlab.com/os85/httpx/wikis/Rate-Limiter
|
|
13
13
|
#
|
|
14
14
|
module RateLimiter
|
|
15
|
-
|
|
16
|
-
RATE_LIMIT_CODES = [429, 503].freeze
|
|
17
|
-
|
|
18
|
-
def configure(klass)
|
|
19
|
-
klass.plugin(:retries,
|
|
20
|
-
retry_change_requests: true,
|
|
21
|
-
retry_on: method(:retry_on_rate_limited_response?),
|
|
22
|
-
retry_after: method(:retry_after_rate_limit))
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def retry_on_rate_limited_response?(response)
|
|
26
|
-
return false unless response.is_a?(Response)
|
|
15
|
+
RATE_LIMIT_CODES = [429, 503].freeze
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
class << self
|
|
18
|
+
def load_dependencies(klass)
|
|
19
|
+
klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
|
|
31
20
|
end
|
|
32
21
|
|
|
33
22
|
# Servers send the "Retry-After" header field to indicate how long the
|
|
@@ -48,6 +37,22 @@ module HTTPX
|
|
|
48
37
|
Utils.parse_retry_after(retry_after)
|
|
49
38
|
end
|
|
50
39
|
end
|
|
40
|
+
|
|
41
|
+
module InstanceMethods
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def retryable_request?(request, response, options)
|
|
45
|
+
super || rate_limit_error?(response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def retryable_response?(response, options)
|
|
49
|
+
rate_limit_error?(response) || super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def rate_limit_error?(response)
|
|
53
|
+
response.is_a?(Response) && RATE_LIMIT_CODES.include?(response.status)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
register_plugin :rate_limiter, RateLimiter
|
|
@@ -142,15 +142,8 @@ module HTTPX
|
|
|
142
142
|
|
|
143
143
|
if response &&
|
|
144
144
|
request.retries.positive? &&
|
|
145
|
-
|
|
146
|
-
(
|
|
147
|
-
(
|
|
148
|
-
response.is_a?(ErrorResponse) && retryable_error?(response.error)
|
|
149
|
-
) ||
|
|
150
|
-
|
|
151
|
-
options.retry_on&.call(response)
|
|
152
|
-
|
|
153
|
-
)
|
|
145
|
+
retryable_request?(request, response, options) &&
|
|
146
|
+
retryable_response?(response, options)
|
|
154
147
|
try_partial_retry(request, response)
|
|
155
148
|
log { "failed to get response, #{request.retries} tries to go..." }
|
|
156
149
|
prepare_to_retry(request, response)
|
|
@@ -186,12 +179,16 @@ module HTTPX
|
|
|
186
179
|
end
|
|
187
180
|
|
|
188
181
|
# returns whether +request+ can be retried.
|
|
189
|
-
def
|
|
182
|
+
def retryable_request?(request, _, options)
|
|
190
183
|
IDEMPOTENT_METHODS.include?(request.verb) || options.retry_change_requests
|
|
191
184
|
end
|
|
192
185
|
|
|
186
|
+
def retryable_response?(response, options)
|
|
187
|
+
(response.is_a?(ErrorResponse) && retryable_error?(response.error, options)) || options.retry_on&.call(response)
|
|
188
|
+
end
|
|
189
|
+
|
|
193
190
|
# returns whether the +ex+ exception happend for a retriable request.
|
|
194
|
-
def retryable_error?(ex)
|
|
191
|
+
def retryable_error?(ex, _)
|
|
195
192
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
|
|
196
193
|
end
|
|
197
194
|
|
data/lib/httpx/plugins/stream.rb
CHANGED
|
@@ -164,7 +164,7 @@ module HTTPX
|
|
|
164
164
|
|
|
165
165
|
unless request.options.stream && !request.stream
|
|
166
166
|
if options[:stream]
|
|
167
|
-
warn "passing `stream: true` with a request
|
|
167
|
+
warn "passing `stream: true` with a request object is not supported anymore. " \
|
|
168
168
|
"You can instead build the request object with `stream :true`"
|
|
169
169
|
end
|
|
170
170
|
return super
|
|
@@ -217,7 +217,7 @@ module HTTPX
|
|
|
217
217
|
|
|
218
218
|
@stream.on_chunk(chunk.dup)
|
|
219
219
|
|
|
220
|
-
chunk.
|
|
220
|
+
chunk.bytesize
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
private
|
|
@@ -278,6 +278,8 @@ module HTTPX
|
|
|
278
278
|
headers_sent = @headers_sent
|
|
279
279
|
|
|
280
280
|
case nextstate
|
|
281
|
+
when :idle
|
|
282
|
+
headers_sent = false
|
|
281
283
|
when :waiting_for_chunk
|
|
282
284
|
return unless @state == :body
|
|
283
285
|
when :body
|
|
@@ -325,7 +327,17 @@ module HTTPX
|
|
|
325
327
|
module RequestBodyMethods
|
|
326
328
|
def initialize(*, **)
|
|
327
329
|
super
|
|
328
|
-
|
|
330
|
+
|
|
331
|
+
return unless @options.stream
|
|
332
|
+
|
|
333
|
+
@headers.delete("content-length")
|
|
334
|
+
|
|
335
|
+
return unless @body
|
|
336
|
+
|
|
337
|
+
return if @body.is_a?(Transcoder::Body::Encoder)
|
|
338
|
+
|
|
339
|
+
raise Error, "bidirectional streams only allow the usage of the `:body` param to set request bodies." \
|
|
340
|
+
"You must encode it yourself if you wish to do so."
|
|
329
341
|
end
|
|
330
342
|
|
|
331
343
|
def empty?
|
data/lib/httpx/pool.rb
CHANGED
data/lib/httpx/request/body.rb
CHANGED
|
@@ -128,7 +128,7 @@ module HTTPX
|
|
|
128
128
|
Transcoder::Body.encode(body)
|
|
129
129
|
elsif (form = params.delete(:form))
|
|
130
130
|
if Transcoder::Multipart.multipart?(form)
|
|
131
|
-
# @type var form: Transcoder::multipart_input
|
|
131
|
+
# @type var form: Transcoder::Multipart::multipart_input
|
|
132
132
|
Transcoder::Multipart.encode(form)
|
|
133
133
|
else
|
|
134
134
|
# @type var form: Transcoder::urlencoded_input
|