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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_3_3.md +1 -1
  3. data/doc/release_notes/1_3_4.md +6 -0
  4. data/doc/release_notes/1_4_0.md +43 -0
  5. data/lib/httpx/adapters/faraday.rb +2 -0
  6. data/lib/httpx/adapters/webmock.rb +11 -5
  7. data/lib/httpx/callbacks.rb +0 -5
  8. data/lib/httpx/chainable.rb +3 -1
  9. data/lib/httpx/connection/http2.rb +11 -7
  10. data/lib/httpx/connection.rb +128 -16
  11. data/lib/httpx/errors.rb +12 -0
  12. data/lib/httpx/loggable.rb +5 -5
  13. data/lib/httpx/options.rb +26 -16
  14. data/lib/httpx/plugins/aws_sigv4.rb +31 -16
  15. data/lib/httpx/plugins/callbacks.rb +12 -2
  16. data/lib/httpx/plugins/circuit_breaker.rb +0 -5
  17. data/lib/httpx/plugins/content_digest.rb +202 -0
  18. data/lib/httpx/plugins/expect.rb +4 -3
  19. data/lib/httpx/plugins/follow_redirects.rb +7 -8
  20. data/lib/httpx/plugins/h2c.rb +23 -20
  21. data/lib/httpx/plugins/internal_telemetry.rb +27 -0
  22. data/lib/httpx/plugins/persistent.rb +16 -0
  23. data/lib/httpx/plugins/proxy/http.rb +17 -19
  24. data/lib/httpx/plugins/proxy.rb +91 -93
  25. data/lib/httpx/plugins/retries.rb +5 -8
  26. data/lib/httpx/plugins/upgrade.rb +5 -10
  27. data/lib/httpx/plugins/webdav.rb +6 -0
  28. data/lib/httpx/plugins/xml.rb +76 -0
  29. data/lib/httpx/pool.rb +73 -244
  30. data/lib/httpx/request/body.rb +16 -12
  31. data/lib/httpx/request.rb +1 -1
  32. data/lib/httpx/resolver/https.rb +12 -19
  33. data/lib/httpx/resolver/multi.rb +34 -16
  34. data/lib/httpx/resolver/native.rb +36 -13
  35. data/lib/httpx/resolver/resolver.rb +49 -11
  36. data/lib/httpx/resolver/system.rb +29 -11
  37. data/lib/httpx/resolver.rb +21 -14
  38. data/lib/httpx/response/body.rb +12 -1
  39. data/lib/httpx/response.rb +5 -3
  40. data/lib/httpx/selector.rb +164 -95
  41. data/lib/httpx/session.rb +296 -139
  42. data/lib/httpx/transcoder/gzip.rb +0 -3
  43. data/lib/httpx/transcoder/json.rb +14 -2
  44. data/lib/httpx/transcoder/multipart/encoder.rb +3 -1
  45. data/lib/httpx/transcoder/utils/deflater.rb +7 -4
  46. data/lib/httpx/transcoder/utils/inflater.rb +2 -0
  47. data/lib/httpx/transcoder.rb +0 -1
  48. data/lib/httpx/version.rb +1 -1
  49. data/lib/httpx.rb +19 -20
  50. data/sig/callbacks.rbs +0 -1
  51. data/sig/chainable.rbs +4 -0
  52. data/sig/connection/http2.rbs +1 -1
  53. data/sig/connection.rbs +14 -3
  54. data/sig/errors.rbs +6 -0
  55. data/sig/loggable.rbs +2 -0
  56. data/sig/options.rbs +7 -0
  57. data/sig/plugins/aws_sigv4.rbs +8 -2
  58. data/sig/plugins/content_digest.rbs +51 -0
  59. data/sig/plugins/cookies/cookie.rbs +9 -0
  60. data/sig/plugins/grpc/call.rbs +4 -0
  61. data/sig/plugins/persistent.rbs +4 -1
  62. data/sig/plugins/proxy/socks5.rbs +11 -3
  63. data/sig/plugins/proxy.rbs +18 -11
  64. data/sig/plugins/push_promise.rbs +3 -0
  65. data/sig/plugins/rate_limiter.rbs +2 -0
  66. data/sig/plugins/retries.rbs +1 -1
  67. data/sig/plugins/ssrf_filter.rbs +26 -0
  68. data/sig/plugins/webdav.rbs +23 -0
  69. data/sig/plugins/xml.rbs +37 -0
  70. data/sig/pool.rbs +25 -33
  71. data/sig/request/body.rbs +5 -1
  72. data/sig/resolver/multi.rbs +26 -1
  73. data/sig/resolver/native.rbs +0 -2
  74. data/sig/resolver/resolver.rbs +21 -2
  75. data/sig/resolver.rbs +5 -1
  76. data/sig/response/body.rbs +2 -2
  77. data/sig/response/buffer.rbs +2 -2
  78. data/sig/selector.rbs +30 -4
  79. data/sig/session.rbs +45 -18
  80. data/sig/transcoder/body.rbs +1 -1
  81. data/sig/transcoder/chunker.rbs +1 -1
  82. data/sig/transcoder/deflate.rbs +1 -0
  83. data/sig/transcoder/form.rbs +8 -0
  84. data/sig/transcoder/gzip.rbs +4 -1
  85. data/sig/transcoder/multipart.rbs +3 -3
  86. data/sig/transcoder/utils/body_reader.rbs +2 -2
  87. data/sig/transcoder/utils/deflater.rbs +2 -2
  88. metadata +12 -4
  89. data/lib/httpx/transcoder/xml.rb +0 -52
  90. 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