httpx 0.16.0 → 0.18.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_16_1.md +5 -0
  3. data/doc/release_notes/0_17_0.md +49 -0
  4. data/doc/release_notes/0_18_0.md +69 -0
  5. data/doc/release_notes/0_18_1.md +12 -0
  6. data/lib/httpx/adapters/datadog.rb +1 -1
  7. data/lib/httpx/adapters/faraday.rb +5 -3
  8. data/lib/httpx/adapters/webmock.rb +9 -3
  9. data/lib/httpx/altsvc.rb +2 -2
  10. data/lib/httpx/chainable.rb +4 -4
  11. data/lib/httpx/connection/http1.rb +23 -14
  12. data/lib/httpx/connection/http2.rb +35 -17
  13. data/lib/httpx/connection.rb +74 -76
  14. data/lib/httpx/domain_name.rb +1 -1
  15. data/lib/httpx/extensions.rb +50 -4
  16. data/lib/httpx/headers.rb +1 -1
  17. data/lib/httpx/io/ssl.rb +5 -1
  18. data/lib/httpx/io/tls.rb +7 -7
  19. data/lib/httpx/loggable.rb +5 -5
  20. data/lib/httpx/options.rb +35 -13
  21. data/lib/httpx/parser/http1.rb +10 -6
  22. data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
  23. data/lib/httpx/plugins/aws_sigv4.rb +9 -11
  24. data/lib/httpx/plugins/compression.rb +5 -3
  25. data/lib/httpx/plugins/cookies/jar.rb +1 -1
  26. data/lib/httpx/plugins/digest_authentication.rb +4 -4
  27. data/lib/httpx/plugins/expect.rb +7 -3
  28. data/lib/httpx/plugins/grpc/message.rb +2 -2
  29. data/lib/httpx/plugins/grpc.rb +3 -3
  30. data/lib/httpx/plugins/h2c.rb +7 -3
  31. data/lib/httpx/plugins/internal_telemetry.rb +8 -8
  32. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  33. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  34. data/lib/httpx/plugins/multipart/part.rb +2 -2
  35. data/lib/httpx/plugins/multipart.rb +16 -2
  36. data/lib/httpx/plugins/ntlm_authentication.rb +4 -4
  37. data/lib/httpx/plugins/proxy/ssh.rb +11 -4
  38. data/lib/httpx/plugins/proxy.rb +6 -4
  39. data/lib/httpx/plugins/response_cache/store.rb +55 -0
  40. data/lib/httpx/plugins/response_cache.rb +88 -0
  41. data/lib/httpx/plugins/retries.rb +36 -14
  42. data/lib/httpx/plugins/stream.rb +3 -4
  43. data/lib/httpx/pool.rb +39 -13
  44. data/lib/httpx/registry.rb +1 -1
  45. data/lib/httpx/request.rb +12 -13
  46. data/lib/httpx/resolver/https.rb +5 -7
  47. data/lib/httpx/resolver/native.rb +19 -5
  48. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  49. data/lib/httpx/resolver/system.rb +2 -0
  50. data/lib/httpx/resolver.rb +2 -2
  51. data/lib/httpx/response.rb +60 -44
  52. data/lib/httpx/selector.rb +9 -19
  53. data/lib/httpx/session.rb +22 -15
  54. data/lib/httpx/session2.rb +3 -1
  55. data/lib/httpx/timers.rb +84 -0
  56. data/lib/httpx/transcoder/body.rb +2 -1
  57. data/lib/httpx/transcoder/form.rb +20 -0
  58. data/lib/httpx/transcoder/json.rb +12 -0
  59. data/lib/httpx/transcoder.rb +62 -1
  60. data/lib/httpx/utils.rb +10 -2
  61. data/lib/httpx/version.rb +1 -1
  62. data/lib/httpx.rb +1 -0
  63. data/sig/buffer.rbs +2 -2
  64. data/sig/chainable.rbs +7 -1
  65. data/sig/connection/http1.rbs +15 -4
  66. data/sig/connection/http2.rbs +19 -5
  67. data/sig/connection.rbs +15 -9
  68. data/sig/headers.rbs +19 -18
  69. data/sig/options.rbs +13 -5
  70. data/sig/parser/http1.rbs +3 -3
  71. data/sig/plugins/aws_sdk_authentication.rbs +22 -4
  72. data/sig/plugins/aws_sigv4.rbs +12 -3
  73. data/sig/plugins/basic_authentication.rbs +1 -1
  74. data/sig/plugins/multipart.rbs +64 -8
  75. data/sig/plugins/proxy.rbs +6 -6
  76. data/sig/plugins/response_cache.rbs +35 -0
  77. data/sig/plugins/retries.rbs +3 -0
  78. data/sig/pool.rbs +6 -0
  79. data/sig/request.rbs +11 -8
  80. data/sig/resolver/native.rbs +2 -1
  81. data/sig/resolver/resolver_mixin.rbs +1 -1
  82. data/sig/resolver/system.rbs +3 -1
  83. data/sig/response.rbs +11 -4
  84. data/sig/selector.rbs +8 -6
  85. data/sig/session.rbs +8 -14
  86. data/sig/timers.rbs +32 -0
  87. data/sig/transcoder/form.rbs +1 -0
  88. data/sig/transcoder/json.rbs +1 -0
  89. data/sig/transcoder.rbs +5 -4
  90. data/sig/utils.rbs +4 -0
  91. metadata +62 -61
