httpx 0.15.4 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_16_0.md +93 -0
  3. data/doc/release_notes/0_16_1.md +5 -0
  4. data/doc/release_notes/0_17_0.md +49 -0
  5. data/doc/release_notes/0_18_0.md +69 -0
  6. data/lib/httpx/adapters/datadog.rb +1 -1
  7. data/lib/httpx/adapters/faraday.rb +8 -14
  8. data/lib/httpx/adapters/webmock.rb +9 -3
  9. data/lib/httpx/altsvc.rb +2 -2
  10. data/lib/httpx/buffer.rb +1 -1
  11. data/lib/httpx/callbacks.rb +1 -1
  12. data/lib/httpx/chainable.rb +18 -11
  13. data/lib/httpx/connection/http1.rb +21 -13
  14. data/lib/httpx/connection/http2.rb +20 -25
  15. data/lib/httpx/connection.rb +73 -77
  16. data/lib/httpx/domain_name.rb +1 -1
  17. data/lib/httpx/errors.rb +11 -11
  18. data/lib/httpx/extensions.rb +50 -4
  19. data/lib/httpx/headers.rb +1 -1
  20. data/lib/httpx/io/ssl.rb +3 -3
  21. data/lib/httpx/io/tls.rb +8 -8
  22. data/lib/httpx/loggable.rb +5 -5
  23. data/lib/httpx/options.rb +108 -81
  24. data/lib/httpx/parser/http1.rb +11 -7
  25. data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
  26. data/lib/httpx/plugins/aws_sigv4.rb +19 -20
  27. data/lib/httpx/plugins/compression.rb +17 -14
  28. data/lib/httpx/plugins/cookies/cookie.rb +4 -2
  29. data/lib/httpx/plugins/cookies/jar.rb +21 -2
  30. data/lib/httpx/plugins/cookies.rb +20 -7
  31. data/lib/httpx/plugins/digest_authentication.rb +19 -15
  32. data/lib/httpx/plugins/expect.rb +26 -18
  33. data/lib/httpx/plugins/follow_redirects.rb +9 -9
  34. data/lib/httpx/plugins/grpc/call.rb +4 -1
  35. data/lib/httpx/plugins/grpc/message.rb +2 -2
  36. data/lib/httpx/plugins/grpc.rb +72 -46
  37. data/lib/httpx/plugins/h2c.rb +7 -3
  38. data/lib/httpx/plugins/internal_telemetry.rb +8 -8
  39. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  40. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  41. data/lib/httpx/plugins/multipart/part.rb +2 -2
  42. data/lib/httpx/plugins/multipart.rb +16 -2
  43. data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
  44. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  45. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  46. data/lib/httpx/plugins/proxy/ssh.rb +20 -13
  47. data/lib/httpx/plugins/proxy.rb +10 -10
  48. data/lib/httpx/plugins/response_cache/store.rb +55 -0
  49. data/lib/httpx/plugins/response_cache.rb +88 -0
  50. data/lib/httpx/plugins/retries.rb +46 -23
  51. data/lib/httpx/plugins/stream.rb +3 -4
  52. data/lib/httpx/plugins/upgrade.rb +7 -6
  53. data/lib/httpx/pool.rb +39 -13
  54. data/lib/httpx/registry.rb +2 -2
  55. data/lib/httpx/request.rb +16 -25
  56. data/lib/httpx/resolver/https.rb +4 -8
  57. data/lib/httpx/resolver/native.rb +19 -5
  58. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  59. data/lib/httpx/resolver/system.rb +2 -0
  60. data/lib/httpx/resolver.rb +2 -2
  61. data/lib/httpx/response.rb +91 -48
  62. data/lib/httpx/selector.rb +11 -24
  63. data/lib/httpx/session.rb +41 -23
  64. data/lib/httpx/session2.rb +23 -0
  65. data/lib/httpx/timers.rb +84 -0
  66. data/lib/httpx/transcoder/body.rb +3 -2
  67. data/lib/httpx/transcoder/chunker.rb +2 -1
  68. data/lib/httpx/transcoder/form.rb +20 -0
  69. data/lib/httpx/transcoder/json.rb +12 -0
  70. data/lib/httpx/transcoder.rb +62 -1
  71. data/lib/httpx/utils.rb +10 -2
  72. data/lib/httpx/version.rb +1 -1
  73. data/lib/httpx.rb +7 -3
  74. data/sig/buffer.rbs +3 -1
  75. data/sig/chainable.rbs +31 -29
  76. data/sig/connection/http1.rbs +11 -5
  77. data/sig/connection/http2.rbs +16 -5
  78. data/sig/connection.rbs +31 -13
  79. data/sig/errors.rbs +35 -1
  80. data/sig/headers.rbs +20 -19
  81. data/sig/httpx.rbs +4 -1
  82. data/sig/loggable.rbs +3 -1
  83. data/sig/options.rbs +45 -34
  84. data/sig/parser/http1.rbs +3 -3
  85. data/sig/plugins/authentication.rbs +1 -1
  86. data/sig/plugins/aws_sdk_authentication.rbs +25 -3
  87. data/sig/plugins/aws_sigv4.rbs +13 -5
  88. data/sig/plugins/basic_authentication.rbs +1 -1
  89. data/sig/plugins/compression.rbs +4 -6
  90. data/sig/plugins/cookies/cookie.rbs +5 -7
  91. data/sig/plugins/cookies/jar.rbs +9 -10
  92. data/sig/plugins/cookies.rbs +4 -5
  93. data/sig/plugins/digest_authentication.rbs +2 -3
  94. data/sig/plugins/expect.rbs +2 -4
  95. data/sig/plugins/follow_redirects.rbs +3 -5
  96. data/sig/plugins/grpc.rbs +4 -7
  97. data/sig/plugins/h2c.rbs +0 -2
  98. data/sig/plugins/multipart.rbs +64 -10
  99. data/sig/plugins/ntlm_authentication.rbs +2 -3
  100. data/sig/plugins/persistent.rbs +3 -8
  101. data/sig/plugins/proxy/ssh.rbs +4 -4
  102. data/sig/plugins/proxy.rbs +13 -13
  103. data/sig/plugins/push_promise.rbs +0 -2
  104. data/sig/plugins/response_cache.rbs +35 -0
  105. data/sig/plugins/retries.rbs +7 -8
  106. data/sig/plugins/stream.rbs +1 -1
  107. data/sig/plugins/upgrade.rbs +2 -3
  108. data/sig/pool.rbs +7 -2
  109. data/sig/registry.rbs +1 -1
  110. data/sig/request.rbs +11 -8
  111. data/sig/resolver/native.rbs +10 -5
  112. data/sig/resolver/resolver_mixin.rbs +4 -5
  113. data/sig/resolver/system.rbs +4 -0
  114. data/sig/resolver.rbs +7 -0
  115. data/sig/response.rbs +26 -13
  116. data/sig/selector.rbs +11 -9
  117. data/sig/session.rbs +22 -23
  118. data/sig/timers.rbs +32 -0
  119. data/sig/transcoder/body.rbs +6 -1
  120. data/sig/transcoder/chunker.rbs +8 -2
  121. data/sig/transcoder/form.rbs +3 -1
  122. data/sig/transcoder/json.rbs +2 -0
  123. data/sig/transcoder.rbs +13 -5
  124. data/sig/utils.rbs +6 -0
  125. metadata +18 -18
  126. data/lib/httpx/request2.rb +0 -14
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,
@@ -38,71 +39,100 @@ module HTTPX
38
39
  :resolver_options => { cache: true },
