httpx 1.5.1 → 1.6.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_6_0.md +50 -0
  3. data/lib/httpx/adapters/datadog.rb +23 -13
  4. data/lib/httpx/adapters/faraday.rb +14 -9
  5. data/lib/httpx/adapters/webmock.rb +1 -1
  6. data/lib/httpx/callbacks.rb +1 -1
  7. data/lib/httpx/connection/http1.rb +5 -6
  8. data/lib/httpx/connection/http2.rb +30 -12
  9. data/lib/httpx/connection.rb +17 -24
  10. data/lib/httpx/errors.rb +3 -1
  11. data/lib/httpx/io/ssl.rb +1 -4
  12. data/lib/httpx/io/tcp.rb +25 -16
  13. data/lib/httpx/io/unix.rb +4 -3
  14. data/lib/httpx/loggable.rb +4 -1
  15. data/lib/httpx/options.rb +252 -158
  16. data/lib/httpx/plugins/aws_sdk_authentication.rb +2 -0
  17. data/lib/httpx/plugins/aws_sigv4.rb +2 -0
  18. data/lib/httpx/plugins/callbacks.rb +13 -1
  19. data/lib/httpx/plugins/circuit_breaker.rb +2 -0
  20. data/lib/httpx/plugins/content_digest.rb +2 -0
  21. data/lib/httpx/plugins/cookies.rb +2 -2
  22. data/lib/httpx/plugins/digest_auth.rb +2 -0
  23. data/lib/httpx/plugins/expect.rb +2 -0
  24. data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
  25. data/lib/httpx/plugins/follow_redirects.rb +2 -0
  26. data/lib/httpx/plugins/grpc.rb +2 -0
  27. data/lib/httpx/plugins/h2c.rb +26 -16
  28. data/lib/httpx/plugins/internal_telemetry.rb +0 -49
  29. data/lib/httpx/plugins/ntlm_auth.rb +2 -0
  30. data/lib/httpx/plugins/oauth.rb +2 -0
  31. data/lib/httpx/plugins/persistent.rb +27 -18
  32. data/lib/httpx/plugins/proxy/socks4.rb +1 -1
  33. data/lib/httpx/plugins/proxy/socks5.rb +1 -1
  34. data/lib/httpx/plugins/proxy/ssh.rb +2 -0
  35. data/lib/httpx/plugins/proxy.rb +61 -20
  36. data/lib/httpx/plugins/response_cache/file_store.rb +2 -2
  37. data/lib/httpx/plugins/response_cache.rb +2 -0
  38. data/lib/httpx/plugins/retries.rb +2 -0
  39. data/lib/httpx/plugins/ssrf_filter.rb +2 -2
  40. data/lib/httpx/plugins/stream_bidi.rb +3 -3
  41. data/lib/httpx/plugins/upgrade/h2.rb +11 -1
  42. data/lib/httpx/plugins/upgrade.rb +8 -0
  43. data/lib/httpx/pool.rb +15 -10
  44. data/lib/httpx/request/body.rb +8 -3
  45. data/lib/httpx/request.rb +22 -11
  46. data/lib/httpx/resolver/entry.rb +30 -0
  47. data/lib/httpx/resolver/https.rb +3 -1
  48. data/lib/httpx/resolver/multi.rb +5 -2
  49. data/lib/httpx/resolver/native.rb +15 -6
  50. data/lib/httpx/resolver/resolver.rb +3 -5
  51. data/lib/httpx/resolver/system.rb +1 -1
  52. data/lib/httpx/resolver.rb +34 -21
  53. data/lib/httpx/response/body.rb +1 -1
  54. data/lib/httpx/response/buffer.rb +13 -18
  55. data/lib/httpx/selector.rb +92 -34
  56. data/lib/httpx/session.rb +89 -30
  57. data/lib/httpx/session_extensions.rb +3 -2
  58. data/lib/httpx/transcoder/form.rb +1 -13
  59. data/lib/httpx/transcoder/multipart/mime_type_detector.rb +1 -1
  60. data/lib/httpx/transcoder/multipart.rb +14 -0
  61. data/lib/httpx/transcoder/utils/deflater.rb +1 -1
  62. data/lib/httpx/version.rb +1 -1
  63. data/sig/callbacks.rbs +1 -1
  64. data/sig/chainable.rbs +1 -0
  65. data/sig/connection/http1.rbs +2 -0
  66. data/sig/connection/http2.rbs +4 -0
  67. data/sig/connection.rbs +6 -6
  68. data/sig/errors.rbs +3 -1
  69. data/sig/io/ssl.rbs +1 -1
  70. data/sig/io/tcp.rbs +13 -7
  71. data/sig/io/udp.rbs +7 -2
  72. data/sig/io/unix.rbs +0 -1
  73. data/sig/io.rbs +0 -3
  74. data/sig/options.rbs +63 -10
  75. data/sig/plugins/fiber_concurrency.rbs +51 -0
  76. data/sig/plugins/h2c.rbs +5 -1
  77. data/sig/plugins/persistent.rbs +1 -1
  78. data/sig/plugins/proxy/socks4.rbs +1 -1
  79. data/sig/plugins/proxy/socks5.rbs +1 -1
  80. data/sig/plugins/proxy.rbs +5 -2
  81. data/sig/plugins/ssrf_filter.rbs +1 -1
  82. data/sig/plugins/stream_bidi.rbs +2 -2
  83. data/sig/request.rbs +4 -1
  84. data/sig/resolver/entry.rbs +13 -0
  85. data/sig/resolver/native.rbs +1 -0
  86. data/sig/resolver/resolver.rbs +2 -3
  87. data/sig/resolver/system.rbs +2 -2
  88. data/sig/resolver.rbs +10 -11
  89. data/sig/response.rbs +2 -2
  90. data/sig/selector.rbs +18 -10
  91. data/sig/session.rbs +4 -0
  92. data/sig/transcoder/form.rbs +3 -3
  93. data/sig/transcoder/multipart.rbs +9 -3
  94. metadata +9 -3