@@ -14,6 +14,8 @@ module HTTPX
14
14
 
15
15
  def_delegator :@body, :to_s
16
16
 
17
+ def_delegator :@body, :to_str
18
+
17
19
  def_delegator :@body, :read
18
20
 
19
21
  def_delegator :@body, :copy_to
@@ -45,7 +47,7 @@ module HTTPX
45
47
  end
46
48
 
47
49
  def content_type
48
- ContentType.parse(@headers["content-type"])
50
+ @content_type ||= ContentType.new(@headers["content-type"])
49
51
  end
50
52
 
51
53
  def complete?
@@ -55,21 +57,50 @@ module HTTPX
55
57
  # :nocov:
56
58
  def inspect
57
59
  "#<Response:#{object_id} "\
58
- "HTTP/#{version} " \
59
- "@status=#{@status} " \
60
- "@headers=#{@headers} " \
61
- "@body=#{@body.bytesize}>"
60
+ "HTTP/#{version} " \
61
+ "@status=#{@status} " \
62
+ "@headers=#{@headers} " \
63
+ "@body=#{@body.bytesize}>"
62
64
  end
63
65
  # :nocov:
64
66
 
65
- def raise_for_status
67
+ def error
66
68
  return if @status < 400
67
69
 
68
- raise HTTPError, self
70
+ HTTPError.new(self)
71
+ end
72
+
73
+ def raise_for_status
74
+ return self unless (err = error)
75
+
76
+ raise err
77
+ end
78
+
79
+ def json(options = nil)
80
+ decode("json", options)
81
+ end
82
+
83
+ def form
84
+ decode("form")
69
85
  end
70
86
 
71
87
  private
72
88
 
89
+ def decode(format, options = nil)
90
+ # TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
91
+ transcoder = Transcoder.registry(format)
92
+
93
+ raise Error, "no decoder available for \"#{format}\"" unless transcoder.respond_to?(:decode)
94
+
95
+ decoder = transcoder.decode(self)
96
+
97
+ raise Error, "no decoder available for \"#{format}\"" unless decoder
98
+
99
+ decoder.call(self, options)
100
+ rescue Registry::Error
101
+ raise Error, "no decoder available for \"#{format}\""
102
+ end
103
+
73
104
  def no_data?
74
105
  @status < 200 ||
75
106
  @status == 204 ||
@@ -93,16 +124,6 @@ module HTTPX
93
124
  @length = 0
94
125
  @buffer = nil
95
126
  @state = :idle