39
40
  }.freeze
40
41
 
42
+ begin
43
+ module HashExtensions
44
+ refine Hash do
45
+ def >=(other)
46
+ Hash[other] <= self
47
+ end
48
+
49
+ def <=(other)
50
+ other = Hash[other]
51
+ return false unless size <= other.size
52
+
53
+ each do |k, v|
54
+ v2 = other.fetch(k) { return false }
55
+ return false unless v2 == v
56
+ end
57
+ true
58
+ end
59
+ end
60
+ end
61
+ using HashExtensions
62
+ end unless Hash.method_defined?(:>=)
63
+
41
64
  class << self
42
65
  def new(options = {})
43
66
  # let enhanced options go through
44
- return options if self == Options && options.class > self
67
+ return options if self == Options && options.class < self
45
68
  return options if options.is_a?(self)
46
69
 
47
70
  super
48
71
  end
49
72
 
50
- def def_option(name, layout = nil, &interpreter)
51
- attr_reader name
73
+ def method_added(meth)
74
+ super
52
75
 
53
- if layout
54
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
55
- def #{name}=(value)
56
- return if value.nil?
76
+ return unless meth =~ /^option_(.+)$/
57
77
 
58
- value = begin
59
- #{layout}
60
- end
78
+ optname = Regexp.last_match(1).to_sym
61
79
 