data/lib/httpx/options.rb CHANGED
@@ -15,70 +15,16 @@ module HTTPX
15
15
  CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
16
16
  REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
17
17
 
18
- # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
19
- ip_address_families = begin
20
- list = Socket.ip_address_list
21
- if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? && !a.ipv6_unique_local? }
22
- [Socket::AF_INET6, Socket::AF_INET]
23
- else
24
- [Socket::AF_INET]
25
- end
26
- rescue NotImplementedError
27
- [Socket::AF_INET]
28
- end.freeze
18
+ @options_names = []
29
19
 
30
- SET_TEMPORARY_NAME = ->(mod, pl = nil) do
31
- if mod.respond_to?(:set_temporary_name) # ruby 3.4 only
32
- name = mod.name || "#{mod.superclass.name}(plugin)"
33
- name = "#{name}/#{pl}" if pl
34
- mod.set_temporary_name(name)
35
- end
36
- end
20
+ class << self
21
+ attr_reader :options_names
37
22
 
38
- DEFAULT_OPTIONS = {
39
- :max_requests => Float::INFINITY,
40
- :debug => nil,
41
- :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
42
- :debug_redact => ENV.key?("HTTPX_DEBUG_REDACT"),
43
- :ssl => EMPTY_HASH,
44
- :http2_settings => { settings_enable_push: 0 }.freeze,
45
- :fallback_protocol => "http/1.1",
46
- :supported_compression_formats => %w[gzip deflate],
47
- :decompress_response_body => true,
48
- :compress_request_body => true,
49
- :timeout => {
50
- connect_timeout: CONNECT_TIMEOUT,
51
- settings_timeout: SETTINGS_TIMEOUT,
52
- close_handshake_timeout: CLOSE_HANDSHAKE_TIMEOUT,
53
- operation_timeout: OPERATION_TIMEOUT,
54
- keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
55
- read_timeout: READ_TIMEOUT,
56
- write_timeout: WRITE_TIMEOUT,
57
- request_timeout: REQUEST_TIMEOUT,
58
- },
59
- :headers_class => Class.new(Headers, &SET_TEMPORARY_NAME),
60
- :headers => {},
61
- :window_size => WINDOW_SIZE,
62
- :buffer_size => BUFFER_SIZE,
63
- :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
64
- :request_class => Class.new(Request, &SET_TEMPORARY_NAME),
65
- :response_class => Class.new(Response, &SET_TEMPORARY_NAME),
66
- :request_body_class => Class.new(Request::Body, &SET_TEMPORARY_NAME),
67
- :response_body_class => Class.new(Response::Body, &SET_TEMPORARY_NAME),
68
- :pool_class => Class.new(Pool, &SET_TEMPORARY_NAME),
69
- :connection_class => Class.new(Connection, &SET_TEMPORARY_NAME),
70
- :options_class => Class.new(self, &SET_TEMPORARY_NAME),
71
- :transport => nil,
72
- :addresses => nil,
73
- :persistent => false,
74
- :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
75
- :resolver_options => { cache: true }.freeze,
76
- :pool_options => EMPTY_HASH,
77
- :ip_families => ip_address_families,
78
- :close_on_fork => false,
79
- }.freeze
23
+ def inherited(klass)
24
+ super
25
+ klass.instance_variable_set(:@options_names, @options_names.dup)
26
+ end
80
27
 
