httpx 1.3.4 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_4_0.md +43 -0
  3. data/lib/httpx/adapters/faraday.rb +2 -0
  4. data/lib/httpx/adapters/webmock.rb +11 -5
  5. data/lib/httpx/callbacks.rb +0 -5
  6. data/lib/httpx/chainable.rb +3 -1
  7. data/lib/httpx/connection/http2.rb +11 -7
  8. data/lib/httpx/connection.rb +128 -16
  9. data/lib/httpx/errors.rb +12 -0
  10. data/lib/httpx/loggable.rb +5 -5
  11. data/lib/httpx/options.rb +26 -16
  12. data/lib/httpx/plugins/aws_sigv4.rb +31 -16
  13. data/lib/httpx/plugins/callbacks.rb +12 -2
  14. data/lib/httpx/plugins/circuit_breaker.rb +0 -5
  15. data/lib/httpx/plugins/content_digest.rb +202 -0
  16. data/lib/httpx/plugins/expect.rb +4 -3
  17. data/lib/httpx/plugins/follow_redirects.rb +7 -8
  18. data/lib/httpx/plugins/h2c.rb +23 -20
  19. data/lib/httpx/plugins/internal_telemetry.rb +27 -0
  20. data/lib/httpx/plugins/persistent.rb +16 -0
  21. data/lib/httpx/plugins/proxy/http.rb +17 -19
  22. data/lib/httpx/plugins/proxy.rb +91 -93
  23. data/lib/httpx/plugins/retries.rb +5 -8
  24. data/lib/httpx/plugins/upgrade.rb +5 -10
  25. data/lib/httpx/plugins/webdav.rb +6 -0
  26. data/lib/httpx/plugins/xml.rb +76 -0
  27. data/lib/httpx/pool.rb +73 -244
  28. data/lib/httpx/request/body.rb +16 -12
  29. data/lib/httpx/request.rb +1 -1
  30. data/lib/httpx/resolver/https.rb +12 -19
  31. data/lib/httpx/resolver/multi.rb +34 -16
  32. data/lib/httpx/resolver/native.rb +36 -13
  33. data/lib/httpx/resolver/resolver.rb +49 -11
  34. data/lib/httpx/resolver/system.rb +29 -11
  35. data/lib/httpx/resolver.rb +21 -14
  36. data/lib/httpx/response.rb +5 -3
  37. data/lib/httpx/selector.rb +164 -95
  38. data/lib/httpx/session.rb +296 -139
  39. data/lib/httpx/transcoder/gzip.rb +0 -3
  40. data/lib/httpx/transcoder/json.rb +14 -2
  41. data/lib/httpx/transcoder/utils/deflater.rb +7 -4
  42. data/lib/httpx/transcoder/utils/inflater.rb +2 -0
  43. data/lib/httpx/transcoder.rb +0 -1
  44. data/lib/httpx/version.rb +1 -1
  45. data/lib/httpx.rb +19 -20
  46. data/sig/callbacks.rbs +0 -1
  47. data/sig/chainable.rbs +4 -0
  48. data/sig/connection/http2.rbs +1 -1
  49. data/sig/connection.rbs +14 -3
  50. data/sig/errors.rbs +6 -0
  51. data/sig/loggable.rbs +2 -0
  52. data/sig/options.rbs +7 -0
  53. data/sig/plugins/aws_sigv4.rbs +8 -2
  54. data/sig/plugins/content_digest.rbs +51 -0
  55. data/sig/plugins/cookies/cookie.rbs +9 -0
  56. data/sig/plugins/grpc/call.rbs +4 -0
  57. data/sig/plugins/persistent.rbs +4 -1
  58. data/sig/plugins/proxy/socks5.rbs +11 -3
  59. data/sig/plugins/proxy.rbs +18 -11
  60. data/sig/plugins/push_promise.rbs +3 -0
  61. data/sig/plugins/rate_limiter.rbs +2 -0
  62. data/sig/plugins/retries.rbs +1 -1
  63. data/sig/plugins/ssrf_filter.rbs +26 -0
  64. data/sig/plugins/webdav.rbs +23 -0
  65. data/sig/plugins/xml.rbs +37 -0
  66. data/sig/pool.rbs +25 -33
  67. data/sig/request/body.rbs +5 -1
  68. data/sig/resolver/multi.rbs +26 -1
  69. data/sig/resolver/native.rbs +0 -2
  70. data/sig/resolver/resolver.rbs +21 -2
  71. data/sig/resolver.rbs +5 -1
  72. data/sig/response/buffer.rbs +1 -1
  73. data/sig/selector.rbs +30 -4
  74. data/sig/session.rbs +45 -18
  75. data/sig/transcoder/body.rbs +1 -1
  76. data/sig/transcoder/chunker.rbs +1 -1
  77. data/sig/transcoder/deflate.rbs +1 -0
  78. data/sig/transcoder/form.rbs +8 -0
  79. data/sig/transcoder/gzip.rbs +4 -1
  80. data/sig/transcoder/utils/body_reader.rbs +2 -2
  81. data/sig/transcoder/utils/deflater.rbs +2 -2
  82. metadata +10 -4
  83. data/lib/httpx/transcoder/xml.rb +0 -52
  84. data/sig/transcoder/xml.rbs +0 -22
