httpx 0.17.0 → 0.18.3
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 +4 -3
- data/doc/release_notes/0_18_0.md +69 -0
- data/doc/release_notes/0_18_1.md +12 -0
- data/doc/release_notes/0_18_2.md +10 -0
- data/doc/release_notes/0_18_3.md +7 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +5 -3
- data/lib/httpx/adapters/webmock.rb +7 -1
- data/lib/httpx/altsvc.rb +2 -2
- data/lib/httpx/chainable.rb +3 -3
- data/lib/httpx/connection/http1.rb +8 -5
- data/lib/httpx/connection/http2.rb +22 -7
- data/lib/httpx/connection.rb +70 -71
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/extensions.rb +50 -4
- data/lib/httpx/io/ssl.rb +5 -1
- data/lib/httpx/io/tls.rb +7 -7
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +7 -7
- data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
- data/lib/httpx/plugins/aws_sigv4.rb +9 -11
- data/lib/httpx/plugins/compression.rb +5 -3
- data/lib/httpx/plugins/cookies/jar.rb +1 -1
- data/lib/httpx/plugins/expect.rb +7 -3
- data/lib/httpx/plugins/grpc/message.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +3 -3
- data/lib/httpx/plugins/internal_telemetry.rb +8 -8
- data/lib/httpx/plugins/multipart.rb +2 -2
- data/lib/httpx/plugins/response_cache/store.rb +55 -0
- data/lib/httpx/plugins/response_cache.rb +88 -0
- data/lib/httpx/plugins/retries.rb +36 -14
- data/lib/httpx/plugins/stream.rb +1 -1
- data/lib/httpx/pool.rb +39 -13
- data/lib/httpx/request.rb +7 -7
- data/lib/httpx/resolver/https.rb +5 -7
- data/lib/httpx/resolver/native.rb +4 -2
- data/lib/httpx/resolver/system.rb +2 -0
- data/lib/httpx/resolver.rb +2 -2
- data/lib/httpx/response.rb +23 -14
- data/lib/httpx/selector.rb +12 -17
- data/lib/httpx/session.rb +7 -2
- data/lib/httpx/session2.rb +1 -1
- data/lib/httpx/timers.rb +84 -0
- data/lib/httpx/transcoder/body.rb +2 -1
- data/lib/httpx/transcoder/form.rb +1 -1
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/utils.rb +8 -0
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/chainable.rbs +1 -0
- data/sig/connection/http1.rbs +5 -0
- data/sig/connection/http2.rbs +3 -0
- data/sig/connection.rbs +12 -6
- data/sig/plugins/aws_sdk_authentication.rbs +22 -4
- data/sig/plugins/response_cache.rbs +35 -0
- data/sig/plugins/retries.rbs +3 -0
- data/sig/pool.rbs +6 -0
- data/sig/resolver/native.rbs +3 -4
- data/sig/resolver/system.rbs +2 -0
- data/sig/response.rbs +3 -2
- data/sig/timers.rbs +32 -0
- data/sig/utils.rbs +4 -0
- metadata +17 -17
@@ -12,19 +12,29 @@ module HTTPX
|
|
12
12
|
# TODO: pass max_retries in a configure/load block
|
13
13
|
|
14
14
|
IDEMPOTENT_METHODS = %i[get options head put delete].freeze
|
15
|
-
RETRYABLE_ERRORS = [
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
RETRYABLE_ERRORS = [
|
16
|
+
IOError,
|
17
|
+
EOFError,
|
18
|
+
Errno::ECONNRESET,
|
19
|
+
Errno::ECONNABORTED,
|
20
|
+
Errno::EPIPE,
|
21
|
+
Errno::EINVAL,
|
22
|
+
Errno::ETIMEDOUT,
|
23
|
+
Parser::Error,
|
24
|
+
TLSError,
|
25
|
+
TimeoutError,
|
26
|
+
Connection::HTTP2::GoawayError,
|
27
|
+
].freeze
|
28
|
+
DEFAULT_JITTER = ->(interval) { interval * (0.5 * (1 + rand)) }
|
29
|
+
|
30
|
+
if ENV.key?("HTTPX_NO_JITTER")
|
31
|
+
def self.extra_options(options)
|
32
|
+
options.merge(max_retries: MAX_RETRIES)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
def self.extra_options(options)
|
36
|
+
options.merge(max_retries: MAX_RETRIES, retry_jitter: DEFAULT_JITTER)
|
37
|
+
end
|
28
38
|
end
|
29
39
|
|
30
40
|
module OptionsMethods
|
@@ -38,6 +48,13 @@ module HTTPX
|
|
38
48
|
value
|
39
49
|
end
|
40
50
|
|
51
|
+
def option_retry_jitter(value)
|
52
|
+
# return early if callable
|
53
|
+
raise TypeError, ":retry_jitter must be callable" unless value.respond_to?(:call)
|
54
|
+
|
55
|
+
value
|
56
|
+
end
|
57
|
+
|
41
58
|
def option_max_retries(value)
|
42
59
|
num = Integer(value)
|
43
60
|
raise TypeError, ":max_retries must be positive" unless num.positive?
|
@@ -87,10 +104,15 @@ module HTTPX
|
|
87
104
|
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
88
105
|
|
89
106
|
if retry_after
|
107
|
+
# apply jitter
|
108
|
+
if (jitter = request.options.retry_jitter)
|
109
|
+
retry_after = jitter.call(retry_after)
|
110
|
+
end
|
90
111
|
|
112
|
+
retry_start = Utils.now
|
91
113
|
log { "retrying after #{retry_after} secs..." }
|
92
114
|
pool.after(retry_after) do
|
93
|
-
log { "retrying!!" }
|
115
|
+
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
94
116
|
connection = find_connection(request, connections, options)
|
95
117
|
connection.send(request)
|
96
118
|
end
|
data/lib/httpx/plugins/stream.rb
CHANGED
data/lib/httpx/pool.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "forwardable"
|
4
|
-
require "timers"
|
5
4
|
require "httpx/selector"
|
6
5
|
require "httpx/connection"
|
7
6
|
require "httpx/resolver"
|
8
7
|
|
9
8
|
module HTTPX
|
10
9
|
class Pool
|
10
|
+
using ArrayExtensions
|
11
11
|
extend Forwardable
|
12
12
|
|
13
13
|
def_delegator :@timers, :after
|
@@ -15,7 +15,7 @@ module HTTPX
|
|
15
15
|
def initialize
|
16
16
|
@resolvers = {}
|
17
17
|
@_resolver_ios = {}
|
18
|
-
@timers = Timers
|
18
|
+
@timers = Timers.new
|
19
19
|
@selector = Selector.new
|
20
20
|
@connections = []
|
21
21
|
@connected_connections = 0
|
@@ -27,15 +27,18 @@ module HTTPX
|
|
27
27
|
|
28
28
|
def next_tick
|
29
29
|
catch(:jump_tick) do
|
30
|
-
timeout =
|
30
|
+
timeout = next_timeout
|
31
31
|
if timeout && timeout.negative?
|
32
32
|
@timers.fire
|
33
33
|
throw(:jump_tick)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
begin
|
37
|
+
@selector.select(timeout, &:call)
|
38
|
+
@timers.fire
|
39
|
+
rescue TimeoutError => e
|
40
|
+
@timers.fire(e)
|
41
|
+
end
|
39
42
|
end
|
40
43
|
rescue StandardError => e
|
41
44
|
@connections.each do |connection|
|
@@ -64,6 +67,16 @@ module HTTPX
|
|
64
67
|
connection.on(:open) do
|
65
68
|
@connected_connections += 1
|
66
69
|
end
|
70
|
+
connection.on(:activate) do
|
71
|
+
select_connection(connection)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def deactivate(connections)
|
76
|
+
connections.each do |connection|
|
77
|
+
connection.deactivate
|
78
|
+
deselect_connection(connection) if connection.state == :inactive
|
79
|
+
end
|
67
80
|
end
|
68
81
|
|
69
82
|
# opens a connection to the IP reachable through +uri+.
|
@@ -81,7 +94,7 @@ module HTTPX
|
|
81
94
|
def resolve_connection(connection)
|
82
95
|
@connections << connection unless @connections.include?(connection)
|
83
96
|
|
84
|
-
if connection.addresses || connection.
|
97
|
+
if connection.addresses || connection.open?
|
85
98
|
#
|
86
99
|
# there are two cases in which we want to activate initialization of
|
87
100
|
# connection immediately:
|
@@ -98,7 +111,7 @@ module HTTPX
|
|
98
111
|
resolver << connection
|
99
112
|
return if resolver.empty?
|
100
113
|
|
101
|
-
@_resolver_ios[resolver] ||=
|
114
|
+
@_resolver_ios[resolver] ||= select_connection(resolver)
|
102
115
|
end
|
103
116
|
|
104
117
|
def on_resolver_connection(connection)
|
@@ -107,7 +120,7 @@ module HTTPX
|
|
107
120
|
end
|
108
121
|
return register_connection(connection) unless found_connection
|
109
122
|
|
110
|
-
if found_connection.
|
123
|
+
if found_connection.open?
|
111
124
|
coalesce_connections(found_connection, connection)
|
112
125
|
throw(:coalesced, found_connection)
|
113
126
|
else
|
@@ -129,7 +142,7 @@ module HTTPX
|
|
129
142
|
|
130
143
|
@resolvers.delete(resolver_type)
|
131
144
|
|
132
|
-
|
145
|
+
deselect_connection(resolver)
|
133
146
|
@_resolver_ios.delete(resolver)
|
134
147
|
resolver.close unless resolver.closed?
|
135
148
|
end
|
@@ -140,7 +153,7 @@ module HTTPX
|
|
140
153
|
# consider it connected already.
|
141
154
|
@connected_connections += 1
|
142
155
|
end
|
143
|
-
|
156
|
+
select_connection(connection)
|
144
157
|
connection.on(:close) do
|
145
158
|
unregister_connection(connection)
|
146
159
|
end
|
@@ -148,10 +161,18 @@ module HTTPX
|
|
148
161
|
|
149
162
|
def unregister_connection(connection)
|
150
163
|
@connections.delete(connection)
|
151
|
-
|
164
|
+
deselect_connection(connection)
|
152
165
|
@connected_connections -= 1
|
153
166
|
end
|
154
167
|
|
168
|
+
def select_connection(connection)
|
169
|
+
@selector.register(connection)
|
170
|
+
end
|
171
|
+
|
172
|
+
def deselect_connection(connection)
|
173
|
+
@selector.deregister(connection)
|
174
|
+
end
|
175
|
+
|
155
176
|
def coalesce_connections(conn1, conn2)
|
156
177
|
if conn1.coalescable?(conn2)
|
157
178
|
conn1.merge(conn2)
|
@@ -162,7 +183,11 @@ module HTTPX
|
|
162
183
|
end
|
163
184
|
|
164
185
|
def next_timeout
|
165
|
-
|
186
|
+
[
|
187
|
+
@timers.wait_interval,
|
188
|
+
*@resolvers.values.reject(&:closed?).filter_map(&:timeout),
|
189
|
+
*@connections.filter_map(&:timeout),
|
190
|
+
].compact.min
|
166
191
|
end
|
167
192
|
|
168
193
|
def find_resolver_for(connection)
|
@@ -172,6 +197,7 @@ module HTTPX
|
|
172
197
|
|
173
198
|
@resolvers[resolver_type] ||= begin
|
174
199
|
resolver = resolver_type.new(connection_options)
|
200
|
+
resolver.pool = self if resolver.respond_to?(:pool=)
|
175
201
|
resolver.on(:resolve, &method(:on_resolver_connection))
|
176
202
|
resolver.on(:error, &method(:on_resolver_error))
|
177
203
|
resolver.on(:close) { on_resolver_close(resolver) }
|
data/lib/httpx/request.rb
CHANGED
@@ -148,10 +148,10 @@ module HTTPX
|
|
148
148
|
# :nocov:
|
149
149
|
def inspect
|
150
150
|
"#<HTTPX::Request:#{object_id} " \
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
151
|
+
"#{@verb.to_s.upcase} " \
|
152
|
+
"#{uri} " \
|
153
|
+
"@headers=#{@headers} " \
|
154
|
+
"@body=#{@body}>"
|
155
155
|
end
|
156
156
|
# :nocov:
|
157
157
|
|
@@ -181,7 +181,7 @@ module HTTPX
|
|
181
181
|
end
|
182
182
|
|
183
183
|
def each(&block)
|
184
|
-
return enum_for(__method__) unless
|
184
|
+
return enum_for(__method__) unless block
|
185
185
|
return if @body.nil?
|
186
186
|
|
187
187
|
body = stream(@body)
|
@@ -236,7 +236,7 @@ module HTTPX
|
|
236
236
|
# :nocov:
|
237
237
|
def inspect
|
238
238
|
"#<HTTPX::Request::Body:#{object_id} " \
|
239
|
-
|
239
|
+
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
240
240
|
end
|
241
241
|
# :nocov:
|
242
242
|
end
|
@@ -285,7 +285,7 @@ module HTTPX
|
|
285
285
|
end
|
286
286
|
|
287
287
|
def write(data)
|
288
|
-
@block.call(data)
|
288
|
+
@block.call(data.dup)
|
289
289
|
data.bytesize
|
290
290
|
end
|
291
291
|
end
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -24,7 +24,9 @@ module HTTPX
|
|
24
24
|
record_types: RECORD_TYPES.keys,
|
25
25
|
}.freeze
|
26
26
|
|
27
|
-
def_delegators :@resolver_connection, :connecting?, :to_io, :call, :close
|
27
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close
|
28
|
+
|
29
|
+
attr_writer :pool
|
28
30
|
|
29
31
|
def initialize(options)
|
30
32
|
@options = Options.new(options)
|
@@ -63,15 +65,11 @@ module HTTPX
|
|
63
65
|
|
64
66
|
private
|
65
67
|
|
66
|
-
def pool
|
67
|
-
Thread.current[:httpx_connection_pool] ||= Pool.new
|
68
|
-
end
|
69
|
-
|
70
68
|
def resolver_connection
|
71
|
-
@resolver_connection ||= pool.find_connection(@uri, @options) || begin
|
69
|
+
@resolver_connection ||= @pool.find_connection(@uri, @options) || begin
|
72
70
|
@building_connection = true
|
73
71
|
connection = @options.connection_class.new("ssl", @uri, @options.merge(ssl: { alpn_protocols: %w[h2] }))
|
74
|
-
pool.init_connection(connection, @options)
|
72
|
+
@pool.init_connection(connection, @options)
|
75
73
|
emit_addresses(connection, @uri_addresses)
|
76
74
|
@building_connection = false
|
77
75
|
connection
|
@@ -47,6 +47,8 @@ module HTTPX
|
|
47
47
|
|
48
48
|
def_delegator :@connections, :empty?
|
49
49
|
|
50
|
+
attr_reader :state
|
51
|
+
|
50
52
|
def initialize(options)
|
51
53
|
@options = Options.new(options)
|
52
54
|
@ns_index = 0
|
@@ -120,7 +122,7 @@ module HTTPX
|
|
120
122
|
def timeout
|
121
123
|
return if @connections.empty?
|
122
124
|
|
123
|
-
@start_timeout =
|
125
|
+
@start_timeout = Utils.now
|
124
126
|
hosts = @queries.keys
|
125
127
|
@timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
|
126
128
|
end
|
@@ -140,7 +142,7 @@ module HTTPX
|
|
140
142
|
def do_retry
|
141
143
|
return if @queries.empty?
|
142
144
|
|
143
|
-
loop_time =
|
145
|
+
loop_time = Utils.elapsed_time(@start_timeout)
|
144
146
|
connections = []
|
145
147
|
queries = {}
|
146
148
|
while (query = @queries.shift)
|
data/lib/httpx/resolver.rb
CHANGED
@@ -26,14 +26,14 @@ module HTTPX
|
|
26
26
|
module_function
|
27
27
|
|
28
28
|
def cached_lookup(hostname)
|
29
|
-
now =
|
29
|
+
now = Utils.now
|
30
30
|
@lookup_mutex.synchronize do
|
31
31
|
lookup(hostname, now)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def cached_lookup_set(hostname, entries)
|
36
|
-
now =
|
36
|
+
now = Utils.now
|
37
37
|
entries.each do |entry|
|
38
38
|
entry["TTL"] += now
|
39
39
|
end
|
data/lib/httpx/response.rb
CHANGED
@@ -57,17 +57,23 @@ module HTTPX
|
|
57
57
|
# :nocov:
|
58
58
|
def inspect
|
59
59
|
"#<Response:#{object_id} "\
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
"HTTP/#{version} " \
|
61
|
+
"@status=#{@status} " \
|
62
|
+
"@headers=#{@headers} " \
|
63
|
+
"@body=#{@body.bytesize}>"
|
64
64
|
end
|
65
65
|
# :nocov:
|
66
66
|
|
67
|
-
def
|
67
|
+
def error
|
68
68
|
return if @status < 400
|
69
69
|
|
70
|
-
|
70
|
+
HTTPError.new(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
def raise_for_status
|
74
|
+
return self unless (err = error)
|
75
|
+
|
76
|
+
raise err
|
71
77
|
end
|
72
78
|
|
73
79
|
def json(options = nil)
|
@@ -211,18 +217,20 @@ module HTTPX
|
|
211
217
|
end
|
212
218
|
|
213
219
|
def ==(other)
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
220
|
+
object_id == other.object_id || begin
|
221
|
+
if other.respond_to?(:read)
|
222
|
+
_with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
|
223
|
+
else
|
224
|
+
to_s == other.to_s
|
225
|
+
end
|
218
226
|
end
|
219
227
|
end
|
220
228
|
|
221
229
|
# :nocov:
|
222
230
|
def inspect
|
223
231
|
"#<HTTPX::Response::Body:#{object_id} " \
|
224
|
-
|
225
|
-
|
232
|
+
"@state=#{@state} " \
|
233
|
+
"@length=#{@length}>"
|
226
234
|
end
|
227
235
|
# :nocov:
|
228
236
|
|
@@ -311,17 +319,18 @@ module HTTPX
|
|
311
319
|
end
|
312
320
|
|
313
321
|
def status
|
322
|
+
warn ":#{__method__} is deprecated, use :error.message instead"
|
314
323
|
@error.message
|
315
324
|
end
|
316
325
|
|
317
326
|
if Exception.method_defined?(:full_message)
|
318
327
|
def to_s
|
319
|
-
@error.full_message
|
328
|
+
@error.full_message(highlight: false)
|
320
329
|
end
|
321
330
|
else
|
322
331
|
def to_s
|
323
332
|
"#{@error.message} (#{@error.class})\n" \
|
324
|
-
|
333
|
+
"#{@error.backtrace.join("\n") if @error.backtrace}"
|
325
334
|
end
|
326
335
|
end
|
327
336
|
|
data/lib/httpx/selector.rb
CHANGED
@@ -2,20 +2,6 @@
|
|
2
2
|
|
3
3
|
require "io/wait"
|
4
4
|
|
5
|
-
module IOExtensions
|
6
|
-
refine IO do
|
7
|
-
# provides a fallback for rubies where IO#wait isn't implemented,
|
8
|
-
# but IO#wait_readable and IO#wait_writable are.
|
9
|
-
def wait(timeout = nil, _mode = :read_write)
|
10
|
-
r, w = IO.select([self], [self], nil, timeout)
|
11
|
-
|
12
|
-
return unless r || w
|
13
|
-
|
14
|
-
self
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
5
|
class HTTPX::Selector
|
20
6
|
READABLE = %i[rw r].freeze
|
21
7
|
WRITABLE = %i[rw w].freeze
|
@@ -23,7 +9,7 @@ class HTTPX::Selector
|
|
23
9
|
private_constant :READABLE
|
24
10
|
private_constant :WRITABLE
|
25
11
|
|
26
|
-
using IOExtensions
|
12
|
+
using HTTPX::IOExtensions
|
27
13
|
|
28
14
|
def initialize
|
29
15
|
@selectables = []
|
@@ -58,11 +44,13 @@ class HTTPX::Selector
|
|
58
44
|
selectables = @selectables
|
59
45
|
@selectables = []
|
60
46
|
|
61
|
-
selectables.
|
47
|
+
selectables.delete_if do |io|
|
62
48
|
interests = io.interests
|
63
49
|
|
64
50
|
(r ||= []) << io if READABLE.include?(interests)
|
65
51
|
(w ||= []) << io if WRITABLE.include?(interests)
|
52
|
+
|
53
|
+
io.state == :closed
|
66
54
|
end
|
67
55
|
|
68
56
|
if @selectables.empty?
|
@@ -70,7 +58,7 @@ class HTTPX::Selector
|
|
70
58
|
|
71
59
|
# do not run event loop if there's nothing to wait on.
|
72
60
|
# this might happen if connect failed and connection was unregistered.
|
73
|
-
return if (!r || r.empty?) && (!w || w.empty?)
|
61
|
+
return if (!r || r.empty?) && (!w || w.empty?) && !selectables.empty?
|
74
62
|
|
75
63
|
break
|
76
64
|
else
|
@@ -129,6 +117,13 @@ class HTTPX::Selector
|
|
129
117
|
end
|
130
118
|
|
131
119
|
def select(interval, &block)
|
120
|
+
# do not cause an infinite loop here.
|
121
|
+
#
|
122
|
+
# this may happen if timeout calculation actually triggered an error which causes
|
123
|
+
# the connections to be reaped (such as the total timeout error) before #select
|
124
|
+
# gets called.
|
125
|
+
return if interval.nil? && @selectables.empty?
|
126
|
+
|
132
127
|
return select_one(interval, &block) if @selectables.size == 1
|
133
128
|
|
134
129
|
select_many(interval, &block)
|
data/lib/httpx/session.rb
CHANGED
@@ -11,7 +11,7 @@ module HTTPX
|
|
11
11
|
@options = self.class.default_options.merge(options)
|
12
12
|
@responses = {}
|
13
13
|
@persistent = @options.persistent
|
14
|
-
wrap(&blk) if
|
14
|
+
wrap(&blk) if blk
|
15
15
|
end
|
16
16
|
|
17
17
|
def wrap
|
@@ -21,6 +21,7 @@ module HTTPX
|
|
21
21
|
yield self
|
22
22
|
ensure
|
23
23
|
@persistent = prev_persistent
|
24
|
+
close unless @persistent
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
@@ -226,7 +227,11 @@ module HTTPX
|
|
226
227
|
end
|
227
228
|
responses
|
228
229
|
ensure
|
229
|
-
|
230
|
+
if @persistent
|
231
|
+
pool.deactivate(connections)
|
232
|
+
else
|
233
|
+
close(connections)
|
234
|
+
end
|
230
235
|
end
|
231
236
|
end
|
232
237
|
|
data/lib/httpx/session2.rb
CHANGED
data/lib/httpx/timers.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
class Timers
|
5
|
+
def initialize
|
6
|
+
@intervals = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def after(interval_in_secs, &blk)
|
10
|
+
return unless interval_in_secs
|
11
|
+
|
12
|
+
# I'm assuming here that most requests will have the same
|
13
|
+
# request timeout, as in most cases they share common set of
|
14
|
+
# options. A user setting different request timeouts for 100s of
|
15
|
+
# requests will already have a hard time dealing with that.
|
16
|
+
unless (interval = @intervals.find { |t| t == interval_in_secs })
|
17
|
+
interval = Interval.new(interval_in_secs)
|
18
|
+
@intervals << interval
|
19
|
+
@intervals.sort!
|
20
|
+
end
|
21
|
+
|
22
|
+
interval << blk
|
23
|
+
end
|
24
|
+
|
25
|
+
def wait_interval
|
26
|
+
return if @intervals.empty?
|
27
|
+
|
28
|
+
@next_interval_at = Utils.now
|
29
|
+
|
30
|
+
@intervals.first.interval
|
31
|
+
end
|
32
|
+
|
33
|
+
def fire(error = nil)
|
34
|
+
raise error if error && error.timeout != @intervals.first
|
35
|
+
return if @intervals.empty? || !@next_interval_at
|
36
|
+
|
37
|
+
elapsed_time = Utils.elapsed_time(@next_interval_at)
|
38
|
+
|
39
|
+
@intervals.delete_if { |interval| interval.elapse(elapsed_time) <= 0 }
|
40
|
+
end
|
41
|
+
|
42
|
+
def cancel
|
43
|
+
@intervals.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
class Interval
|
47
|
+
include Comparable
|
48
|
+
|
49
|
+
attr_reader :interval
|
50
|
+
|
51
|
+
def initialize(interval)
|
52
|
+
@interval = interval
|
53
|
+
@callbacks = []
|
54
|
+
end
|
55
|
+
|
56
|
+
def <=>(other)
|
57
|
+
@interval <=> other.interval
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
return @interval == other if other.is_a?(Numeric)
|
62
|
+
|
63
|
+
@interval == other.to_f # rubocop:disable Lint/FloatComparison
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_f
|
67
|
+
@interval
|
68
|
+
end
|
69
|
+
|
70
|
+
def <<(callback)
|
71
|
+
@callbacks << callback
|
72
|
+
end
|
73
|
+
|
74
|
+
def elapse(elapsed)
|
75
|
+
@interval -= elapsed
|
76
|
+
|
77
|
+
@callbacks.each(&:call) if @interval <= 0
|
78
|
+
|
79
|
+
@interval
|
80
|
+
end
|
81
|
+
end
|
82
|
+
private_constant :Interval
|
83
|
+
end
|
84
|
+
end
|
@@ -9,6 +9,7 @@ module HTTPX::Transcoder
|
|
9
9
|
module_function
|
10
10
|
|
11
11
|
class Encoder
|
12
|
+
using HTTPX::ArrayExtensions
|
12
13
|
extend Forwardable
|
13
14
|
|
14
15
|
def_delegator :@raw, :to_s
|
@@ -21,7 +22,7 @@ module HTTPX::Transcoder
|
|
21
22
|
if @raw.respond_to?(:bytesize)
|
22
23
|
@raw.bytesize
|
23
24
|
elsif @raw.respond_to?(:to_ary)
|
24
|
-
@raw.
|
25
|
+
@raw.sum(&:bytesize)
|
25
26
|
elsif @raw.respond_to?(:size)
|
26
27
|
@raw.size || Float::INFINITY
|
27
28
|
elsif @raw.respond_to?(:length)
|
@@ -50,7 +50,7 @@ module HTTPX::Transcoder
|
|
50
50
|
def decode(response)
|
51
51
|
content_type = response.content_type.mime_type
|
52
52
|
|
53
|
-
raise Error, "invalid form mime type (#{content_type})" unless content_type == "application/x-www-form-urlencoded"
|
53
|
+
raise HTTPX::Error, "invalid form mime type (#{content_type})" unless content_type == "application/x-www-form-urlencoded"
|
54
54
|
|
55
55
|
Decoder
|
56
56
|
end
|
@@ -35,7 +35,7 @@ module HTTPX::Transcoder
|
|
35
35
|
def decode(response)
|
36
36
|
content_type = response.content_type.mime_type
|
37
37
|
|
38
|
-
raise Error, "invalid json mime type (#{content_type})" unless JSON_REGEX.match?(content_type)
|
38
|
+
raise HTTPX::Error, "invalid json mime type (#{content_type})" unless JSON_REGEX.match?(content_type)
|
39
39
|
|
40
40
|
::JSON.method(:parse)
|
41
41
|
end
|
data/lib/httpx/utils.rb
CHANGED
@@ -6,6 +6,14 @@ module HTTPX
|
|
6
6
|
|
7
7
|
module_function
|
8
8
|
|
9
|
+
def now
|
10
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
11
|
+
end
|
12
|
+
|
13
|
+
def elapsed_time(monotonic_timestamp)
|
14
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - monotonic_timestamp
|
15
|
+
end
|
16
|
+
|
9
17
|
# The value of this field can be either an HTTP-date or a number of
|
10
18
|
# seconds to delay after the response is received.
|
11
19
|
def parse_retry_after(retry_after)
|
data/lib/httpx/version.rb
CHANGED