httpx 0.15.4 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_16_0.md +93 -0
  3. data/lib/httpx.rb +6 -3
  4. data/lib/httpx/adapters/faraday.rb +3 -11
  5. data/lib/httpx/buffer.rb +1 -1
  6. data/lib/httpx/callbacks.rb +1 -1
  7. data/lib/httpx/chainable.rb +15 -8
  8. data/lib/httpx/connection.rb +2 -2
  9. data/lib/httpx/connection/http1.rb +3 -1
  10. data/lib/httpx/connection/http2.rb +1 -11
  11. data/lib/httpx/errors.rb +11 -11
  12. data/lib/httpx/io/ssl.rb +2 -2
  13. data/lib/httpx/io/tls.rb +1 -1
  14. data/lib/httpx/options.rb +78 -73
  15. data/lib/httpx/parser/http1.rb +1 -1
  16. data/lib/httpx/plugins/aws_sigv4.rb +10 -9
  17. data/lib/httpx/plugins/compression.rb +12 -11
  18. data/lib/httpx/plugins/cookies.rb +20 -7
  19. data/lib/httpx/plugins/cookies/cookie.rb +4 -2
  20. data/lib/httpx/plugins/cookies/jar.rb +20 -1
  21. data/lib/httpx/plugins/digest_authentication.rb +15 -11
  22. data/lib/httpx/plugins/expect.rb +19 -15
  23. data/lib/httpx/plugins/follow_redirects.rb +9 -9
  24. data/lib/httpx/plugins/grpc.rb +72 -46
  25. data/lib/httpx/plugins/grpc/call.rb +4 -1
  26. data/lib/httpx/plugins/ntlm_authentication.rb +8 -6
  27. data/lib/httpx/plugins/proxy.rb +4 -6
  28. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  29. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  30. data/lib/httpx/plugins/proxy/ssh.rb +9 -9
  31. data/lib/httpx/plugins/retries.rb +25 -21
  32. data/lib/httpx/plugins/upgrade.rb +7 -6
  33. data/lib/httpx/registry.rb +1 -1
  34. data/lib/httpx/request.rb +4 -12
  35. data/lib/httpx/resolver/https.rb +0 -2
  36. data/lib/httpx/response.rb +45 -18
  37. data/lib/httpx/selector.rb +2 -5
  38. data/lib/httpx/session.rb +19 -8
  39. data/lib/httpx/session2.rb +21 -0
  40. data/lib/httpx/transcoder/body.rb +1 -1
  41. data/lib/httpx/transcoder/chunker.rb +2 -1
  42. data/lib/httpx/version.rb +1 -1
  43. data/sig/buffer.rbs +2 -0
  44. data/sig/chainable.rbs +24 -28
  45. data/sig/connection.rbs +20 -8
  46. data/sig/connection/http1.rbs +3 -3
  47. data/sig/connection/http2.rbs +1 -1
  48. data/sig/errors.rbs +35 -1
  49. data/sig/headers.rbs +5 -5
  50. data/sig/httpx.rbs +4 -1
  51. data/sig/loggable.rbs +3 -1
  52. data/sig/options.rbs +35 -32
  53. data/sig/plugins/authentication.rbs +1 -1
  54. data/sig/plugins/aws_sdk_authentication.rbs +5 -1
  55. data/sig/plugins/aws_sigv4.rbs +1 -2
  56. data/sig/plugins/basic_authentication.rbs +1 -1
  57. data/sig/plugins/compression.rbs +4 -6
  58. data/sig/plugins/cookies.rbs +4 -5
  59. data/sig/plugins/cookies/cookie.rbs +5 -7
  60. data/sig/plugins/cookies/jar.rbs +9 -10
  61. data/sig/plugins/digest_authentication.rbs +2 -3
  62. data/sig/plugins/expect.rbs +2 -4
  63. data/sig/plugins/follow_redirects.rbs +3 -5
  64. data/sig/plugins/grpc.rbs +4 -7
  65. data/sig/plugins/h2c.rbs +0 -2
  66. data/sig/plugins/multipart.rbs +2 -4
  67. data/sig/plugins/ntlm_authentication.rbs +2 -3
  68. data/sig/plugins/persistent.rbs +3 -8
  69. data/sig/plugins/proxy.rbs +7 -7
  70. data/sig/plugins/proxy/ssh.rbs +4 -4
  71. data/sig/plugins/push_promise.rbs +0 -2
  72. data/sig/plugins/retries.rbs +4 -8
  73. data/sig/plugins/stream.rbs +1 -1
  74. data/sig/plugins/upgrade.rbs +2 -3
  75. data/sig/pool.rbs +1 -2
  76. data/sig/registry.rbs +1 -1
  77. data/sig/request.rbs +2 -2
  78. data/sig/resolver.rbs +7 -0
  79. data/sig/resolver/native.rbs +9 -5
  80. data/sig/resolver/resolver_mixin.rbs +4 -5
  81. data/sig/resolver/system.rbs +2 -0
  82. data/sig/response.rbs +17 -11
  83. data/sig/selector.rbs +6 -6
  84. data/sig/session.rbs +19 -14
  85. data/sig/transcoder.rbs +11 -4
  86. data/sig/transcoder/body.rbs +6 -1
  87. data/sig/transcoder/chunker.rbs +8 -2
  88. data/sig/transcoder/form.rbs +2 -1
  89. data/sig/transcoder/json.rbs +1 -0
  90. data/sig/utils.rbs +2 -0
  91. metadata +5 -3
  92. data/lib/httpx/request2.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9ddf7801add132a755b4ae9a069d5267251780577ca68398f92ca04788ea5ba