96
- ObjectSpace.define_finalizer(self, self.class.finalize(@buffer))
97
- end
98
-
99
- def self.finalize(buffer)
100
- proc {
101
- return unless buffer
102
-
103
- @buffer.close
104
- @buffer.unlink if @buffer.respond_to?(:unlink)
105
- }
106
127
  end
107
128
 
108
129
  def closed?
@@ -196,18 +217,20 @@ module HTTPX
196
217
  end
197
218
 
198
219
  def ==(other)
199
- if other.respond_to?(:read)
200
- _with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
201
- else
202
- to_s == other.to_s
220
+ object_id == other.object_id || begin
221
+ if other.respond_to?(:read)
222
+ _with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
223
+ else
224
+ to_s == other.to_s
225
+ end
203
226
  end
204
227
  end
205
228
 
206
229
  # :nocov:
207
230
  def inspect
208
231
  "#<HTTPX::Response::Body:#{object_id} " \
209
- "@state=#{@state} " \
210
- "@length=#{@length}>"
232
+ "@state=#{@state} " \
233
+ "@length=#{@length}>"
211
234
  end
212
235
  # :nocov:
213
236
 
@@ -264,30 +287,22 @@ module HTTPX
264
287
  MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
265
288
  CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
266
289
 
267
- attr_reader :mime_type, :charset
268
-
269
- def initialize(mime_type, charset)
270
- @mime_type = mime_type
271
- @charset = charset
290
+ def initialize(header_value)
291
+ @header_value = header_value
272
292
  end
273
293
 
274
- class << self
275
- # Parse string and return ContentType struct
276
- def parse(str)
277
- new(mime_type(str), charset(str))
278
- end
294
+ def mime_type
295
+ return @mime_type if defined?(@mime_type)
279
296
 
280
- private
297
+ m = @header_value.to_s[MIME_TYPE_RE, 1]
298
+ m && @mime_type = m.strip.downcase
299
+ end
281
300
 
282
- def mime_type(str)
283
- m = str.to_s[MIME_TYPE_RE, 1]
284
- m && m.strip.downcase
285
- end
301
+ def charset
302
+ return @charset if defined?(@charset)
286
303
 
287
- def charset(str)
288
- m = str.to_s[CHARSET_RE, 1]
289
- m && m.strip.delete('"')
290
- end
304
+ m = @header_value.to_s[CHARSET_RE, 1]
305
+ m && @charset = m.strip.delete('"')
291
306
  end
292
307
  end
293
308
 
@@ -304,17 +319,18 @@ module HTTPX
304
319
  end
305
320
 
306
321
  def status
322
+ warn ":#{__method__} is deprecated, use :error.message instead"
307
323
  @error.message
308
324
  end
309
325
 
310
326
  if Exception.method_defined?(:full_message)
311
327
  def to_s
312
- @error.full_message
328
+ @error.full_message(highlight: false)
313
329
  end
314
330
  else
315
331
  def to_s
316
332
  "#{@error.message} (#{@error.class})\n" \
317
- "#{@error.backtrace.join("\n") if @error.backtrace}"
333
+ "#{@error.backtrace.join("\n") if @error.backtrace}"
318
334
  end
319
335
  end
320
336
 
@@ -2,20 +2,6 @@
2
2
 
3
3
  require "io/wait"
4
4
 
5
- module IOExtensions
6
- refine IO do
7
- # provides a fallback for rubies where IO#wait isn't implemented,
8
- # but IO#wait_readable and IO#wait_writable are.
9
- def wait(timeout = nil, _mode = :read_write)
10
- r, w = IO.select([self], [self], nil, timeout)
11
-
12
- return unless r || w
13
-
14
- self
15
- end
16
- end
17
- end
18
-
19
5
  class HTTPX::Selector
20
6
  READABLE = %i[rw r].freeze
21
7
  WRITABLE = %i[rw w].freeze
@@ -23,7 +9,7 @@ class HTTPX::Selector
23
9
  private_constant :READABLE
24
10
  private_constant :WRITABLE
25
11
 
26
- using IOExtensions unless IO.method_defined?(:wait) && IO.instance_method(:wait).arity == 2
12
+ using HTTPX::IOExtensions
27
13
 