81
- class << self
82
28
  def new(options = {})
83
29
  # let enhanced options go through
84
30
  return options if self == Options && options.class < self
@@ -87,14 +33,34 @@ module HTTPX
87
33
  super
88
34
  end
89
35
 
36
+ def freeze
37
+ @options_names.freeze
38
+ super
39
+ end
40
+
90
41
  def method_added(meth)
91
42
  super
92
43
 
93
44
  return unless meth =~ /^option_(.+)$/
94
45
 
95
- optname = Regexp.last_match(1).to_sym
46
+ optname = Regexp.last_match(1)
47
+
48
+ if optname =~ /^(.+[^_])_+with/
49
+ # ignore alias method chain generated methods.
50
+ # this is the case with RBS runtime tests.
51
+ # it relies on the "_with/_without" separator, which is the most used convention,
52
+ # however it shouldn't be used in practice in httpx given the plugin architecture
53
+ # as the main extension API.
54
+ orig_name = Regexp.last_match(1)
55
+
56
+ return if @options_names.include?(orig_name.to_sym)
57
+ end
58
+
59
+ optname = optname.to_sym
96
60
 
97
61
  attr_reader(optname)
62
+
63
+ @options_names << optname unless @options_names.include?(optname)
98
64
  end
99
65
  end
100
66
 
@@ -103,7 +69,7 @@ module HTTPX
103
69
  # :debug :: an object which log messages are written to (must respond to <tt><<</tt>)
104
70
  # :debug_level :: the log level of messages (can be 1, 2, or 3).
105
71
  # :debug_redact :: whether header/body payload should be redacted (defaults to <tt>false</tt>).
106
- # :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::IO::SSL)
72
+ # :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::SSL)
107
73
  # :http2_settings :: a hash of options to be passed to a HTTP2::Connection (ex: <tt>{ max_concurrent_streams: 2 }</tt>)
108
74
  # :fallback_protocol :: version of HTTP protocol to use by default in the absence of protocol negotiation
109
75
  # like ALPN (defaults to <tt>"http/1.1"</tt>)
@@ -123,6 +89,11 @@ module HTTPX
123
89
  # :request_body_class :: class used to instantiate a request body
124
90
  # :response_body_class :: class used to instantiate a response body
125
91
  # :connection_class :: class used to instantiate connections
92
+ # :http1_class :: class used to manage HTTP1 sessions
93
+ # :http2_class :: class used to imanage HTTP2 sessions
94
+ # :resolver_native_class :: class used to resolve names using pure ruby DNS implementation
95
+ # :resolver_system_class :: class used to resolve names using system-based (getaddrinfo) name resolution
96
+ # :resolver_https_class :: class used to resolve names using DoH
126
97
  # :pool_class :: class used to instantiate the session connection pool
127
98
  # :options_class :: class used to instantiate options
128
99
  # :transport :: type of transport to use (set to "unix" for UNIX sockets)
@@ -143,99 +114,46 @@ module HTTPX
143
114
  # it only works if the session is persistent (and ruby 3.1 or higher is used).
144
115
  #