4
- data.tar.gz: 17a8072343c029940262b14810d66744d7761e561c2f0f0c5bb892737690c761
3
+ metadata.gz: 829f24d8bbd078fc042f4b9ff73909708961aa2fcb5813d30680cf069d1d43d0
4
+ data.tar.gz: 52bb4d7e335d9f7bc82c928632e4bd67c2320210a2e63050902a280dc9ee711b
5
5
  SHA512:
6
- metadata.gz: bcf80ebdd6b1b767f0617e5f21c162d2beaacfb988d83061aebc2a4bbf4b7308ed7a693f2147cbc31cdee01f1dd4543087b4d57d241de61bdcf3e68d07525597
7
- data.tar.gz: 176bfb2025dcf3039da7fc803a4abfada95d19a8221dfac746872f8806018b7b6b9b2c659ed6788183fe37bc661d7deb769094a9929b34534602bd7c4689ea01
6
+ metadata.gz: bf83ea792daf620629aa084eef37ff8cc81ffdf3c99f39bb0ae462d9673a82c941aa2341af766cf81359450d1b59338898f7431e14418e46808d87bf001673cc
7
+ data.tar.gz: 91b6de1278808c497b08ba273dd2f2de9f41e93b878867ac7cb4aa3bd716fd3a5ec5538722a59a362de0f2c263ab7e32b570546f2d940fa838a262741ad59e95
@@ -0,0 +1,93 @@
1
+ # 0.16.0
2
+
3
+ ## Features
4
+
5
+ ### Response::Body#to_s does not clear the internal buffer
6
+
7
+ It's well documented that the response body should be treated as a file, and that calling `.to_s` on a response should be done only once, and the user should not expect the same call to return the same response body again, while suggesting that the first call should be cached in a variable in case it's needed:
8
+
9
+ ```ruby
10
+ response = HTTPX.get("https://google.com")
11
+ body = response.body.to_s #=> "<html ...."
12
+ response.body.to_s #=> ""
13
+
14
+ # thankfully,it's cached in the body var there.
15
+ ```
16
+
17
+ The justification for this behaviour probably had to do with avoiding keeping huge payloads around, but it got a bit lost in git history. It became a feature, not a bug.
18
+
19
+ However, I got an [issue report](https://gitlab.com/honeyryderchuck/httpx/-/issues/143) that made me change my mind about this behaviour (tl;dr: it broke pattern matching when matching against response bodies more than once).
20
+
21
+ So now, you can call `.to_s` how many times you want!
22
+
23
+ ```ruby
24
+ response = HTTPX.get("https://google.com")
25
+ body = response.body.to_s #=> "<html ...."
26
+ response.body.to_s #=> "<html ....", still here!
27
+ ```
28
+
29
+ Some optimizations were done around how the body is carried forward, and bodies buffered in files will now get properly garbage collected and not leak descriptors behind when users forget to call `.close`.
30
+
31
+ ### grpc plugin improvements
32
+
33
+ ##### build fully-enabled stub from grpc service
34
+
35
+ The `:grpc` plugin can now build fully-loaded stubs from existing GRPC generic services.
36
+
37
+ GRPC stubs could be a bit tedious to write when compared to what the `grpc` gem offers, which is, auto-generation from ruby service stubs from protobuf definitions:
38
+
39
+ ```ruby
40
+ # service generated from the command:
41
+ #
42
+ # > grpc_tools_ruby_protoc -I ../../protos --ruby_out=../lib --grpc_out=../lib ../../protos/route_guide.proto
43
+ #
44
+ require "route_guide_services_pb.rb"
45
+
46
+ # with httpx, before 0.16
47
+ stub = HTTPX.plugin(:grpc).build_stub("localhost:#{server_port}", service: "RouteGuide")
48
+ .rpc(:GetFeature, Point, Feature)
49
+ .rpc(:ListFeatures, # ... and so on, all hand stitched
50
+
51
+ stub.get_feature(# ...
52
+
53
+ # with httpx 0.16
54
+ stub = HTTPX.plugin(:grpc).build_stub("localhost:#{server_port}", service: RouteGuide)
55
+ # that's it!
56
+ stub.get_feature(# ...
57
+ ```
58
+
59
+ #### no google/protobuf direct dependency
60
+
61
+ `"google/protobuf"` is no longer assumed when using the plugin, i.e. you can use other protobuf serializers, such as https://github.com/ruby-protobuf/protobuf , which supports `jruby` (unlike the former).
62
+
63
+ ### OptionsMethods for plugins
64
+
65
+ https://gitlab.com/honeyryderchuck/httpx/-/wikis/Custom-Plugins
66
+
67
+ You can now define an `OptionsMethods` module under your custom plugin to define your own methods. The tl;dr is, that, given the following module below, a new `:bar` option will be available (and the method will be used to set it):
68
+
69
+ ```ruby
70
+ module CustomPlugin
71
+ module OptionsMethods
72
+ def option_bar(x) ; x; end
73
+ end
74
+ end
75
+
76
+ HTTPX.plugin(CustomPlugin).with(bar: 2)
77
+ ```
78
+
79
+ ### cookies plugin: improved jar management
80
+
81
+ The behaviour of the cookies jar from the `:cookies` plugin was a bit unpredictable in certain conditions, for instance if a "Cookie" header would be passed directly via `.with(headers: {"Cookie" => "a=1"})` and there'd be a value for it already (in same cases, it'd be fully ignored). This would even get worse, if the session had a jar, and a specific set of cookies would be passed to a request(i.e.: `session_with_cookies.get("http://url.get", headers: {"Cookies" => "..."}`).
82
+
83
+ The behaviour was fixed, and is now specced under https://gitlab.com/honeyryderchuck/httpx/-/blob/master/test/support/requests/plugins/cookies.rb .
84
+
85
+ ## Bugfixes
86
+
87
+ * Cookies sorting in the `:cookies` plugin jar was fixed for truffleruby;
88
+
89
+ ## Chore
90
+
91
+ * errors when setting options nnow raise `TypeError` instead of `HTTPX::Error`.
92
+ * options are now internally frozen by default, which should protect the internals against accidentally updating them;
93
+ * Fixed optimization around options initialization, to prevent needless allocations;
data/lib/httpx.rb CHANGED
@@ -20,6 +20,7 @@ require "httpx/response"
20
20
  require "httpx/options"