28
14
  def initialize
29
15
  @selectables = []
@@ -58,11 +44,13 @@ class HTTPX::Selector
58
44
  selectables = @selectables
59
45
  @selectables = []
60
46
 
61
- selectables.each do |io|
47
+ selectables.delete_if do |io|
62
48
  interests = io.interests
63
49
 
64
50
  (r ||= []) << io if READABLE.include?(interests)
65
51
  (w ||= []) << io if WRITABLE.include?(interests)
52
+
53
+ io.state == :closed
66
54
  end
67
55
 
68
56
  if @selectables.empty?
@@ -70,7 +58,7 @@ class HTTPX::Selector
70
58
 
71
59
  # do not run event loop if there's nothing to wait on.
72
60
  # this might happen if connect failed and connection was unregistered.
73
- return if (!r || r.empty?) && (!w || w.empty?)
61
+ return if (!r || r.empty?) && (!w || w.empty?) && !selectables.empty?
74
62
 
75
63
  break
76
64
  else
@@ -86,7 +74,7 @@ class HTTPX::Selector
86
74
 
87
75
  readers, writers = IO.select(r, w, nil, interval)
88
76
 
89
- raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil?
77
+ raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil? && interval
90
78
  rescue IOError, SystemCallError
91
79
  @selectables.reject!(&:closed?)
92
80
  retry
@@ -109,6 +97,8 @@ class HTTPX::Selector
109
97
  def select_one(interval)
110
98
  io = @selectables.first
111
99
 
100
+ return unless io
101
+
112
102
  interests = io.interests
113
103
 
114
104
  result = case interests
@@ -118,7 +108,7 @@ class HTTPX::Selector
118
108
  when nil then return
119
109
  end
120
110
 
121
- raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result
111
+ raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result || interval.nil?
122
112
 
123
113
  yield io
124
114
  rescue IOError, SystemCallError
data/lib/httpx/session.rb CHANGED
@@ -11,7 +11,7 @@ module HTTPX
11
11
  @options = self.class.default_options.merge(options)
12
12
  @responses = {}
13
13
  @persistent = @options.persistent
14
- wrap(&blk) if block_given?
14
+ wrap(&blk) if blk
15
15
  end
16
16
 
17
17
  def wrap
@@ -21,6 +21,7 @@ module HTTPX
21
21
  yield self
22
22
  ensure
23
23
  @persistent = prev_persistent
24
+ close unless @persistent
24
25
  end
25
26
  end
26
27
 
@@ -32,7 +33,7 @@ module HTTPX
32
33
  raise ArgumentError, "must perform at least one request" if args.empty?
33
34
 
34
35
  requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
35
- responses = send_requests(*requests, options)
36
+ responses = send_requests(*requests)
36
37
  return responses.first if responses.size == 1
37
38
 
38
39
  responses
@@ -40,7 +41,8 @@ module HTTPX
40
41
 
41
42
  def build_request(verb, uri, options = EMPTY_HASH)
42
43
  rklass = @options.request_class
43
- request = rklass.new(verb, uri, @options.merge(options).merge(persistent: @persistent))
44
+ options = @options.merge(options) unless options.is_a?(Options)
45
+ request = rklass.new(verb, uri, options.merge(persistent: @persistent))
44
46
  request.on(:response, &method(:on_response).curry(2)[request])
45
47
  request.on(:promise, &method(:on_promise))
46
48
  request
@@ -174,37 +176,38 @@ module HTTPX
174
176
  end
175
177
  end
176
178
 
177
- def send_requests(*requests, options)
178
- request_options = @options.merge(options)
179
-
180
- connections = _send_requests(requests, request_options)
181
- receive_requests(requests, connections, request_options)
179
+ def send_requests(*requests)
180
+ connections = _send_requests(requests)
181
+ receive_requests(requests, connections)
182
182
  end
183
183
 
184
- def _send_requests(requests, options)
184
+ def _send_requests(requests)
185
185
  connections = []
186
186
 
187
187
  requests.each do |request|
188
188
  error = catch(:resolve_error) do