145
116
  # This list of options are enhanced with each loaded plugin, see the plugin docs for details.
146
- def initialize(options = {})
147
- do_initialize(options)
148
- freeze
149
- end
150
-
151
- def freeze
152
- @origin.freeze
153
- @base_path.freeze
154
- @timeout.freeze
155
- @headers.freeze
156
- @addresses.freeze
157
- @supported_compression_formats.freeze
158
- @ssl.freeze
159
- @http2_settings.freeze
160
- @pool_options.freeze
161
- @resolver_options.freeze
162
- @ip_families.freeze
163
- super
164
- end
165
-
166
- def option_origin(value)
167
- URI(value)
168
- end
169
-
170
- def option_base_path(value)
171
- String(value)
172
- end
173
-
174
- def option_headers(value)
175
- headers_class.new(value)
176
- end
177
-
178
- def option_timeout(value)
179
- Hash[value]
180
- end
117
+ def initialize(options = EMPTY_HASH)
118
+ options_names = self.class.options_names
181
119
 
182
- def option_supported_compression_formats(value)
183
- Array(value).map(&:to_s)
184
- end
120
+ defaults =
121
+ case options
122
+ when Options
123
+ unknown_options = options.class.options_names - options_names
185
124
 
186
- def option_transport(value)
187
- transport = value.to_s
188
- raise TypeError, "#{transport} is an unsupported transport type" unless %w[unix].include?(transport)
125
+ raise Error, "unknown option: #{unknown_options.first}" unless unknown_options.empty?
189
126
 
190
- transport
191
- end
127
+ DEFAULT_OPTIONS.merge(options)
128
+ else
129
+ options.each_key do |k|
130
+ raise Error, "unknown option: #{k}" unless options_names.include?(k)
131
+ end
192
132
 
193
- def option_addresses(value)
194
- Array(value)
195
- end
133
+ options.empty? ? DEFAULT_OPTIONS : DEFAULT_OPTIONS.merge(options)
134
+ end
196
135
 
197
- def option_ip_families(value)
198
- Array(value)
199
- end
136
+ options_names.each do |k|
137
+ v = defaults[k]
200
138
 
201
- # number options
202
- %i[
203
- max_concurrent_requests max_requests window_size buffer_size
204
- body_threshold_size debug_level
205
- ].each do |option|
206
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
207
- # converts +v+ into an Integer before setting the +#{option}+ option.
208
- def option_#{option}(value) # def option_max_requests(v)
209
- value = Integer(value) unless value.infinite?
210
- raise TypeError, ":#{option} must be positive" unless value.positive? # raise TypeError, ":max_requests must be positive" unless value.positive?
139
+ if v.nil?
140
+ instance_variable_set(:"@#{k}", v)
211
141
 
212
- value
142
+ next
213
143
  end
214
- OUT
215
- end
216
144
 
217
- # hashable options
218
- %i[ssl http2_settings resolver_options pool_options].each do |option|
219
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
220
- # converts +v+ into an Hash before setting the +#{option}+ option.
221
- def option_#{option}(value) # def option_ssl(v)
222
- Hash[value]
223
- end
224
- OUT
145
+ value = __send__(:"option_#{k}", v)
146
+ instance_variable_set(:"@#{k}", value)
147
+ end
148
+
149
+ freeze
225
150
  end
226
151
 
227
- %i[
228
- request_class response_class headers_class request_body_class
229
- response_body_class connection_class options_class
230
- pool_class
231
- io fallback_protocol debug debug_redact resolver_class
232
- compress_request_body decompress_response_body
233
- persistent close_on_fork
234
- ].each do |method_name|
235
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
236
- # sets +v+ as the value of the +#{method_name}+ option
237
- def option_#{method_name}(v); v; end # def option_smth(v); v; end
238
- OUT
152
+ def freeze
153
+ self.class.options_names.each do |ivar|
154
+ instance_variable_get(:"@#{ivar}").freeze
155
+ end
156
+ super
239
157
  end
240
158
 
241
159
  REQUEST_BODY_IVARS = %i[@headers].freeze
@@ -262,11 +180,12 @@ module HTTPX
262
180
  def merge(other)