21
21
  require "httpx/chainable"
22
22
 
23
+ require "mutex_m"
23
24
  # Top-Level Namespace
24
25
  #
25
26
  module HTTPX
@@ -28,15 +29,16 @@ module HTTPX
28
29
  #
29
30
  module Plugins
30
31
  @plugins = {}
32
+ @plugins.extend(Mutex_m)
31
33
 
32
34
  # Loads a plugin based on a name. If the plugin hasn't been loaded, tries to load
33
35
  # it from the load path under "httpx/plugins/" directory.
34
36
  #
35
37
  def self.load_plugin(name)
36
38
  h = @plugins
37
- unless (plugin = h[name])
39
+ unless (plugin = h.synchronize { h[name] })
38
40
  require "httpx/plugins/#{name}"
39
- raise "Plugin #{name} hasn't been registered" unless (plugin = h[name])
41
+ raise "Plugin #{name} hasn't been registered" unless (plugin = h.synchronize { h[name] })
40
42
  end
41
43
  plugin
42
44
  end
@@ -44,7 +46,8 @@ module HTTPX
44
46
  # Registers a plugin (+mod+) in the central store indexed by +name+.
45
47
  #
46
48
  def self.register_plugin(name, mod)
47
- @plugins[name] = mod
49
+ h = @plugins
50
+ h.synchronize { h[name] = mod }
48
51
  end