189
- connection = find_connection(request, connections, options)
189
+ connection = find_connection(request, connections, request.options)
190
190
  connection.send(request)
191
191
  end
192
192
  next unless error.is_a?(ResolveError)
193
193
 
194
- request.emit(:response, ErrorResponse.new(request, error, options))
194
+ request.emit(:response, ErrorResponse.new(request, error, request.options))
195
195
  end
196
196
 
197
197
  connections
198
198
  end
199
199
 
200
- def receive_requests(requests, connections, options)
200
+ def receive_requests(requests, connections)
201
201
  responses = []
202
202
 
203
203
  begin
204
204
  # guarantee ordered responses
205
205
  loop do
206
206
  request = requests.first
207
- pool.next_tick until (response = fetch_response(request, connections, options))
207
+
208
+ return responses unless request
209
+
210
+ pool.next_tick until (response = fetch_response(request, connections, request.options))
208
211
 
209
212
  responses << response
210
213
  requests.shift
@@ -218,13 +221,17 @@ module HTTPX
218
221
  # opportunity to traverse the requests, hence we're returning only a fraction of the errors
219
222
  # we were supposed to. This effectively fetches the existing responses and return them.
220
223
  while (request = requests.shift)
221
- responses << fetch_response(request, connections, options)
224
+ responses << fetch_response(request, connections, request.options)
222
225
  end
223
226
  break
224
227
  end
225
228
  responses
226
229
  ensure
227
- close(connections) unless @persistent
230
+ if @persistent
231
+ pool.deactivate(connections)
232
+ else
233
+ close(connections)
234
+ end
228
235
  end
229
236
  end
230
237
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "session"
2
4
  module HTTPX
3
5
  class Session
@@ -5,7 +7,7 @@ module HTTPX
5
7
  @options = self.class.default_options.merge(options)
6
8
  @responses = {}
7
9
  @persistent = @options.persistent
8
- wrap(&blk) if block_given?
10
+ wrap(&blk) if blk
9
11
  end
10
12
 
11
13
  def wrap
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ class Timers
5
+ def initialize
6
+ @intervals = []
7
+ end
8
+
9
+ def after(interval_in_secs, &blk)
10
+ return unless interval_in_secs
11
+
12
+ # I'm assuming here that most requests will have the same
13
+ # request timeout, as in most cases they share common set of
14
+ # options. A user setting different request timeouts for 100s of
15
+ # requests will already have a hard time dealing with that.
16
+ unless (interval = @intervals.find { |t| t == interval_in_secs })
17
+ interval = Interval.new(interval_in_secs)
18
+ @intervals << interval
19
+ @intervals.sort!
20
+ end
21
+
22
+ interval << blk
23
+ end
24
+
25
+ def wait_interval
26
+ return if @intervals.empty?
27
+
28
+ @next_interval_at = Utils.now
29
+
30
+ @intervals.first.interval
31
+ end
32
+
33
+ def fire(error = nil)
34
+ raise error if error && error.timeout != @intervals.first
35
+ return if @intervals.empty? || !@next_interval_at
36
+
37
+ elapsed_time = Utils.elapsed_time(@next_interval_at)
38
+
39
+ @intervals.delete_if { |interval| interval.elapse(elapsed_time) <= 0 }
40
+ end
41
+
42
+ def cancel
43
+ @intervals.clear
44
+ end
45
+
46
+ class Interval
47
+ include Comparable
48
+
49
+ attr_reader :interval
50
+
51
+ def initialize(interval)
52
+ @interval = interval
53
+ @callbacks = []
54
+ end
55
+
56
+ def <=>(other)
57
+ @interval <=> other.interval
58
+ end
59
+
60
+ def ==(other)
61
+ return @interval == other if other.is_a?(Numeric)
62
+
63
+ @interval == other.to_f # rubocop:disable Lint/FloatComparison
64
+ end
65
+
66
+ def to_f
67
+ @interval
68
+ end
69
+
70
+ def <<(callback)
71
+ @callbacks << callback
72
+ end
73
+
74
+ def elapse(elapsed)
75
+ @interval -= elapsed
76
+
77
+ @callbacks.each(&:call) if @interval <= 0
78
+
79
+ @interval
80
+ end
81
+ end
82
+ private_constant :Interval
83
+ end
84
+ end
@@ -9,6 +9,7 @@ module HTTPX::Transcoder
9
9
  module_function