263
181
  ivar_map = nil
264
182
  other_ivars = case other
265
- when Hash
183
+ when Options
184
+ other.instance_variables
185
+ else
186
+ other = Hash[other] unless other.is_a?(Hash)
266
187
  ivar_map = other.keys.to_h { |k| [:"@#{k}", k] }
267
188
  ivar_map.keys
268
- else
269
- other.instance_variables
270
189
  end
271
190
 
272
191
  return self if other_ivars.empty?
@@ -297,70 +216,177 @@ module HTTPX
297
216
 
298
217
  def to_hash
299
218
  instance_variables.each_with_object({}) do |ivar, hs|
300
- hs[ivar[1..-1].to_sym] = instance_variable_get(ivar)
219
+ val = instance_variable_get(ivar)
220
+
221
+ next if val.nil?
222
+
223
+ hs[ivar[1..-1].to_sym] = val
301
224
  end
302
225
  end
303
226
 
304
227
  def extend_with_plugin_classes(pl)
228
+ # extend request class
305
229
  if defined?(pl::RequestMethods) || defined?(pl::RequestClassMethods)
306
230
  @request_class = @request_class.dup
307
231
  SET_TEMPORARY_NAME[@request_class, pl]
308
232
  @request_class.__send__(:include, pl::RequestMethods) if defined?(pl::RequestMethods)
309
233
  @request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods)
310
234
  end
235
+ # extend response class
311
236
  if defined?(pl::ResponseMethods) || defined?(pl::ResponseClassMethods)
312
237
  @response_class = @response_class.dup
313
238
  SET_TEMPORARY_NAME[@response_class, pl]
314
239
  @response_class.__send__(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods)
315
240
  @response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods)
316
241
  end
242
+ # extend headers class
317
243
  if defined?(pl::HeadersMethods) || defined?(pl::HeadersClassMethods)
318
244
  @headers_class = @headers_class.dup
319
245
  SET_TEMPORARY_NAME[@headers_class, pl]
320
246
  @headers_class.__send__(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods)
321
247
  @headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods)
322
248
  end
249
+ # extend request body class
323
250
  if defined?(pl::RequestBodyMethods) || defined?(pl::RequestBodyClassMethods)
324
251
  @request_body_class = @request_body_class.dup
325
252
  SET_TEMPORARY_NAME[@request_body_class, pl]
326
253
  @request_body_class.__send__(:include, pl::RequestBodyMethods) if defined?(pl::RequestBodyMethods)
327
254
  @request_body_class.extend(pl::RequestBodyClassMethods) if defined?(pl::RequestBodyClassMethods)
328
255
  end
256
+ # extend response body class
329
257
  if defined?(pl::ResponseBodyMethods) || defined?(pl::ResponseBodyClassMethods)
330
258
  @response_body_class = @response_body_class.dup
331
259
  SET_TEMPORARY_NAME[@response_body_class, pl]
332
260
  @response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
333
261
  @response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
334
262
  end
263
+ # extend connection pool class
335
264
  if defined?(pl::PoolMethods)
336
265
  @pool_class = @pool_class.dup
337
266
  SET_TEMPORARY_NAME[@pool_class, pl]
338
267
  @pool_class.__send__(:include, pl::PoolMethods)
339
268
  end
269
+ # extend connection class
340
270
  if defined?(pl::ConnectionMethods)
341
271
  @connection_class = @connection_class.dup
342
272
  SET_TEMPORARY_NAME[@connection_class, pl]
343
273
  @connection_class.__send__(:include, pl::ConnectionMethods)
344
274
  end