49
52
  end
50
53
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
3
4
  require "httpx"
4
5
  require "faraday"
5
6
 
@@ -91,11 +92,12 @@ module Faraday
91
92
  end
92
93
 
93
94
  class ParallelManager
94
- class ResponseHandler
95
+ class ResponseHandler < SimpleDelegator
95
96
  attr_reader :env
96
97
 
97
98
  def initialize(env)
98
99
  @env = env
100
+ super
99
101
  end
100
102
 
101
103
  def on_response(&blk)
@@ -117,16 +119,6 @@ module Faraday
117
119
  @on_complete
118
120
  end
119
121
  end
120
-
121
- def respond_to_missing?(meth)
122
- @env.respond_to?(meth) || super
123
- end
124
-
125
- def method_missing(meth, *args, &blk)
126
- return super unless @env && @env.respond_to?(meth)
127
-
128
- @env.__send__(meth, *args, &blk)
129
- end
130
122
  end
131
123
 
132
124
  include RequestMixin
data/lib/httpx/buffer.rb CHANGED
@@ -32,7 +32,7 @@ module HTTPX
32
32
  end
33
33
 
34
34
  def shift!(fin)
35
- @buffer = @buffer.byteslice(fin..-1)
35
+ @buffer = @buffer.byteslice(fin..-1) || "".b
36
36
  end
37
37
  end
38
38
  end
@@ -19,7 +19,7 @@ module HTTPX
19
19
  end
20
20
 
21
21
  def emit(type, *args)
22
- callbacks(type).delete_if { |pr| :delete == pr[*args] } # rubocop:disable Style/YodaCondition
22
+ callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
23
23
  end
24
24
 
25
25
  protected
@@ -34,21 +34,21 @@ module HTTPX
34
34
  branch(default_options).wrap(&blk)
35
35
  end
36
36
 
37
- def plugin(*args, **opts, &blk)
37
+ def plugin(pl, options = nil, &blk)
38
38
  klass = is_a?(Session) ? self.class : Session
39
39
  klass = Class.new(klass)
40
40
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
41
- klass.plugin(*args, **opts, &blk).new
41
+ klass.plugin(pl, options, &blk).new
42
42
  end
43
43
 
44
44
  # deprecated
45
45
  # :nocov:
46
- def plugins(*args, **opts)
46
+ def plugins(pls)
47
47
  warn ":#{__method__} is deprecated, use :plugin instead"
48
48
  klass = is_a?(Session) ? self.class : Session
49
49
  klass = Class.new(klass)
50
50
  klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
51
- klass.plugins(*args, **opts).new
51
+ klass.plugins(pls).new
52
52
  end
53
53
  # :nocov:
54
54
 
@@ -71,12 +71,19 @@ module HTTPX
71
71
  def method_missing(meth, *args, **options)
72
72
  return super unless meth =~ /\Awith_(.+)/
73
73
 
74
- option = Regexp.last_match(1).to_sym
75
- with(option => (args.first || options))
74
+ option = Regexp.last_match(1)
75
+
76
+ return super unless option
77
+
78
+ with(option.to_sym => (args.first || options))
76
79
  end
77
80
 
