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