275
+ # extend http1 class
276
+ if defined?(pl::HTTP1Methods)
277
+ @http1_class = @http1_class.dup
278
+ SET_TEMPORARY_NAME[@http1_class, pl]
279
+ @http1_class.__send__(:include, pl::HTTP1Methods)
280
+ end
281
+ # extend http2 class
282
+ if defined?(pl::HTTP2Methods)
283
+ @http2_class = @http2_class.dup
284
+ SET_TEMPORARY_NAME[@http2_class, pl]
285
+ @http2_class.__send__(:include, pl::HTTP2Methods)
286
+ end
287
+ # extend native resolver class
288
+ if defined?(pl::ResolverNativeMethods)
289
+ @resolver_native_class = @resolver_native_class.dup
290
+ SET_TEMPORARY_NAME[@resolver_native_class, pl]
291
+ @resolver_native_class.__send__(:include, pl::ResolverNativeMethods)
292
+ end
293
+ # extend system resolver class
294
+ if defined?(pl::ResolverSystemMethods)
295
+ @resolver_system_class = @resolver_system_class.dup
296
+ SET_TEMPORARY_NAME[@resolver_system_class, pl]
297
+ @resolver_system_class.__send__(:include, pl::ResolverSystemMethods)
298
+ end
299
+ # extend https resolver class
300
+ if defined?(pl::ResolverHTTPSMethods)
301
+ @resolver_https_class = @resolver_https_class.dup
302
+ SET_TEMPORARY_NAME[@resolver_https_class, pl]
303
+ @resolver_https_class.__send__(:include, pl::ResolverHTTPSMethods)
304
+ end
305
+
345
306
  return unless defined?(pl::OptionsMethods)
346
307
 
308
+ # extend option class
309
+ # works around lack of initialize_dup callback
347
310
  @options_class = @options_class.dup
311
+ # (self.class.options_names)
348
312
  @options_class.__send__(:include, pl::OptionsMethods)
349
313
  end
350
314
 
351
315
  private
352
316
 
353
- def do_initialize(options = {})
354
- defaults = DEFAULT_OPTIONS.merge(options)
355
- defaults.each do |k, v|
356
- next if v.nil?
317
+ # number options
318
+ %i[
319
+ max_concurrent_requests max_requests window_size buffer_size
320
+ body_threshold_size debug_level
321
+ ].each do |option|
322
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
323
+ # converts +v+ into an Integer before setting the +#{option}+ option.
324
+ private def option_#{option}(value) # private def option_max_requests(v)
325
+ value = Integer(value) unless value.respond_to?(:infinite?) && value.infinite?
326
+ raise TypeError, ":#{option} must be positive" unless value.positive? # raise TypeError, ":max_requests must be positive" unless value.positive?
357
327
 
358
- option_method_name = :"option_#{k}"
359
- raise Error, "unknown option: #{k}" unless respond_to?(option_method_name)
328
+ value
329
+ end
330
+ OUT
331
+ end
360
332
 
361
- value = __send__(option_method_name, v)
362
- instance_variable_set(:"@#{k}", value)
363
- end
333
+ # hashable options
334
+ %i[ssl http2_settings resolver_options pool_options].each do |option|
335
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
336
+ # converts +v+ into an Hash before setting the +#{option}+ option.
337
+ private def option_#{option}(value) # def option_ssl(v)
338
+ Hash[value]
339
+ end
340
+ OUT
341
+ end
342
+
343
+ %i[
344
+ request_class response_class headers_class request_body_class
345
+ response_body_class connection_class http1_class http2_class
346
+ resolver_native_class resolver_system_class resolver_https_class options_class pool_class
347
+ io fallback_protocol debug debug_redact resolver_class
348
+ compress_request_body decompress_response_body
349
+ persistent close_on_fork
350
+ ].each do |method_name|
351
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
352
+ # sets +v+ as the value of the +#{method_name}+ option
353
+ private def option_#{method_name}(v); v; end # private def option_smth(v); v; end
354
+ OUT
355
+ end
356
+
357
+ def option_origin(value)
358
+ URI(value)
359
+ end
360
+
361
+ def option_base_path(value)
362
+ String(value)
363
+ end
364
+
365
+ def option_headers(value)
366
+ headers_class.new(value)
367
+ end
368
+
369
+ def option_timeout(value)
370
+ Hash[value]
371
+ end
372
+
373
+ def option_supported_compression_formats(value)
374
+ Array(value).map(&:to_s)
375
+ end
376
+
377
+ def option_transport(value)
378
+ transport = value.to_s
379
+ raise TypeError, "#{transport} is an unsupported transport type" unless %w[unix].include?(transport)
380
+
381
+ transport
382
+ end
383
+
384
+ def option_addresses(value)
385
+ Array(value).map { |entry| Resolver::Entry.convert(entry) }
386
+ end
387
+
388
+ def option_ip_families(value)
389
+ Array(value)
364
390
  end