78
- def respond_to_missing?(meth, *args)
79
- default_options.respond_to?(meth, *args) || super
81
+ def respond_to_missing?(meth)
82
+ return super unless meth =~ /\Awith_(.+)/
83
+
84
+ option = Regexp.last_match(1)
85
+
86
+ default_options.respond_to?(option) || super
80
87
  end
81
88
  end
82
89
  end
@@ -69,10 +69,10 @@ module HTTPX
69
69
  end
70
70
 
71
71
  @inflight = 0
72
- @keep_alive_timeout = options.timeout[:keep_alive_timeout]
72
+ @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
73
73
  @keep_alive_timer = nil
74
74
 
75
- self.addresses = options.addresses if options.addresses
75
+ self.addresses = @options.addresses if @options.addresses
76
76
  end
77
77
 
78
78
  # this is a semi-private method, to be used by the resolver
@@ -263,7 +263,9 @@ module HTTPX
263
263
  request.chunk!
264
264
  end
265
265
 
266
- connection = if request.options.persistent
266
+ connection = request.headers["connection"]
267
+
268
+ connection ||= if request.options.persistent
267
269
  # when in a persistent connection, the request can't be at
268
270
  # the edge of a renegotiation
269
271
  if @requests.index(request) + 1 < @max_requests
@@ -11,7 +11,7 @@ module HTTPX
11
11
 
12
12
  MAX_CONCURRENT_REQUESTS = HTTP2Next::DEFAULT_MAX_CONCURRENT_STREAMS
13
13
 
14
- Error = Class.new(Error) do
14
+ class Error < Error
15
15
  def initialize(id, code)
16
16
  super("stream #{id} closed with error: #{code}")
17
17
  end
@@ -391,16 +391,6 @@ module HTTPX
391
391
  emit(:pong)
392
392
  end
393
393
  end
394
-
395
- def respond_to_missing?(meth, *args)
396
- @connection.respond_to?(meth, *args) || super
397
- end
398
-
399
- def method_missing(meth, *args, &blk)
400
- return super unless @connection.respond_to?(meth)
401
-
402
- @connection.__send__(meth, *args, &blk)
403
- end
404
394
  end
405
395
  Connection.register "h2", Connection::HTTP2
406
396
  end
data/lib/httpx/errors.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- Error = Class.new(StandardError)
4
+ class Error < StandardError; end
5
5
 
6
- UnsupportedSchemeError = Class.new(Error)
6
+ class UnsupportedSchemeError < Error; end
7
7
 
8
- TimeoutError = Class.new(Error) do
8
+ class TimeoutError < Error
9
9
  attr_reader :timeout
10
10
 
11
11
  def initialize(timeout, message)
@@ -20,17 +20,17 @@ module HTTPX
20
20
  end
21
21
  end
22
22
 
23
- TotalTimeoutError = Class.new(TimeoutError)
23
+ class TotalTimeoutError < TimeoutError; end
24
24
 
25
- ConnectTimeoutError = Class.new(TimeoutError)
25
+ class ConnectTimeoutError < TimeoutError; end
26
26
 
27
- SettingsTimeoutError = Class.new(TimeoutError)
27
+ class SettingsTimeoutError < TimeoutError; end
28
28
 
29
- ResolveTimeoutError = Class.new(TimeoutError)
29
+ class ResolveTimeoutError < TimeoutError; end
30
30
 
31
- ResolveError = Class.new(Error)
31
+ class ResolveError < Error; end
32
32
 
33
- NativeResolveError = Class.new(ResolveError) do
33
+ class NativeResolveError < ResolveError
34
34
  attr_reader :connection, :host
35
35
 
36
36
  def initialize(connection, host, message = "Can't resolve #{host}")
@@ -40,7 +40,7 @@ module HTTPX
40
40
  end
41
41
  end
42
42
 
43
- HTTPError = Class.new(Error) do
43
+ class HTTPError < Error
44
44
  attr_reader :response
45
45
 
46
46
  def initialize(response)
@@ -53,5 +53,5 @@ module HTTPX
53
53
  end
54
54
  end
55
55
 
56
- MisdirectedRequestError = Class.new(HTTPError)
56
+ class MisdirectedRequestError < HTTPError; end
57
57
  end