@@ -2,137 +2,206 @@
2
2
 
3
3
  require "io/wait"
4
4
 
5
- class HTTPX::Selector
6
- READABLE = %i[rw r].freeze
7
- WRITABLE = %i[rw w].freeze
5
+ module HTTPX
6
+ class Selector
7
+ extend Forwardable
8
8
 
9
- private_constant :READABLE
10
- private_constant :WRITABLE
9
+ READABLE = %i[rw r].freeze
10
+ WRITABLE = %i[rw w].freeze
11
11
 
12
- def initialize
13
- @selectables = []
14
- end
12
+ private_constant :READABLE
13
+ private_constant :WRITABLE
15
14
 
16
- # deregisters +io+ from selectables.
17
- def deregister(io)
18
- @selectables.delete(io)
19
- end
15
+ def_delegator :@timers, :after
20
16
 
21
- # register +io+.
22
- def register(io)
23
- return if @selectables.include?(io)
17
+ def_delegator :@selectables, :empty?
24
18
 
25
- @selectables << io
26
- end
19
+ def initialize
20
+ @timers = Timers.new
21
+ @selectables = []
22
+ end
27
23
 
28
- private
24
+ def each(&blk)
25
+ @selectables.each(&blk)
26
+ end
29
27
 
30
- def select_many(interval, &block)
31
- selectables, r, w = nil
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
- r = nil
40
- w = nil
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
- selectables = @selectables
43
- @selectables = []
50
+ def terminate
51
+ # array may change during iteration
52
+ selectables = @selectables.reject(&:inflight?)
44
53
 
45
- selectables.delete_if do |io|
46
- interests = io.interests
54
+ selectables.each(&:terminate)
47
55
 
48
- (r ||= []) << io if READABLE.include?(interests)
49
- (w ||= []) << io if WRITABLE.include?(interests)
56
+ until selectables.empty?
57
+ next_tick
50
58
 
51
- io.state == :closed
52
- end
59
+ selectables &= @selectables
60
+ end
61
+ end
53
62
 
54
- if @selectables.empty?
55
- @selectables = selectables
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
- # do not run event loop if there's nothing to wait on.
58
- # this might happen if connect failed and connection was unregistered.
59
- return if (!r || r.empty?) && (!w || w.empty?) && !selectables.empty?
68
+ res.multi if res
69
+ end
60
70
 
61
- break
62
- else
63
- @selectables.concat(selectables)
64
- end
65
- rescue StandardError
66
- @selectables = selectables if selectables
67
- raise
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
- # TODO: what to do if there are no selectables?
72
-
73
- readers, writers = IO.select(r, w, nil, interval)
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
- if readers.nil? && writers.nil? && interval
76
- [*r, *w].each { |io| io.handle_socket_timeout(interval) }
77
- return
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
- if writers
85
- readers.each do |io|
86
- yield io
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
- # so that we don't yield 2 times
89
- writers.delete(io)
90
- end if readers
104
+ # register +io+.
105
+ def register(io)
106
+ return if @selectables.include?(io)
91
107
 
92
- writers.each(&block)
93
- else
94
- readers.each(&block) if readers
108
+ @selectables << io
95
109
  end
96
- end
97
110
 
98
- def select_one(interval)
99
- io = @selectables.first
111
+ private
100
112
 
101
- return unless io
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
- interests = io.interests
121
+ return select_one(interval, &block) if @selectables.size == 1
104
122
 
105
- result = case interests
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
- unless result || interval.nil?
113
- io.handle_socket_timeout(interval)
114
- return
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
- yield io
119
- rescue IOError, SystemCallError
120
- @selectables.reject!(&:closed?)
121
- raise unless @selectables.empty?
122
- end
166
+ def select_one(interval)
167
+ io = @selectables.first
123
168
 
124
- def select(interval, &block)
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
- return select_one(interval, &block) if @selectables.size == 1
171
+ interests = io.interests
133
172
 
134
- select_many(interval, &block)
135
- end
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
- public :select
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