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