365
391
 
366
392
  def access_option(obj, k, ivar_map)
@@ -371,5 +397,73 @@ module HTTPX
371
397
  obj.instance_variable_get(k)
372
398
  end
373
399
  end
400
+
401
+ SET_TEMPORARY_NAME = ->(klass, pl = nil) do
402
+ if klass.respond_to?(:set_temporary_name) # ruby 3.4 only
403
+ name = klass.name || "#{klass.superclass.name}(plugin)"
404
+ name = "#{name}/#{pl}" if pl
405
+ klass.set_temporary_name(name)
406
+ end
407
+ end
408
+
409
+ # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
410
+ ip_address_families = begin
411
+ list = Socket.ip_address_list
412
+ if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
413
+ [Socket::AF_INET6, Socket::AF_INET]
414
+ else
415
+ [Socket::AF_INET]
416
+ end
417
+ rescue NotImplementedError
418
+ [Socket::AF_INET]
419
+ end.freeze
420
+
421
+ DEFAULT_OPTIONS = {
422
+ :max_requests => Float::INFINITY,
423
+ :debug => nil,
424
+ :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
425
+ :debug_redact => ENV.key?("HTTPX_DEBUG_REDACT"),
426
+ :ssl => EMPTY_HASH,
427
+ :http2_settings => { settings_enable_push: 0 }.freeze,
428
+ :fallback_protocol => "http/1.1",
429
+ :supported_compression_formats => %w[gzip deflate],
430
+ :decompress_response_body => true,
431
+ :compress_request_body => true,
432
+ :timeout => {
433
+ connect_timeout: CONNECT_TIMEOUT,
434
+ settings_timeout: SETTINGS_TIMEOUT,
435
+ close_handshake_timeout: CLOSE_HANDSHAKE_TIMEOUT,
436
+ operation_timeout: OPERATION_TIMEOUT,
437
+ keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
438
+ read_timeout: READ_TIMEOUT,
439
+ write_timeout: WRITE_TIMEOUT,
440
+ request_timeout: REQUEST_TIMEOUT,
441
+ }.freeze,
442
+ :headers_class => Class.new(Headers, &SET_TEMPORARY_NAME),
443
+ :headers => EMPTY_HASH,
444
+ :window_size => WINDOW_SIZE,
445
+ :buffer_size => BUFFER_SIZE,
446
+ :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
447
+ :request_class => Class.new(Request, &SET_TEMPORARY_NAME),
448
+ :response_class => Class.new(Response, &SET_TEMPORARY_NAME),
449
+ :request_body_class => Class.new(Request::Body, &SET_TEMPORARY_NAME),
450
+ :response_body_class => Class.new(Response::Body, &SET_TEMPORARY_NAME),
451
+ :pool_class => Class.new(Pool, &SET_TEMPORARY_NAME),
452
+ :connection_class => Class.new(Connection, &SET_TEMPORARY_NAME),
453
+ :http1_class => Class.new(Connection::HTTP1, &SET_TEMPORARY_NAME),
454
+ :http2_class => Class.new(Connection::HTTP2, &SET_TEMPORARY_NAME),
455
+ :resolver_native_class => Class.new(Resolver::Native, &SET_TEMPORARY_NAME),
456
+ :resolver_system_class => Class.new(Resolver::System, &SET_TEMPORARY_NAME),
457
+ :resolver_https_class => Class.new(Resolver::HTTPS, &SET_TEMPORARY_NAME),
458
+ :options_class => Class.new(self, &SET_TEMPORARY_NAME),
459
+ :transport => nil,
460
+ :addresses => nil,
461
+ :persistent => false,
462
+ :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
463
+ :resolver_options => { cache: true }.freeze,
464
+ :pool_options => EMPTY_HASH,
465
+ :ip_families => ip_address_families,
466
+ :close_on_fork => false,
467
+ }.freeze
374
468
  end
375
469
  end
@@ -76,6 +76,8 @@ module HTTPX
76
76
  #