10
10
 
11
11
  class Encoder
12
+ using HTTPX::ArrayExtensions
12
13
  extend Forwardable
13
14
 
14
15
  def_delegator :@raw, :to_s
@@ -21,7 +22,7 @@ module HTTPX::Transcoder
21
22
  if @raw.respond_to?(:bytesize)
22
23
  @raw.bytesize
23
24
  elsif @raw.respond_to?(:to_ary)
24
- @raw.map(&:bytesize).reduce(0, :+)
25
+ @raw.sum(&:bytesize)
25
26
  elsif @raw.respond_to?(:size)
26
27
  @raw.size || Float::INFINITY
27
28
  elsif @raw.respond_to?(:length)
@@ -7,6 +7,8 @@ module HTTPX::Transcoder
7
7
  module Form
8
8
  module_function
9
9
 
10
+ PARAM_DEPTH_LIMIT = 32
11
+
10
12
  class Encoder
11
13
  extend Forwardable
12
14
 
@@ -31,9 +33,27 @@ module HTTPX::Transcoder
31
33
  end
32
34
  end
33
35
 
36
+ module Decoder
37
+ module_function
38
+
39
+ def call(response, _)
40
+ URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
41
+ HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
42
+ end
43
+ end
44
+ end
45
+
34
46
  def encode(form)
35
47
  Encoder.new(form)
36
48
  end
49
+
50
+ def decode(response)
51
+ content_type = response.content_type.mime_type
52
+
53
+ raise HTTPX::Error, "invalid form mime type (#{content_type})" unless content_type == "application/x-www-form-urlencoded"
54
+
55
+ Decoder
56
+ end
37
57
  end
38
58
  register "form", Form
39
59
  end
@@ -5,6 +5,10 @@ require "json"
5
5
 
6
6
  module HTTPX::Transcoder
7
7
  module JSON
8
+ JSON_REGEX = %r{\bapplication/(?:vnd\.api\+)?json\b}i.freeze
9
+
10
+ using HTTPX::RegexpExtensions unless Regexp.method_defined?(:match?)
11
+
8
12
  module_function
9
13
 
10
14
  class Encoder
@@ -27,6 +31,14 @@ module HTTPX::Transcoder
27
31
  def encode(json)
28
32
  Encoder.new(json)
29
33
  end
34
+
35
+ def decode(response)
36
+ content_type = response.content_type.mime_type
37
+
38
+ raise HTTPX::Error, "invalid json mime type (#{content_type})" unless JSON_REGEX.match?(content_type)
39
+
40
+ ::JSON.method(:parse)
41
+ end
30
42
  end
31
43
  register "json", JSON
32
44
  end
@@ -4,7 +4,11 @@ module HTTPX
4
4
  module Transcoder
5
5
  extend Registry
6
6
 
7
- def self.normalize_keys(key, value, cond = nil, &block)
7
+ using RegexpExtensions unless Regexp.method_defined?(:match?)
8
+
9
+ module_function
10
+
11
+ def normalize_keys(key, value, cond = nil, &block)
8
12
  if (cond && cond.call(value))
9
13
  block.call(key.to_s, value)
10
14
  elsif value.respond_to?(:to_ary)
@@ -23,6 +27,63 @@ module HTTPX
23
27
  block.call(key.to_s, value)
24
28
  end
25
29
  end