data/lib/httpx/io/ssl.rb CHANGED
@@ -7,9 +7,9 @@ module HTTPX
7
7
 
8
8
  class SSL < TCP
9
9
  TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
10
- { alpn_protocols: %w[h2 http/1.1] }
10
+ { alpn_protocols: %w[h2 http/1.1].freeze }.freeze
11
11
  else
12
- {}
12
+ {}.freeze
13
13
  end
14
14
 
15
15
  def initialize(_, _, options)
data/lib/httpx/io/tls.rb CHANGED
@@ -4,7 +4,7 @@ require "openssl"
4
4
 
5
5
  module HTTPX
6
6
  class TLS < TCP
7
- Error = Class.new(StandardError)
7
+ class Error < StandardError; end
8
8
 
9
9
  def initialize(_, _, options)
10
10
  super
data/lib/httpx/options.rb CHANGED
@@ -30,6 +30,7 @@ module HTTPX
30
30
  :request_body_class => Class.new(Request::Body),
31
31
  :response_body_class => Class.new(Response::Body),
32
32
  :connection_class => Class.new(Connection),
33
+ :options_class => Class.new(self),
33
34
  :transport => nil,
34
35
  :transport_options => nil,
35
36
  :addresses => nil,
@@ -41,39 +42,48 @@ module HTTPX
41
42
  class << self
42
43
  def new(options = {})
43
44
  # let enhanced options go through
44
- return options if self == Options && options.class > self
45
+ return options if self == Options && options.class < self
45
46
  return options if options.is_a?(self)
46
47
 
47
48
  super
48
49
  end
49
50
 
50
- def def_option(name, layout = nil, &interpreter)
51
- attr_reader name
51
+ def method_added(meth)
52
+ super
52
53
 
53
- if layout
54
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
55
- def #{name}=(value)
56
- return if value.nil?
54
+ return unless meth =~ /^option_(.+)$/
57
55
 
58
- value = begin
59
- #{layout}
60
- end
56
+ optname = Regexp.last_match(1).to_sym
61
57
 
62
- @#{name} = value
63
- end
58
+ attr_reader(optname)
59
+ end
60
+
61
+ def def_option(optname, *args, &block)
62
+ if args.size.zero? && !block_given?
63
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
64
+ def option_#{optname}(v); v; end
64
65
  OUT
66
+ return
67
+ end
65
68
 
66
- elsif interpreter
67
- define_method(:"#{name}=") do |value|
68
- return if value.nil?
69
+ deprecated_def_option(optname, *args, &block)
70
+ end
71
+
72
+ def deprecated_def_option(optname, layout = nil, &interpreter)
73
+ warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
74
+ "Define module OptionsMethods and `def option_#{optname}(val)` instead."
69
75
 
70
- instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
76
+ if layout
77
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
78
+ def option_#{optname}(value)
79
+ #{layout}
80
+ end
81
+ OUT
82
+ elsif block_given?
83
+ define_method(:"option_#{optname}") do |value|
84
+ instance_exec(value, &interpreter)
71
85
  end
72
- else
73
- attr_writer name
74
86
  end
75
-
76
- protected :"#{name}="
77
87
  end
78
88
  end
79
89
 
@@ -83,26 +93,24 @@ module HTTPX
83
93
  next if v.nil?
84
94
 
85
95
  begin
86
- __send__(:"#{k}=", v)
96
+ value = __send__(:"option_#{k}", v)
97
+ instance_variable_set(:"@#{k}", value)
87
98
  rescue NoMethodError
88
99
  raise Error, "unknown option: #{k}"
89
100
  end
90
101
  end
102
+ freeze
91
103
  end
92
104
 
93
- def_option(:origin, <<-OUT)
105
+ def option_origin(value)
94
106
  URI(value)
95
- OUT
107
+ end
96
108
 
97
- def_option(:headers, <<-OUT)
98
- if self.headers
99
- self.headers.merge(value)
100
- else
101
- Headers.new(value)
102
- end
103
- OUT
109
+ def option_headers(value)
110
+ Headers.new(value)
111
+ end
104
112
 
105
- def_option(:timeout, <<-OUT)
113
+ def option_timeout(value)
106
114
  timeouts = Hash[value]