62
- @#{name} = value
63
- end
80
+ attr_reader(optname)
81
+ end
82
+
83
+ def def_option(optname, *args, &block)
84
+ if args.size.zero? && !block
85
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
86
+ def option_#{optname}(v); v; end # def option_smth(v); v; end
64
87
  OUT
88
+ return
89
+ end
65
90
 
66
- elsif interpreter
67
- define_method(:"#{name}=") do |value|
68
- return if value.nil?
91
+ deprecated_def_option(optname, *args, &block)
92
+ end
93
+
94
+ def deprecated_def_option(optname, layout = nil, &interpreter)
95
+ warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
96
+ "Define module OptionsMethods and `def option_#{optname}(val)` instead."
69
97
 
70
- instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
98
+ if layout
99
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
100
+ def option_#{optname}(value) # def option_origin(v)
101
+ #{layout} # URI(v)
102
+ end # end
103
+ OUT
104
+ elsif interpreter
105
+ define_method(:"option_#{optname}") do |value|
106
+ instance_exec(value, &interpreter)
71
107
  end
72
- else
73
- attr_writer name
74
108
  end
75
-
76
- protected :"#{name}="
77
109
  end
78
110
  end
79
111
 
80
112
  def initialize(options = {})
81
113
  defaults = DEFAULT_OPTIONS.merge(options)
82
- defaults.each do |(k, v)|
114
+ defaults.each do |k, v|
83
115
  next if v.nil?
84
116
 
85
117
  begin
86
- __send__(:"#{k}=", v)
118
+ value = __send__(:"option_#{k}", v)
119
+ instance_variable_set(:"@#{k}", value)
87
120
  rescue NoMethodError
88
121
  raise Error, "unknown option: #{k}"
89
122
  end
90
123
  end
124
+ freeze
91
125
  end
92
126
 
93
- def_option(:origin, <<-OUT)
127
+ def option_origin(value)
94
128
  URI(value)
95
- OUT
129
+ end
96
130
 
97
- def_option(:headers, <<-OUT)
98
- if self.headers
99
- self.headers.merge(value)
100
- else
101
- Headers.new(value)
102
- end
103
- OUT
131
+ def option_headers(value)
132
+ Headers.new(value)
133
+ end
104
134
 
105
- def_option(:timeout, <<-OUT)
135
+ def option_timeout(value)
106
136
  timeouts = Hash[value]
107
137
 
108
138
  if timeouts.key?(:loop_timeout)
@@ -111,42 +141,43 @@ module HTTPX
111
141
  end
112
142
 
113
143
  timeouts
114
- OUT
144
+ end
115
145
 
116
- def_option(:max_concurrent_requests, <<-OUT)
117
- raise Error, ":max_concurrent_requests must be positive" unless value.positive?
146
+ def option_max_concurrent_requests(value)
147
+ raise TypeError, ":max_concurrent_requests must be positive" unless value.positive?
118
148
 
119
149
  value
120
- OUT
150
+ end
121
151
 
122
- def_option(:max_requests, <<-OUT)
123
- raise Error, ":max_requests must be positive" unless value.positive?
152
+ def option_max_requests(value)
153
+ raise TypeError, ":max_requests must be positive" unless value.positive?
124
154
 
125
155
  value
126
- OUT
156
+ end
127
157
 
128
- def_option(:window_size, <<-OUT)
158
+ def option_window_size(value)
129
159
  Integer(value)
130
- OUT
160
+ end
131
161
 
132
- def_option(:body_threshold_size, <<-OUT)
162
+ def option_body_threshold_size(value)
133
163
  Integer(value)
134
- OUT
164
+ end
135
165
 
136
- def_option(:transport, <<-OUT)
166
+ def option_transport(value)
137
167
  transport = value.to_s
138
- raise Error, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
168
+ raise TypeError, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
139
169
 