77
77
  # :aws_profile :: AWS account profile to retrieve credentials from.
78
78
  module OptionsMethods
79
+ private
80
+
79
81
  def option_aws_profile(value)
80
82
  String(value)
81
83
  end
@@ -170,6 +170,8 @@ module HTTPX
170
170
  #
171
171
  # :sigv4_signer :: instance of HTTPX::Plugins::AWSSigV4 used to sign requests.
172
172
  module OptionsMethods
173
+ private
174
+
173
175
  def option_sigv4_signer(value)
174
176
  value.is_a?(Signer) ? value : Signer.new(value)
175
177
  end
@@ -32,6 +32,18 @@ module HTTPX
32
32
  MOD
33
33
  end
34
34
 
35
+ def plugin(*args, &blk)
36
+ super(*args).tap do |sess|
37
+ CALLBACKS.each do |cb|
38
+ next unless callbacks_for?(cb)
39
+
40
+ sess.callbacks(cb).concat(callbacks(cb))
41
+ end
42
+
43
+ sess.wrap(&blk) if blk
44
+ end
45
+ end
46
+
35
47
  private
36
48
 
37
49
  def branch(options, &blk)
@@ -85,7 +97,7 @@ module HTTPX
85
97
  end
86
98
  end
87
99
  request.on(:response) do |res|
88
- emit_or_callback_error(:response_completed, request, res)
100
+ emit_or_callback_error(:response_completed, request, res) if res.is_a?(Response)
89
101
  end
90
102
  end
91
103
 
@@ -105,6 +105,8 @@ module HTTPX
105
105
  # :circuit_breaker_half_open_drip_rate :: the rate of requests a circuit allows to be performed when in an half-open state
106
106
  # (defaults to <tt>1</tt>).
107
107
  module OptionsMethods
108
+ private
109
+
108
110
  def option_circuit_breaker_max_attempts(value)
109
111
  attempts = Integer(value)
110
112
  raise TypeError, ":circuit_breaker_max_attempts must be positive" unless attempts.positive?
@@ -43,6 +43,8 @@ module HTTPX
43
43
  # :validate_content_digest :: whether a <tt>Content-Digest</tt> header in the response should be validated;
44
44
  # can also be a callable object (i.e. <tt>->(res) { ... }</tt>, defaults to <tt>false</tt>)
45
45
  module OptionsMethods
46
+ private
47
+
46
48
  def option_content_digest_algorithm(value)
47
49
  raise TypeError, ":content_digest_algorithm must be one of 'sha-256', 'sha-512'" unless SUPPORTED_ALGORITHMS.key?(value)
48
50
 
@@ -74,6 +74,8 @@ module HTTPX
74
74
  #
75
75
  # :cookies :: cookie jar for the session (can be a Hash, an Array, an instance of HTTPX::Plugins::Cookies::CookieJar)
76
76
  module OptionsMethods
77
+ private
78
+
77
79
  def option_headers(*)
78
80
  value = super
79
81
 
@@ -90,8 +92,6 @@ module HTTPX
90
92
  jar
91
93
  end
92
94
 
93
- private
94
-
95
95
  def merge_cookie_in_jar(cookies, jar)
96
96
  cookies.each do |ck|
97
97
  ck.split(/ *; */).each do |cookie|
@@ -24,6 +24,8 @@ module HTTPX
24
24
  #
25
25
  # :digest :: instance of HTTPX::Plugins::Authentication::Digest, used to authenticate requests in the session.
26
26
  module OptionsMethods
27
+ private
28
+
27
29
  def option_digest(value)
28
30
  raise TypeError, ":digest must be a #{Authentication::Digest}" unless value.is_a?(Authentication::Digest)
29
31
 
@@ -26,6 +26,8 @@ module HTTPX
26
26
  # before retrying without the Expect header (defaults to <tt>2</tt>).
27
27
  # :expect_threshold_size :: min threshold (in bytes) of the request payload to enable the 100-continue negotiation on.
28
28
  module OptionsMethods
29
+ private
30
+
29
31
  def option_expect_timeout(value)
30
32
  seconds = Float(value)
31
33
  raise TypeError, ":expect_timeout must be positive" unless seconds.positive?