30
+
31
+ # based on https://github.com/rack/rack/blob/d15dd728440710cfc35ed155d66a98dc2c07ae42/lib/rack/query_parser.rb#L82
32
+ def normalize_query(params, name, v, depth)
33
+ raise Error, "params depth surpasses what's supported" if depth <= 0
34
+
35
+ name =~ /\A[\[\]]*([^\[\]]+)\]*/
36
+ k = Regexp.last_match(1) || ""
37
+ after = Regexp.last_match ? Regexp.last_match.post_match : ""
38
+
39
+ if k.empty?
40
+ return Array(v) if !v.empty? && name == "[]"
41
+
42
+ return
43
+ end
44
+
45
+ case after
46
+ when ""
47
+ params[k] = v
48
+ when "["
49
+ params[name] = v
50
+ when "[]"
51
+ params[k] ||= []
52
+ raise Error, "expected Array (got #{params[k].class}) for param '#{k}'" unless params[k].is_a?(Array)
53
+
54
+ params[k] << v
55
+ when /^\[\]\[([^\[\]]+)\]$/, /^\[\](.+)$/
56
+ child_key = Regexp.last_match(1)
57
+ params[k] ||= []
58
+ raise Error, "expected Array (got #{params[k].class}) for param '#{k}'" unless params[k].is_a?(Array)
59
+
60
+ if params[k].last.is_a?(Hash) && !params_hash_has_key?(params[k].last, child_key)
61
+ normalize_query(params[k].last, child_key, v, depth - 1)
62
+ else
63
+ params[k] << normalize_query({}, child_key, v, depth - 1)
64
+ end
65
+ else
66
+ params[k] ||= {}
67
+ raise Error, "expected Hash (got #{params[k].class}) for param '#{k}'" unless params[k].is_a?(Hash)
68
+
69
+ params[k] = normalize_query(params[k], after, v, depth - 1)
70
+ end
71
+
72
+ params
73
+ end
74
+
75
+ def params_hash_has_key?(hash, key)
76
+ return false if /\[\]/.match?(key)
77
+
78
+ key.split(/[\[\]]+/).inject(hash) do |h, part|
79
+ next h if part == ""
80
+ return false unless h.is_a?(Hash) && h.key?(part)
81
+
82
+ h[part]
83
+ end
84
+
85
+ true
86
+ end
26
87
  end
27
88
  end
28
89
 
data/lib/httpx/utils.rb CHANGED
@@ -6,6 +6,14 @@ module HTTPX
6
6
 
7
7
  module_function
8
8
 
9
+ def now
10
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
11
+ end
12
+
13
+ def elapsed_time(monotonic_timestamp)
14
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - monotonic_timestamp
15
+ end
16
+
9
17
  # The value of this field can be either an HTTP-date or a number of
10
18
  # seconds to delay after the response is received.
11
19
  def parse_retry_after(retry_after)
@@ -28,9 +36,9 @@ module HTTPX
28
36
  URIParser = URI::RFC2396_Parser.new
29
37
 
30
38
  def to_uri(uri)
31
- return Kernel.URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
39
+ return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
32
40
 
33
- uri = Kernel.URI(URIParser.escape(uri))
41
+ uri = URI(URIParser.escape(uri))
34
42
 
35
43
  non_ascii_hostname = URIParser.unescape(uri.host)
36
44
 
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.16.0"
4
+ VERSION = "0.18.1"
5
5
  end
data/lib/httpx.rb CHANGED
@@ -13,6 +13,7 @@ require "httpx/callbacks"
13
13
  require "httpx/loggable"
14
14
  require "httpx/registry"
15
15
  require "httpx/transcoder"
16
+ require "httpx/timers"
16
17
  require "httpx/pool"
17
18
  require "httpx/headers"
18
19
  require "httpx/request"
data/sig/buffer.rbs CHANGED
@@ -1,7 +1,7 @@
1
1
  module HTTPX
2
2
  class Buffer
3
3
  extend Forwardable
4
-
4
+
5
5
  include _ToS
6
6
  include _ToStr
7
7
 
@@ -13,7 +13,7 @@ module HTTPX
13
13
  def shift!: (Integer) -> void
14
14
 
15
15
  # delegated
16
- def <<: (string data) -> void
16
+ def <<: (string data) -> String
17
17
  def empty?: () -> bool
18
18
  def bytesize: () -> Integer
19
19
  def clear: () -> void