140
170
  transport
141
- OUT
171
+ end
142
172
 
143
- def_option(:addresses, <<-OUT)
173
+ def option_addresses(value)
144
174
  Array(value)
145
- OUT
175
+ end
146
176
 
147
177
  %i[
148
178
  params form json body ssl http2_settings
149
- request_class response_class headers_class request_body_class response_body_class connection_class
179
+ request_class response_class headers_class request_body_class
180
+ response_body_class connection_class options_class
150
181
  io fallback_protocol debug debug_level transport_options resolver_class resolver_options
151
182
  persistent
152
183
  ].each do |method_name|
@@ -154,6 +185,7 @@ module HTTPX
154
185
  end
155
186
 
156
187
  REQUEST_IVARS = %i[@params @form @json @body].freeze
188
+ private_constant :REQUEST_IVARS
157
189
 
158
190
  def ==(other)
159
191
  ivars = instance_variables | other.instance_variables
@@ -171,18 +203,17 @@ module HTTPX
171
203
  end
172
204
 
173
205
  def merge(other)
174
- raise ArgumentError, "#{other.inspect} is not a valid set of options" unless other.respond_to?(:to_hash)
206
+ raise ArgumentError, "#{other} is not a valid set of options" unless other.respond_to?(:to_hash)
175
207
 
176
208
  h2 = other.to_hash
177
209
  return self if h2.empty?
178
210
 
179
211
  h1 = to_hash
180
212
 
181
- return self if h1 == h2
213
+ return self if h1 >= h2
182
214
 
183
- merged = h1.merge(h2) do |k, v1, v2|
184
- case k
185
- when :headers, :ssl, :http2_settings, :timeout
215
+ merged = h1.merge(h2) do |_k, v1, v2|
216
+ if v1.respond_to?(:merge) && v2.respond_to?(:merge)
186
217
  v1.merge(v2)
187
218
  else
188
219
  v2
@@ -193,34 +224,30 @@ module HTTPX
193
224
  end
194
225
 
195
226
  def to_hash
196
- hash_pairs = instance_variables.map do |ivar|
197
- [ivar[1..-1].to_sym, instance_variable_get(ivar)]
227
+ instance_variables.each_with_object({}) do |ivar, hs|
228
+ hs[ivar[1..-1].to_sym] = instance_variable_get(ivar)
229
+ end
230
+ end
231
+
232
+ if RUBY_VERSION > "2.4.0"
233
+ def initialize_dup(other)
234
+ instance_variables.each do |ivar|
235
+ instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
236
+ end
237
+ end
238
+ else
239
+ def initialize_dup(other)
240
+ instance_variables.each do |ivar|
241
+ value = other.instance_variable_get(ivar)
242
+ value = case value
243
+ when Symbol, Fixnum, TrueClass, FalseClass # rubocop:disable Lint/UnifiedInteger
244
+ value
245
+ else
246
+ value.dup
247
+ end
248
+ instance_variable_set(ivar, value)
249
+ end
198
250
  end
199
- Hash[hash_pairs]
200
- end
201
-
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
224
251
  end
225
252
  end
226
253
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module HTTPX
4
4
  module Parser
5
- Error = Class.new(Error)
5
+ class Error < Error; end
6
6
 
7
7
  class HTTP1
8
8
  VERSIONS = %w[1.0 1.1].freeze
