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.
- checksums.yaml +4 -4
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/doc/release_notes/0_18_0.md +69 -0
- data/doc/release_notes/0_18_1.md +12 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +5 -3
- data/lib/httpx/adapters/webmock.rb +9 -3
- data/lib/httpx/altsvc.rb +2 -2
- data/lib/httpx/chainable.rb +4 -4
- data/lib/httpx/connection/http1.rb +23 -14
- data/lib/httpx/connection/http2.rb +35 -17
- data/lib/httpx/connection.rb +74 -76
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/extensions.rb +50 -4
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/ssl.rb +5 -1
- data/lib/httpx/io/tls.rb +7 -7
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +35 -13
- data/lib/httpx/parser/http1.rb +10 -6
- data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
- data/lib/httpx/plugins/aws_sigv4.rb +9 -11
- data/lib/httpx/plugins/compression.rb +5 -3
- data/lib/httpx/plugins/cookies/jar.rb +1 -1
- data/lib/httpx/plugins/digest_authentication.rb +4 -4
- data/lib/httpx/plugins/expect.rb +7 -3
- data/lib/httpx/plugins/grpc/message.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +3 -3
- data/lib/httpx/plugins/h2c.rb +7 -3
- data/lib/httpx/plugins/internal_telemetry.rb +8 -8
- data/lib/httpx/plugins/multipart/decoder.rb +187 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
- data/lib/httpx/plugins/multipart/part.rb +2 -2
- data/lib/httpx/plugins/multipart.rb +16 -2
- data/lib/httpx/plugins/ntlm_authentication.rb +4 -4
- data/lib/httpx/plugins/proxy/ssh.rb +11 -4
- data/lib/httpx/plugins/proxy.rb +6 -4
- data/lib/httpx/plugins/response_cache/store.rb +55 -0
- data/lib/httpx/plugins/response_cache.rb +88 -0
- data/lib/httpx/plugins/retries.rb +36 -14
- data/lib/httpx/plugins/stream.rb +3 -4
- data/lib/httpx/pool.rb +39 -13
- data/lib/httpx/registry.rb +1 -1
- data/lib/httpx/request.rb +12 -13
- data/lib/httpx/resolver/https.rb +5 -7
- data/lib/httpx/resolver/native.rb +19 -5
- data/lib/httpx/resolver/resolver_mixin.rb +2 -1
- data/lib/httpx/resolver/system.rb +2 -0
- data/lib/httpx/resolver.rb +2 -2
- data/lib/httpx/response.rb +60 -44
- data/lib/httpx/selector.rb +9 -19
- data/lib/httpx/session.rb +22 -15
- data/lib/httpx/session2.rb +3 -1
- data/lib/httpx/timers.rb +84 -0
- data/lib/httpx/transcoder/body.rb +2 -1
- data/lib/httpx/transcoder/form.rb +20 -0
- data/lib/httpx/transcoder/json.rb +12 -0
- data/lib/httpx/transcoder.rb +62 -1
- data/lib/httpx/utils.rb +10 -2
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/buffer.rbs +2 -2
- data/sig/chainable.rbs +7 -1
- data/sig/connection/http1.rbs +15 -4
- data/sig/connection/http2.rbs +19 -5
- data/sig/connection.rbs +15 -9
- data/sig/headers.rbs +19 -18
- data/sig/options.rbs +13 -5
- data/sig/parser/http1.rbs +3 -3
- data/sig/plugins/aws_sdk_authentication.rbs +22 -4
- data/sig/plugins/aws_sigv4.rbs +12 -3
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/multipart.rbs +64 -8
- data/sig/plugins/proxy.rbs +6 -6
- data/sig/plugins/response_cache.rbs +35 -0
- data/sig/plugins/retries.rbs +3 -0
- data/sig/pool.rbs +6 -0
- data/sig/request.rbs +11 -8
- data/sig/resolver/native.rbs +2 -1
- data/sig/resolver/resolver_mixin.rbs +1 -1
- data/sig/resolver/system.rbs +3 -1
- data/sig/response.rbs +11 -4
- data/sig/selector.rbs +8 -6
- data/sig/session.rbs +8 -14
- data/sig/timers.rbs +32 -0
- data/sig/transcoder/form.rbs +1 -0
- data/sig/transcoder/json.rbs +1 -0
- data/sig/transcoder.rbs +5 -4
- data/sig/utils.rbs +4 -0
- metadata +62 -61
data/lib/httpx/response.rb
CHANGED
@@ -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.
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
"HTTP/#{version} " \
|
61
|
+
"@status=#{@status} " \
|
62
|
+
"@headers=#{@headers} " \
|
63
|
+
"@body=#{@body.bytesize}>"
|
62
64
|
end
|
63
65
|
# :nocov:
|
64
66
|
|
65
|
-
def
|
67
|
+
def error
|
66
68
|
return if @status < 400
|
67
69
|
|
68
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
210
|
-
|
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
|
-
|
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
|
-
|
275
|
-
|
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
|
-
|
297
|
+
m = @header_value.to_s[MIME_TYPE_RE, 1]
|
298
|
+
m && @mime_type = m.strip.downcase
|
299
|
+
end
|
281
300
|
|
282
|
-
|
283
|
-
|
284
|
-
m && m.strip.downcase
|
285
|
-
end
|
301
|
+
def charset
|
302
|
+
return @charset if defined?(@charset)
|
286
303
|
|
287
|
-
|
288
|
-
|
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
|
-
|
333
|
+
"#{@error.backtrace.join("\n") if @error.backtrace}"
|
318
334
|
end
|
319
335
|
end
|
320
336
|
|
data/lib/httpx/selector.rb
CHANGED
@@ -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
|
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.
|
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
|
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
|
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
|
-
|
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
|
178
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
230
|
+
if @persistent
|
231
|
+
pool.deactivate(connections)
|
232
|
+
else
|
233
|
+
close(connections)
|
234
|
+
end
|
228
235
|
end
|
229
236
|
end
|
230
237
|
|
data/lib/httpx/session2.rb
CHANGED
@@ -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
|
10
|
+
wrap(&blk) if blk
|
9
11
|
end
|
10
12
|
|
11
13
|
def wrap
|
data/lib/httpx/timers.rb
ADDED
@@ -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.
|
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
|
data/lib/httpx/transcoder.rb
CHANGED
@@ -4,7 +4,11 @@ module HTTPX
|
|
4
4
|
module Transcoder
|
5
5
|
extend Registry
|
6
6
|
|
7
|
-
|
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
|
39
|
+
return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
|
32
40
|
|
33
|
-
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
data/lib/httpx.rb
CHANGED
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) ->
|
16
|
+
def <<: (string data) -> String
|
17
17
|
def empty?: () -> bool
|
18
18
|
def bytesize: () -> Integer
|
19
19
|
def clear: () -> void
|