107
115
 
108
116
  if timeouts.key?(:loop_timeout)
@@ -111,42 +119,43 @@ module HTTPX
111
119
  end
112
120
 
113
121
  timeouts
114
- OUT
122
+ end
115
123
 
116
- def_option(:max_concurrent_requests, <<-OUT)
117
- raise Error, ":max_concurrent_requests must be positive" unless value.positive?
124
+ def option_max_concurrent_requests(value)
125
+ raise TypeError, ":max_concurrent_requests must be positive" unless value.positive?
118
126
 
119
127
  value
120
- OUT
128
+ end
121
129
 
122
- def_option(:max_requests, <<-OUT)
123
- raise Error, ":max_requests must be positive" unless value.positive?
130
+ def option_max_requests(value)
131
+ raise TypeError, ":max_requests must be positive" unless value.positive?
124
132
 
125
133
  value
126
- OUT
134
+ end
127
135
 
128
- def_option(:window_size, <<-OUT)
136
+ def option_window_size(value)
129
137
  Integer(value)
130
- OUT
138
+ end
131
139
 
132
- def_option(:body_threshold_size, <<-OUT)
140
+ def option_body_threshold_size(value)
133
141
  Integer(value)
134
- OUT
142
+ end
135
143
 
136
- def_option(:transport, <<-OUT)
144
+ def option_transport(value)
137
145
  transport = value.to_s
138
- raise Error, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
146
+ raise TypeError, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
139
147
 
140
148
  transport
141
- OUT
149
+ end
142
150
 
143
- def_option(:addresses, <<-OUT)
151
+ def option_addresses(value)
144
152
  Array(value)
145
- OUT
153
+ end
146
154
 
147
155
  %i[
148
156
  params form json body ssl http2_settings
149
- request_class response_class headers_class request_body_class response_body_class connection_class
157
+ request_class response_class headers_class request_body_class
158
+ response_body_class connection_class options_class
150
159
  io fallback_protocol debug debug_level transport_options resolver_class resolver_options
151
160
  persistent
152
161
  ].each do |method_name|
@@ -180,9 +189,8 @@ module HTTPX
180
189
 
181
190
  return self if h1 == h2
182
191
 
183
- merged = h1.merge(h2) do |k, v1, v2|
184
- case k
185
- when :headers, :ssl, :http2_settings, :timeout
192
+ merged = h1.merge(h2) do |_k, v1, v2|
193
+ if v1.respond_to?(:merge) && v2.respond_to?(:merge)
186
194
  v1.merge(v2)
187
195
  else
188
196
  v2
@@ -199,28 +207,25 @@ module HTTPX
199
207
  Hash[hash_pairs]
200
208
  end
201
209
 
202
- def initialize_dup(other)
203
- self.headers = other.headers.dup
204
- self.ssl = other.ssl.dup
205
- self.request_class = other.request_class.dup
206
- self.response_class = other.response_class.dup
207
- self.headers_class = other.headers_class.dup
208
- self.request_body_class = other.request_body_class.dup
209
- self.response_body_class = other.response_body_class.dup
210
- self.connection_class = other.connection_class.dup
211
- end
212
-
213
- def freeze
214
- super
215
-
216
- headers.freeze
217
- ssl.freeze
218
- request_class.freeze
219
- response_class.freeze
220
- headers_class.freeze
221
- request_body_class.freeze
222
- response_body_class.freeze
223
- connection_class.freeze
210
+ if RUBY_VERSION > "2.4.0"
211
+ def initialize_dup(other)
212
+ instance_variables.each do |ivar|
213
+ instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
214
+ end
215
+ end
216
+ else
217
+ def initialize_dup(other)
218
+ instance_variables.each do |ivar|
219
+ value = other.instance_variable_get(ivar)
220
+ value = case value
221
+ when Symbol, Fixnum, TrueClass, FalseClass # rubocop:disable Lint/UnifiedInteger
222
+ value
223
+ else
224
+ value.dup
225
+ end
226
+ instance_variable_set(ivar, value)
227
+ end
228
+ end
224
229
  end
225
230
  end
226
231
  end