@@ -60,7 +60,7 @@ module HTTPX
60
60
  (m = %r{\AHTTP(?:/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?}in.match(@buffer)) ||
61
61
  raise(Error, "wrong head line format")
62
62
  version, code, _ = m.captures
63
- raise(Error, "unsupported HTTP version (HTTP/#{version})") unless VERSIONS.include?(version)
63
+ raise(Error, "unsupported HTTP version (HTTP/#{version})") unless version && VERSIONS.include?(version)
64
64
 
65
65
  @http_version = version.split(".").map(&:to_i)
66
66
  @status_code = code.to_i
@@ -72,9 +72,14 @@ module HTTPX
72
72
 
73
73
  def parse_headers
74
74
  headers = @headers
75
- while (idx = @buffer.index("\n"))
76
- line = @buffer.byteslice(0..idx).sub(/\s+\z/, "")
77
- @buffer = @buffer.byteslice((idx + 1)..-1)
75
+ buffer = @buffer
76
+
77
+ while (idx = buffer.index("\n"))
78
+ line = buffer.byteslice(0..idx)
79
+ raise Error, "wrong header format" if line.start_with?("\s", "\t")
80
+
81
+ line.lstrip!
82
+ buffer = @buffer = buffer.byteslice((idx + 1)..-1)
78
83
  if line.empty?
79
84
  case @state
80
85
  when :headers
@@ -97,9 +102,8 @@ module HTTPX
97
102
  raise Error, "wrong header format" unless separator_index
98
103
 
99
104
  key = line.byteslice(0..(separator_index - 1))
100
- raise Error, "wrong header format" if key.start_with?("\s", "\t")
101
105
 
102
- key.strip!
106
+ key.rstrip! # was lstripped previously!
103
107
  value = line.byteslice((separator_index + 1)..-1)
104
108
  value.strip!
105
109
  raise Error, "wrong header format" if value.nil?
@@ -8,6 +8,23 @@ module HTTPX
8
8
  # It requires the "aws-sdk-core" gem.
9
9
  #
10
10
  module AwsSdkAuthentication
11
+ # Mock configuration, to be used only when resolving credentials
12
+ class Configuration
13
+ attr_reader :profile
14
+
15
+ def initialize(profile)
16
+ @profile = profile
17
+ end
18
+
19
+ def respond_to_missing?(*)
20
+ true
21
+ end
22
+
23
+ def method_missing(*)
24
+ nil
25
+ end
26
+ end
27
+
11
28
  #
12
29
  # encapsulates access to an AWS SDK credentials store.
13
30
  #
@@ -30,23 +47,8 @@ module HTTPX
30
47
  end
31
48
 
32
49
  class << self
33
- attr_reader :credentials, :region
34
-
35
50
  def load_dependencies(_klass)
36
51
  require "aws-sdk-core"
37
-
38
- client = Class.new(Seahorse::Client::Base) do
39
- @identifier = :httpx
40
- set_api(Aws::S3::ClientApi::API)
41
- add_plugin(Aws::Plugins::CredentialsConfiguration)
42
- add_plugin(Aws::Plugins::RegionalEndpoint)
43
- class << self
44
- attr_reader :identifier
45
- end
46
- end.new
47
-
48
- @credentials = Credentials.new(client.config[:credentials])
49
- @region = client.config[:region]
50
52
  end
51
53
 
52
54
  def configure(klass)
@@ -56,6 +58,26 @@ module HTTPX
56
58
  def extra_options(options)
57
59
  options.merge(max_concurrent_requests: 1)
58
60
  end
61
+
62
+ def credentials(profile)
63
+ mock_configuration = Configuration.new(profile)
64
+ Credentials.new(Aws::CredentialProviderChain.new(mock_configuration).resolve)
65
+ end
66
+
67
+ def region(profile)
68
+ # https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-core/lib/aws-sdk-core/plugins/regional_endpoint.rb#L62
69
+ keys = %w[AWS_REGION AMAZON_REGION AWS_DEFAULT_REGION]
70
+ env_region = ENV.values_at(*keys).compact.first
71
+ env_region = nil if env_region == ""
72
+ cfg_region = Aws.shared_config.region(profile: profile)
73
+ env_region || cfg_region
74
+ end
75
+ end
76
+
77
+ module OptionsMethods
78
+ def option_aws_profile(value)
79
+ String(value)
80
+ end
59
81
  end
60
82
 
61
83
  module InstanceMethods
@@ -64,9 +86,11 @@ module HTTPX
64
86
  # aws_authentication(credentials: Aws::Credentials.new('akid', 'secret'))
65
87
  # aws_authentication()
66
88
  #
67
- def aws_sdk_authentication(**options)
68
- credentials = AwsSdkAuthentication.credentials
69
- region = AwsSdkAuthentication.region
89
+ def aws_sdk_authentication(
90
+ credentials: AwsSdkAuthentication.credentials(@options.aws_profile),
91
+ region: AwsSdkAuthentication.region(@options.aws_profile),
92
+ **options
93
+ )
70
94
 
71
95
  aws_sigv4_authentication(
72
96
  credentials: credentials,
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
- require "aws-sdk-s3"
5
-
6
3
  module HTTPX
7
4
  module Plugins
8
5
  #
@@ -75,16 +72,16 @@ module HTTPX
75
72
 
76
73
  # canonical request
77
74
  creq = "#{request.verb.to_s.upcase}" \
78
- "\n#{request.canonical_path}" \
79
- "\n#{request.canonical_query}" \
80
- "\n#{canonical_headers}" \
81
- "\n#{signed_headers}" \
82
- "\n#{content_hashed}"
75
+ "\n#{request.canonical_path}" \
76
+ "\n#{request.canonical_query}" \
77
+ "\n#{canonical_headers}" \
78
+ "\n#{signed_headers}" \
79
+ "\n#{content_hashed}"
83
80
 
84
81
  credential_scope = "#{date}" \
85
- "/#{@region}" \
86
- "/#{@service}" \
87
- "/#{lower_provider_prefix}_request"
82
+ "/#{@region}" \
83
+ "/#{@service}" \
84
+ "/#{lower_provider_prefix}_request"
88
85
 
89
86
  algo_line = "#{upper_provider_prefix}-HMAC-#{@algorithm}"
90
87
  # string to sign
@@ -141,22 +138,24 @@ module HTTPX
141
138
  end
142
139
 
143
140
  class << self
144
- def extra_options(options)
145
- Class.new(options.class) do
146
- def_option(:sigv4_signer, <<-OUT)
147
- value.is_a?(#{Signer}) ? value : #{Signer}.new(value)
148
- OUT
149
- end.new(options)
150
- end
151
-
152
- def load_dependencies(klass)
141
+ def load_dependencies(*)
142
+ require "set"
153
143
  require "digest/sha2"
154
144
  require "openssl"
145
+ end
146
+
147
+ def configure(klass)
155
148
  klass.plugin(:expect)
156
149
  klass.plugin(:compression)
157
150
  end
158
151
  end
159
152
 
153
+ module OptionsMethods
154
+ def option_sigv4_signer(value)
155
+ value.is_a?(Signer) ? value : Signer.new(value)
156
+ end
157
+ end
158
+
160
159
  module InstanceMethods
161
160
  def aws_sigv4_authentication(**options)
162
161
  with(sigv4_signer: Signer.new(**options))
@@ -23,21 +23,22 @@ module HTTPX
23
23
  encodings = Module.new do
24
24
  extend Registry
25
25
  end
26
+ options.merge(encodings: encodings)
27
+ end
28
+ end
26
29
 
27
- Class.new(options.class) do
28
- def_option(:compression_threshold_size, <<-OUT)
29
- bytes = Integer(value)
30
- raise Error, ":expect_threshold_size must be positive" unless bytes.positive?
30
+ module OptionsMethods
31
+ def option_compression_threshold_size(value)
32
+ bytes = Integer(value)
33
+ raise TypeError, ":expect_threshold_size must be positive" unless bytes.positive?
31
34
 
32
- bytes
33
- OUT
35
+ bytes
36
+ end
34
37
 
35
- def_option(:encodings, <<-OUT)
36
- raise Error, ":encodings must be a registry" unless value.respond_to?(:registry)
38
+ def option_encodings(value)
39
+ raise TypeError, ":encodings must be a registry" unless value.respond_to?(:registry)
37
40
 
38
- value
39
- OUT
40
- end.new(options).merge(encodings: encodings)
41
+ value
41
42
  end
42
43
  end
43
44
 
@@ -71,6 +72,8 @@ module HTTPX
71
72
  end
72
73
 
73
74
  module ResponseBodyMethods
75
+ using ArrayExtensions
76
+
74
77
  attr_reader :encodings
75
78
 
76
79
  def initialize(*)
@@ -89,7 +92,7 @@ module HTTPX
89
92
  Float::INFINITY
90
93
  end
91
94
 
92
- @_inflaters = @headers.get("content-encoding").map do |encoding|
95
+ @_inflaters = @headers.get("content-encoding").filter_map do |encoding|
93
96
  next if encoding == "identity"
94
97
 
95
98
  inflater = @options.encodings.registry(encoding).inflater(compressed_length)
@@ -99,7 +102,7 @@ module HTTPX
99
102
 
100
103
  @encodings << encoding
101
104
  inflater
102
- end.compact
105
+ end
103
106
 
104
107
  # this can happen if the only declared encoding is "identity"
105
108
  remove_instance_variable(:@_inflaters) if @_inflaters.empty?
@@ -133,7 +136,7 @@ module HTTPX
133
136
  end
134
137
 
135
138
  def each(&blk)
136
- return enum_for(__method__) unless block_given?
139
+ return enum_for(__method__) unless blk
137
140
 
138
141
  return deflate(&blk) if @buffer.size.zero?
139
142
 
@@ -43,7 +43,7 @@ module HTTPX
43
43
  # Precedence: 1. longer path 2. older creation
44
44
  (@name <=> other.name).nonzero? ||
45
45
  (other.path.length <=> @path.length).nonzero? ||
46
- (@created_at <=> other.created_at).nonzero?
46
+ (@created_at <=> other.created_at).nonzero? || 0
47
47
  end
48
48
 
49
49
  class << self
@@ -107,6 +107,8 @@ module HTTPX
107
107
 
108
108
  @path ||= "/"
109
109
  raise ArgumentError, "name must be specified" if @name.nil?
110
+
111
+ @name = @name.to_s
110
112
  end
111
113
 
112
114
  def expires
@@ -122,7 +124,7 @@ module HTTPX
122
124
  # Returns a string for use in the Cookie header, i.e. `name=value`
123
125
  # or `name="value"`.
124
126
  def cookie_value
125
- "#{@name}=#{Scanner.quote(@value)}"
127
+ "#{@name}=#{Scanner.quote(@value.to_s)}"
126
128
  end
127
129
  alias_method :to_s, :cookie_value
128
130
 
@@ -55,9 +55,9 @@ module HTTPX
55
55
  end
56
56
 
57
57
  def each(uri = nil, &blk)
58
- return enum_for(__method__, uri) unless block_given?
58
+ return enum_for(__method__, uri) unless blk
59
59
 
60
- return @store.each(&blk) unless uri
60
+ return @cookies.each(&blk) unless uri
61
61
 
62
62
  uri = URI(uri)
63
63
 
@@ -73,6 +73,25 @@ module HTTPX
73
73
  end
74
74
  end
75
75
  end
76
+
77
+ def merge(other)
78
+ cookies_dup = dup
79
+
80
+ other.each do |elem|
81
+ cookie = case elem
82
+ when Cookie
83
+ elem
84
+ when Array
85
+ Cookie.new(*elem)
86
+ else
87
+ Cookie.new(elem)
88
+ end
89
+
90
+ cookies_dup.add(cookie)
91
+ end
92
+
93
+ cookies_dup
94
+ end
76
95
  end
77
96
  end
78
97
  end
@@ -18,12 +18,10 @@ module HTTPX
18
18
  require "httpx/plugins/cookies/set_cookie_parser"
19
19
  end
20
20
 
21
- def self.extra_options(options)
22
- Class.new(options.class) do
23
- def_option(:cookies, <<-OUT)
24
- value.is_a?(#{Jar}) ? value : #{Jar}.new(value)
25
- OUT
26
- end.new(options)
21
+ module OptionsMethods
22
+ def option_cookies(value)
23
+ value.is_a?(Jar) ? value : Jar.new(value)
24
+ end
27
25
  end
28
26
 
29
27
  module InstanceMethods
@@ -63,7 +61,7 @@ module HTTPX
63
61
 
64
62
  def build_request(*, _)
65
63
  request = super
66
- request.headers.set_cookie(@options.cookies[request.uri])
64
+ request.headers.set_cookie(request.options.cookies[request.uri])
67
65
  request
68
66
  end
69
67
  end
@@ -77,6 +75,21 @@ module HTTPX
77
75
  add("cookie", header_value)
78
76
  end
79
77
  end
78
+
79
+ module OptionsMethods
80
+ def initialize(*)
81
+ super
82
+
83
+ return unless @headers.key?("cookie")
84
+
85
+ @headers.delete("cookie").each do |ck|
86
+ ck.split(/ *; */).each do |cookie|
87
+ name, value = cookie.split("=", 2)
88
+ @cookies.add(Cookie.new(name, value))
89
+ end
90
+ end
91
+ end
92
+ end
80
93
  end
81
94
  register_plugin :cookies, Cookies
82
95
  end