httpx 1.3.3 → 1.4.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_3_3.md +1 -1
- data/doc/release_notes/1_3_4.md +6 -0
- data/doc/release_notes/1_4_0.md +43 -0
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +11 -5
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +11 -7
- data/lib/httpx/connection.rb +128 -16
- data/lib/httpx/errors.rb +12 -0
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +26 -16
- data/lib/httpx/plugins/aws_sigv4.rb +31 -16
- data/lib/httpx/plugins/callbacks.rb +12 -2
- data/lib/httpx/plugins/circuit_breaker.rb +0 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/expect.rb +4 -3
- data/lib/httpx/plugins/follow_redirects.rb +7 -8
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +27 -0
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +17 -19
- data/lib/httpx/plugins/proxy.rb +91 -93
- data/lib/httpx/plugins/retries.rb +5 -8
- data/lib/httpx/plugins/upgrade.rb +5 -10
- data/lib/httpx/plugins/webdav.rb +6 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pool.rb +73 -244
- data/lib/httpx/request/body.rb +16 -12
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +12 -19
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +36 -13
- data/lib/httpx/resolver/resolver.rb +49 -11
- data/lib/httpx/resolver/system.rb +29 -11
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response/body.rb +12 -1
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +164 -95
- data/lib/httpx/session.rb +296 -139
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/multipart/encoder.rb +3 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder/utils/inflater.rb +2 -0
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +19 -20
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +14 -3
- data/sig/errors.rbs +6 -0
- data/sig/loggable.rbs +2 -0
- data/sig/options.rbs +7 -0
- data/sig/plugins/aws_sigv4.rbs +8 -2
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +9 -0
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -11
- data/sig/plugins/push_promise.rbs +3 -0
- data/sig/plugins/rate_limiter.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +25 -33
- data/sig/request/body.rbs +5 -1
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +0 -2
- data/sig/resolver/resolver.rbs +21 -2
- data/sig/resolver.rbs +5 -1
- data/sig/response/body.rbs +2 -2
- data/sig/response/buffer.rbs +2 -2
- data/sig/selector.rbs +30 -4
- data/sig/session.rbs +45 -18
- data/sig/transcoder/body.rbs +1 -1
- data/sig/transcoder/chunker.rbs +1 -1
- data/sig/transcoder/deflate.rbs +1 -0
- data/sig/transcoder/form.rbs +8 -0
- data/sig/transcoder/gzip.rbs +4 -1
- data/sig/transcoder/multipart.rbs +3 -3
- data/sig/transcoder/utils/body_reader.rbs +2 -2
- data/sig/transcoder/utils/deflater.rbs +2 -2
- metadata +12 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/selector.rb
CHANGED
@@ -2,137 +2,206 @@
|
|
2
2
|
|
3
3
|
require "io/wait"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
module HTTPX
|
6
|
+
class Selector
|
7
|
+
extend Forwardable
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
READABLE = %i[rw r].freeze
|
10
|
+
WRITABLE = %i[rw w].freeze
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
12
|
+
private_constant :READABLE
|
13
|
+
private_constant :WRITABLE
|
15
14
|
|
16
|
-
|
17
|
-
def deregister(io)
|
18
|
-
@selectables.delete(io)
|
19
|
-
end
|
15
|
+
def_delegator :@timers, :after
|
20
16
|
|
21
|
-
|
22
|
-
def register(io)
|
23
|
-
return if @selectables.include?(io)
|
17
|
+
def_delegator :@selectables, :empty?
|
24
18
|
|
25
|
-
|
26
|
-
|
19
|
+
def initialize
|
20
|
+
@timers = Timers.new
|
21
|
+
@selectables = []
|
22
|
+
end
|
27
23
|
|
28
|
-
|
24
|
+
def each(&blk)
|
25
|
+
@selectables.each(&blk)
|
26
|
+
end
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
def next_tick
|
29
|
+
catch(:jump_tick) do
|
30
|
+
timeout = next_timeout
|
31
|
+
if timeout && timeout.negative?
|
32
|
+
@timers.fire
|
33
|
+
throw(:jump_tick)
|
34
|
+
end
|
32
35
|
|
33
|
-
# first, we group IOs based on interest type. On call to #interests however,
|
34
|
-
# things might already happen, and new IOs might be registered, so we might
|
35
|
-
# have to start all over again. We do this until we group all selectables
|
36
|
-
begin
|
37
|
-
loop do
|
38
36
|
begin
|
39
|
-
|
40
|
-
|
37
|
+
select(timeout, &:call)
|
38
|
+
@timers.fire
|
39
|
+
rescue TimeoutError => e
|
40
|
+
@timers.fire(e)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue StandardError => e
|
44
|
+
emit_error(e)
|
45
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
46
|
+
each_connection(&:force_reset)
|
47
|
+
raise
|
48
|
+
end
|
41
49
|
|
42
|
-
|
43
|
-
|
50
|
+
def terminate
|
51
|
+
# array may change during iteration
|
52
|
+
selectables = @selectables.reject(&:inflight?)
|
44
53
|
|
45
|
-
|
46
|
-
interests = io.interests
|
54
|
+
selectables.each(&:terminate)
|
47
55
|
|
48
|
-
|
49
|
-
|
56
|
+
until selectables.empty?
|
57
|
+
next_tick
|
50
58
|
|
51
|
-
|
52
|
-
|
59
|
+
selectables &= @selectables
|
60
|
+
end
|
61
|
+
end
|
53
62
|
|
54
|
-
|
55
|
-
|
63
|
+
def find_resolver(options)
|
64
|
+
res = @selectables.find do |c|
|
65
|
+
c.is_a?(Resolver::Resolver) && options == c.options
|
66
|
+
end
|
56
67
|
|
57
|
-
|
58
|
-
|
59
|
-
return if (!r || r.empty?) && (!w || w.empty?) && !selectables.empty?
|
68
|
+
res.multi if res
|
69
|
+
end
|
60
70
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
71
|
+
def each_connection(&block)
|
72
|
+
return enum_for(__method__) unless block
|
73
|
+
|
74
|
+
@selectables.each do |c|
|
75
|
+
if c.is_a?(Resolver::Resolver)
|
76
|
+
c.each_connection(&block)
|
77
|
+
else
|
78
|
+
yield c
|
68
79
|
end
|
69
80
|
end
|
81
|
+
end
|
70
82
|
|
71
|
-
|
72
|
-
|
73
|
-
|
83
|
+
def find_connection(request_uri, options)
|
84
|
+
each_connection.find do |connection|
|
85
|
+
connection.match?(request_uri, options)
|
86
|
+
end
|
87
|
+
end
|
74
88
|
|
75
|
-
|
76
|
-
|
77
|
-
|
89
|
+
def find_mergeable_connection(connection)
|
90
|
+
each_connection.find do |ch|
|
91
|
+
ch != connection && ch.mergeable?(connection)
|
78
92
|
end
|
79
|
-
rescue IOError, SystemCallError
|
80
|
-
@selectables.reject!(&:closed?)
|
81
|
-
retry
|
82
93
|
end
|
83
94
|
|
84
|
-
|
85
|
-
|
86
|
-
|
95
|
+
def empty?
|
96
|
+
@selectables.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
# deregisters +io+ from selectables.
|
100
|
+
def deregister(io)
|
101
|
+
@selectables.delete(io)
|
102
|
+
end
|
87
103
|
|
88
|
-
|
89
|
-
|
90
|
-
|
104
|
+
# register +io+.
|
105
|
+
def register(io)
|
106
|
+
return if @selectables.include?(io)
|
91
107
|
|
92
|
-
|
93
|
-
else
|
94
|
-
readers.each(&block) if readers
|
108
|
+
@selectables << io
|
95
109
|
end
|
96
|
-
end
|
97
110
|
|
98
|
-
|
99
|
-
io = @selectables.first
|
111
|
+
private
|
100
112
|
|
101
|
-
|
113
|
+
def select(interval, &block)
|
114
|
+
# do not cause an infinite loop here.
|
115
|
+
#
|
116
|
+
# this may happen if timeout calculation actually triggered an error which causes
|
117
|
+
# the connections to be reaped (such as the total timeout error) before #select
|
118
|
+
# gets called.
|
119
|
+
return if interval.nil? && @selectables.empty?
|
102
120
|
|
103
|
-
|
121
|
+
return select_one(interval, &block) if @selectables.size == 1
|
104
122
|
|
105
|
-
|
106
|
-
when :r then io.to_io.wait_readable(interval)
|
107
|
-
when :w then io.to_io.wait_writable(interval)
|
108
|
-
when :rw then io.to_io.wait(interval, :read_write)
|
109
|
-
when nil then return
|
123
|
+
select_many(interval, &block)
|
110
124
|
end
|
111
125
|
|
112
|
-
|
113
|
-
|
114
|
-
|
126
|
+
def select_many(interval, &block)
|
127
|
+
r, w = nil
|
128
|
+
|
129
|
+
# first, we group IOs based on interest type. On call to #interests however,
|
130
|
+
# things might already happen, and new IOs might be registered, so we might
|
131
|
+
# have to start all over again. We do this until we group all selectables
|
132
|
+
begin
|
133
|
+
@selectables.delete_if do |io|
|
134
|
+
interests = io.interests
|
135
|
+
|
136
|
+
(r ||= []) << io if READABLE.include?(interests)
|
137
|
+
(w ||= []) << io if WRITABLE.include?(interests)
|
138
|
+
|
139
|
+
io.state == :closed
|
140
|
+
end
|
141
|
+
|
142
|
+
# TODO: what to do if there are no selectables?
|
143
|
+
|
144
|
+
readers, writers = IO.select(r, w, nil, interval)
|
145
|
+
|
146
|
+
if readers.nil? && writers.nil? && interval
|
147
|
+
[*r, *w].each { |io| io.handle_socket_timeout(interval) }
|
148
|
+
return
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if writers
|
153
|
+
readers.each do |io|
|
154
|
+
yield io
|
155
|
+
|
156
|
+
# so that we don't yield 2 times
|
157
|
+
writers.delete(io)
|
158
|
+
end if readers
|
159
|
+
|
160
|
+
writers.each(&block)
|
161
|
+
else
|
162
|
+
readers.each(&block) if readers
|
163
|
+
end
|
115
164
|
end
|
116
|
-
# raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
|
117
165
|
|
118
|
-
|
119
|
-
|
120
|
-
@selectables.reject!(&:closed?)
|
121
|
-
raise unless @selectables.empty?
|
122
|
-
end
|
166
|
+
def select_one(interval)
|
167
|
+
io = @selectables.first
|
123
168
|
|
124
|
-
|
125
|
-
# do not cause an infinite loop here.
|
126
|
-
#
|
127
|
-
# this may happen if timeout calculation actually triggered an error which causes
|
128
|
-
# the connections to be reaped (such as the total timeout error) before #select
|
129
|
-
# gets called.
|
130
|
-
return if interval.nil? && @selectables.empty?
|
169
|
+
return unless io
|
131
170
|
|
132
|
-
|
171
|
+
interests = io.interests
|
133
172
|
|
134
|
-
|
135
|
-
|
173
|
+
result = case interests
|
174
|
+
when :r then io.to_io.wait_readable(interval)
|
175
|
+
when :w then io.to_io.wait_writable(interval)
|
176
|
+
when :rw then io.to_io.wait(interval, :read_write)
|
177
|
+
when nil then return
|
178
|
+
end
|
179
|
+
|
180
|
+
unless result || interval.nil?
|
181
|
+
io.handle_socket_timeout(interval)
|
182
|
+
return
|
183
|
+
end
|
184
|
+
# raise TimeoutError.new(interval, "timed out while waiting on select")
|
185
|
+
|
186
|
+
yield io
|
187
|
+
# rescue IOError, SystemCallError
|
188
|
+
# @selectables.reject!(&:closed?)
|
189
|
+
# raise unless @selectables.empty?
|
190
|
+
end
|
136
191
|
|
137
|
-
|
192
|
+
def next_timeout
|
193
|
+
[
|
194
|
+
@timers.wait_interval,
|
195
|
+
@selectables.filter_map(&:timeout).min,
|
196
|
+
].compact.min
|
197
|
+
end
|
198
|
+
|
199
|
+
def emit_error(e)
|
200
|
+
@selectables.each do |c|
|
201
|
+
next if c.is_a?(Resolver::Resolver)
|
202
|
+
|
203
|
+
c.emit(:error, e)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
138
207
|
end
|