httpx 0.8.2 → 0.11.0
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/LICENSE.txt +48 -0
- data/README.md +14 -4
- data/doc/release_notes/0_10_0.md +66 -0
- data/doc/release_notes/0_10_1.md +37 -0
- data/doc/release_notes/0_10_2.md +5 -0
- data/doc/release_notes/0_11_0.md +76 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/lib/httpx.rb +2 -0
- data/lib/httpx/adapters/datadog.rb +205 -0
- data/lib/httpx/adapters/faraday.rb +1 -3
- data/lib/httpx/adapters/webmock.rb +123 -0
- data/lib/httpx/chainable.rb +12 -12
- data/lib/httpx/connection.rb +7 -24
- data/lib/httpx/connection/http1.rb +15 -2
- data/lib/httpx/connection/http2.rb +15 -16
- data/lib/httpx/domain_name.rb +438 -0
- data/lib/httpx/errors.rb +4 -1
- data/lib/httpx/extensions.rb +22 -2
- data/lib/httpx/headers.rb +2 -1
- data/lib/httpx/io/ssl.rb +4 -9
- data/lib/httpx/io/tcp.rb +6 -5
- data/lib/httpx/io/udp.rb +5 -2
- data/lib/httpx/options.rb +5 -1
- data/lib/httpx/parser/http1.rb +14 -17
- data/lib/httpx/plugins/compression.rb +28 -63
- data/lib/httpx/plugins/compression/brotli.rb +10 -14
- data/lib/httpx/plugins/compression/deflate.rb +7 -6
- data/lib/httpx/plugins/compression/gzip.rb +23 -5
- data/lib/httpx/plugins/cookies.rb +21 -60
- data/lib/httpx/plugins/cookies/cookie.rb +173 -0
- data/lib/httpx/plugins/cookies/jar.rb +74 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
- data/lib/httpx/plugins/expect.rb +34 -11
- data/lib/httpx/plugins/follow_redirects.rb +20 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/multipart.rb +41 -30
- data/lib/httpx/plugins/multipart/encoder.rb +115 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +64 -0
- data/lib/httpx/plugins/multipart/part.rb +34 -0
- data/lib/httpx/plugins/persistent.rb +6 -1
- data/lib/httpx/plugins/proxy.rb +16 -2
- data/lib/httpx/plugins/proxy/socks4.rb +14 -14
- data/lib/httpx/plugins/proxy/socks5.rb +3 -2
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/rate_limiter.rb +51 -0
- data/lib/httpx/plugins/retries.rb +3 -2
- data/lib/httpx/plugins/stream.rb +109 -13
- data/lib/httpx/pool.rb +14 -20
- data/lib/httpx/request.rb +29 -31
- data/lib/httpx/resolver.rb +7 -6
- data/lib/httpx/resolver/https.rb +25 -25
- data/lib/httpx/resolver/native.rb +29 -22
- data/lib/httpx/resolver/resolver_mixin.rb +4 -2
- data/lib/httpx/resolver/system.rb +3 -3
- data/lib/httpx/response.rb +16 -25
- data/lib/httpx/selector.rb +12 -18
- data/lib/httpx/session.rb +50 -39
- data/lib/httpx/transcoder.rb +20 -0
- data/lib/httpx/transcoder/chunker.rb +0 -2
- data/lib/httpx/transcoder/form.rb +9 -7
- data/lib/httpx/transcoder/json.rb +0 -4
- data/lib/httpx/utils.rb +45 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/buffer.rbs +24 -0
- data/sig/callbacks.rbs +14 -0
- data/sig/chainable.rbs +37 -0
- data/sig/connection.rbs +85 -0
- data/sig/connection/http1.rbs +66 -0
- data/sig/connection/http2.rbs +77 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +3 -0
- data/sig/headers.rbs +45 -0
- data/sig/httpx.rbs +15 -0
- data/sig/loggable.rbs +11 -0
- data/sig/options.rbs +118 -0
- data/sig/parser/http1.rbs +50 -0
- data/sig/plugins/authentication.rbs +11 -0
- data/sig/plugins/basic_authentication.rbs +13 -0
- data/sig/plugins/compression.rbs +55 -0
- data/sig/plugins/compression/brotli.rbs +21 -0
- data/sig/plugins/compression/deflate.rbs +17 -0
- data/sig/plugins/compression/gzip.rbs +29 -0
- data/sig/plugins/cookies.rbs +26 -0
- data/sig/plugins/cookies/cookie.rbs +50 -0
- data/sig/plugins/cookies/jar.rbs +27 -0
- data/sig/plugins/digest_authentication.rbs +33 -0
- data/sig/plugins/expect.rbs +19 -0
- data/sig/plugins/follow_redirects.rbs +37 -0
- data/sig/plugins/h2c.rbs +26 -0
- data/sig/plugins/multipart.rbs +44 -0
- data/sig/plugins/persistent.rbs +17 -0
- data/sig/plugins/proxy.rbs +47 -0
- data/sig/plugins/proxy/http.rbs +14 -0
- data/sig/plugins/proxy/socks4.rbs +33 -0
- data/sig/plugins/proxy/socks5.rbs +36 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/push_promise.rbs +22 -0
- data/sig/plugins/rate_limiter.rbs +11 -0
- data/sig/plugins/retries.rbs +48 -0
- data/sig/plugins/stream.rbs +39 -0
- data/sig/pool.rbs +36 -0
- data/sig/registry.rbs +9 -0
- data/sig/request.rbs +61 -0
- data/sig/resolver.rbs +26 -0
- data/sig/resolver/https.rbs +51 -0
- data/sig/resolver/native.rbs +60 -0
- data/sig/resolver/resolver_mixin.rbs +27 -0
- data/sig/resolver/system.rbs +17 -0
- data/sig/response.rbs +87 -0
- data/sig/selector.rbs +20 -0
- data/sig/session.rbs +49 -0
- data/sig/timeout.rbs +29 -0
- data/sig/transcoder.rbs +18 -0
- data/sig/transcoder/body.rbs +20 -0
- data/sig/transcoder/chunker.rbs +32 -0
- data/sig/transcoder/form.rbs +22 -0
- data/sig/transcoder/json.rbs +16 -0
- metadata +81 -41
- data/lib/httpx/resolver/options.rb +0 -25
|
@@ -38,7 +38,7 @@ module HTTPX
|
|
|
38
38
|
def early_resolve(connection, hostname: connection.origin.host)
|
|
39
39
|
addresses = connection.addresses ||
|
|
40
40
|
ip_resolve(hostname) ||
|
|
41
|
-
(@resolver_options
|
|
41
|
+
(@resolver_options[:cache] && Resolver.cached_lookup(hostname)) ||
|
|
42
42
|
system_resolve(hostname)
|
|
43
43
|
return unless addresses
|
|
44
44
|
|
|
@@ -57,11 +57,13 @@ module HTTPX
|
|
|
57
57
|
ips.map { |ip| IPAddr.new(ip) }
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def emit_resolve_error(connection, hostname, ex = nil)
|
|
60
|
+
def emit_resolve_error(connection, hostname = connection.origin.host, ex = nil)
|
|
61
61
|
emit(:error, connection, resolve_error(hostname, ex))
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def resolve_error(hostname, ex = nil)
|
|
65
|
+
return ex if ex.is_a?(ResolveError)
|
|
66
|
+
|
|
65
67
|
message = ex ? ex.message : "Can't resolve #{hostname}"
|
|
66
68
|
error = ResolveError.new(message)
|
|
67
69
|
error.set_backtrace(ex ? ex.backtrace : caller)
|
|
@@ -14,13 +14,13 @@ module HTTPX
|
|
|
14
14
|
|
|
15
15
|
def initialize(options)
|
|
16
16
|
@options = Options.new(options)
|
|
17
|
-
@resolver_options =
|
|
17
|
+
@resolver_options = @options.resolver_options
|
|
18
18
|
@state = :idle
|
|
19
|
-
resolv_options = @resolver_options.
|
|
19
|
+
resolv_options = @resolver_options.dup
|
|
20
20
|
timeouts = resolv_options.delete(:timeouts)
|
|
21
21
|
resolv_options.delete(:cache)
|
|
22
22
|
@resolver = Resolv::DNS.new(resolv_options.empty? ? nil : resolv_options)
|
|
23
|
-
@resolver.timeouts = timeouts
|
|
23
|
+
@resolver.timeouts = timeouts || Resolver::RESOLVE_TIMEOUT
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def closed?
|
data/lib/httpx/response.rb
CHANGED
|
@@ -75,11 +75,11 @@ module HTTPX
|
|
|
75
75
|
@status == 204 ||
|
|
76
76
|
@status == 205 ||
|
|
77
77
|
@status == 304 || begin
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
content_length = @headers["content-length"]
|
|
79
|
+
return false if content_length.nil?
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
content_length == "0"
|
|
82
|
+
end
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
class Body
|
|
@@ -95,6 +95,8 @@ module HTTPX
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def write(chunk)
|
|
98
|
+
return if @state == :closed
|
|
99
|
+
|
|
98
100
|
@length += chunk.bytesize
|
|
99
101
|
transition
|
|
100
102
|
@buffer.write(chunk)
|
|
@@ -116,7 +118,7 @@ module HTTPX
|
|
|
116
118
|
return enum_for(__method__) unless block_given?
|
|
117
119
|
|
|
118
120
|
begin
|
|
119
|
-
|
|
121
|
+
if @buffer
|
|
120
122
|
rewind
|
|
121
123
|
while (chunk = @buffer.read(@window_size))
|
|
122
124
|
yield(chunk.force_encoding(@encoding))
|
|
@@ -155,20 +157,19 @@ module HTTPX
|
|
|
155
157
|
if dest.respond_to?(:path) && @buffer.respond_to?(:path)
|
|
156
158
|
FileUtils.mv(@buffer.path, dest.path)
|
|
157
159
|
else
|
|
158
|
-
@buffer.rewind
|
|
159
160
|
::IO.copy_stream(@buffer, dest)
|
|
160
161
|
end
|
|
161
162
|
end
|
|
162
163
|
|
|
163
164
|
# closes/cleans the buffer, resets everything
|
|
164
165
|
def close
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
if @buffer
|
|
167
|
+
@buffer.close
|
|
168
|
+
@buffer.unlink if @buffer.respond_to?(:unlink)
|
|
169
|
+
@buffer = nil
|
|
170
|
+
end
|
|
170
171
|
@length = 0
|
|
171
|
-
@state = :
|
|
172
|
+
@state = :closed
|
|
172
173
|
end
|
|
173
174
|
|
|
174
175
|
def ==(other)
|
|
@@ -186,7 +187,7 @@ module HTTPX
|
|
|
186
187
|
private
|
|
187
188
|
|
|
188
189
|
def rewind
|
|
189
|
-
return
|
|
190
|
+
return unless @buffer
|
|
190
191
|
|
|
191
192
|
@buffer.rewind
|
|
192
193
|
end
|
|
@@ -239,13 +240,11 @@ module HTTPX
|
|
|
239
240
|
|
|
240
241
|
private
|
|
241
242
|
|
|
242
|
-
# :nodoc:
|
|
243
243
|
def mime_type(str)
|
|
244
244
|
m = str.to_s[MIME_TYPE_RE, 1]
|
|
245
245
|
m && m.strip.downcase
|
|
246
246
|
end
|
|
247
247
|
|
|
248
|
-
# :nodoc:
|
|
249
248
|
def charset(str)
|
|
250
249
|
m = str.to_s[CHARSET_RE, 1]
|
|
251
250
|
m && m.strip.delete('"')
|
|
@@ -269,15 +268,7 @@ module HTTPX
|
|
|
269
268
|
@error.message
|
|
270
269
|
end
|
|
271
270
|
|
|
272
|
-
def
|
|
273
|
-
@error.class.name
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def headers
|
|
277
|
-
{}
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def body
|
|
271
|
+
def to_s
|
|
281
272
|
@error.backtrace.join("\n")
|
|
282
273
|
end
|
|
283
274
|
|
|
@@ -287,7 +278,7 @@ module HTTPX
|
|
|
287
278
|
|
|
288
279
|
# rubocop:disable Style/MissingRespondToMissing
|
|
289
280
|
def method_missing(meth, *, &block)
|
|
290
|
-
raise NoMethodError, "undefined response method `#{meth}' for error response" if
|
|
281
|
+
raise NoMethodError, "undefined response method `#{meth}' for error response" if @options.response_class.public_method_defined?(meth)
|
|
291
282
|
|
|
292
283
|
super
|
|
293
284
|
end
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -2,21 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require "io/wait"
|
|
4
4
|
|
|
5
|
-
module IOExtensions
|
|
5
|
+
module IOExtensions
|
|
6
6
|
refine IO do
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return unless r || w
|
|
17
|
-
|
|
18
|
-
self
|
|
19
|
-
end
|
|
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
|
|
20
15
|
end
|
|
21
16
|
end
|
|
22
17
|
end
|
|
@@ -51,7 +46,7 @@ class HTTPX::Selector
|
|
|
51
46
|
READ_INTERESTS = %i[r rw].freeze
|
|
52
47
|
WRITE_INTERESTS = %i[w rw].freeze
|
|
53
48
|
|
|
54
|
-
def select_many(interval)
|
|
49
|
+
def select_many(interval, &block)
|
|
55
50
|
selectables, r, w = nil
|
|
56
51
|
|
|
57
52
|
# first, we group IOs based on interest type. On call to #interests however,
|
|
@@ -102,9 +97,7 @@ class HTTPX::Selector
|
|
|
102
97
|
writers.delete(io)
|
|
103
98
|
end if readers
|
|
104
99
|
|
|
105
|
-
writers.each
|
|
106
|
-
yield io
|
|
107
|
-
end if writers
|
|
100
|
+
writers.each(&block) if writers
|
|
108
101
|
end
|
|
109
102
|
|
|
110
103
|
def select_one(interval)
|
|
@@ -124,6 +117,7 @@ class HTTPX::Selector
|
|
|
124
117
|
yield io
|
|
125
118
|
rescue IOError, SystemCallError
|
|
126
119
|
@selectables.reject!(&:closed?)
|
|
120
|
+
raise unless @selectables.empty?
|
|
127
121
|
end
|
|
128
122
|
|
|
129
123
|
def select(interval, &block)
|
data/lib/httpx/session.rb
CHANGED
|
@@ -5,7 +5,9 @@ module HTTPX
|
|
|
5
5
|
include Loggable
|
|
6
6
|
include Chainable
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
EMPTY_HASH = {}.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(options = EMPTY_HASH, &blk)
|
|
9
11
|
@options = self.class.default_options.merge(options)
|
|
10
12
|
@responses = {}
|
|
11
13
|
@persistent = @options.persistent
|
|
@@ -29,13 +31,21 @@ module HTTPX
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def request(*args, **options)
|
|
32
|
-
requests = build_requests(*args, options)
|
|
34
|
+
requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
|
|
33
35
|
responses = send_requests(*requests, options)
|
|
34
36
|
return responses.first if responses.size == 1
|
|
35
37
|
|
|
36
38
|
responses
|
|
37
39
|
end
|
|
38
40
|
|
|
41
|
+
def build_request(verb, uri, options = EMPTY_HASH)
|
|
42
|
+
rklass = @options.request_class
|
|
43
|
+
request = rklass.new(verb, uri, @options.merge(options).merge(persistent: @persistent))
|
|
44
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
|
45
|
+
request.on(:promise, &method(:on_promise))
|
|
46
|
+
request
|
|
47
|
+
end
|
|
48
|
+
|
|
39
49
|
private
|
|
40
50
|
|
|
41
51
|
def pool
|
|
@@ -56,7 +66,8 @@ module HTTPX
|
|
|
56
66
|
end
|
|
57
67
|
|
|
58
68
|
def find_connection(request, connections, options)
|
|
59
|
-
uri =
|
|
69
|
+
uri = request.uri
|
|
70
|
+
|
|
60
71
|
connection = pool.find_connection(uri, options) || build_connection(uri, options)
|
|
61
72
|
unless connections.nil? || connections.include?(connection)
|
|
62
73
|
connections << connection
|
|
@@ -66,10 +77,16 @@ module HTTPX
|
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
def set_connection_callbacks(connection, connections, options)
|
|
69
|
-
connection.on(:
|
|
70
|
-
other_connection =
|
|
80
|
+
connection.on(:misdirected) do |misdirected_request|
|
|
81
|
+
other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
|
|
82
|
+
other_connection.merge(connection)
|
|
83
|
+
catch(:coalesced) do
|
|
84
|
+
pool.init_connection(other_connection, options)
|
|
85
|
+
end
|
|
86
|
+
set_connection_callbacks(other_connection, connections, options)
|
|
71
87
|
connections << other_connection
|
|
72
|
-
|
|
88
|
+
misdirected_request.transition(:idle)
|
|
89
|
+
other_connection.send(misdirected_request)
|
|
73
90
|
end
|
|
74
91
|
connection.on(:altsvc) do |alt_origin, origin, alt_params|
|
|
75
92
|
other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
|
|
@@ -119,23 +136,20 @@ module HTTPX
|
|
|
119
136
|
def build_requests(*args, options)
|
|
120
137
|
request_options = @options.merge(options)
|
|
121
138
|
|
|
122
|
-
requests =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
end
|
|
137
|
-
else
|
|
138
|
-
raise ArgumentError, "unsupported number of arguments"
|
|
139
|
+
requests = if args.size == 1
|
|
140
|
+
reqs = args.first
|
|
141
|
+
reqs.map do |verb, uri, opts = EMPTY_HASH|
|
|
142
|
+
build_request(verb, uri, request_options.merge(opts))
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
verb, uris = args
|
|
146
|
+
if uris.respond_to?(:each)
|
|
147
|
+
uris.enum_for(:each).map do |uri, opts = EMPTY_HASH|
|
|
148
|
+
build_request(verb, uri, request_options.merge(opts))
|
|
149
|
+
end
|
|
150
|
+
else
|
|
151
|
+
[build_request(verb, uris, request_options)]
|
|
152
|
+
end
|
|
139
153
|
end
|
|
140
154
|
raise ArgumentError, "wrong number of URIs (given 0, expect 1..+1)" if requests.empty?
|
|
141
155
|
|
|
@@ -179,15 +193,13 @@ module HTTPX
|
|
|
179
193
|
begin
|
|
180
194
|
# guarantee ordered responses
|
|
181
195
|
loop do
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
pool.next_tick until (response = fetch_response(request, connections, request_options))
|
|
196
|
+
request = requests.first
|
|
197
|
+
pool.next_tick until (response = fetch_response(request, connections, request_options))
|
|
185
198
|
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
responses << response
|
|
200
|
+
requests.shift
|
|
188
201
|
|
|
189
|
-
|
|
190
|
-
end
|
|
202
|
+
break if requests.empty? || pool.empty?
|
|
191
203
|
end
|
|
192
204
|
responses
|
|
193
205
|
ensure
|
|
@@ -195,14 +207,6 @@ module HTTPX
|
|
|
195
207
|
end
|
|
196
208
|
end
|
|
197
209
|
|
|
198
|
-
def build_request(verb, uri, options)
|
|
199
|
-
rklass = @options.request_class
|
|
200
|
-
request = rklass.new(verb, uri, @options.merge(options).merge(persistent: @persistent))
|
|
201
|
-
request.on(:response, &method(:on_response).curry[request])
|
|
202
|
-
request.on(:promise, &method(:on_promise))
|
|
203
|
-
request
|
|
204
|
-
end
|
|
205
|
-
|
|
206
210
|
@default_options = Options.new
|
|
207
211
|
@default_options.freeze
|
|
208
212
|
@plugins = []
|
|
@@ -219,7 +223,7 @@ module HTTPX
|
|
|
219
223
|
def plugin(pl, options = nil, &block)
|
|
220
224
|
# raise Error, "Cannot add a plugin to a frozen config" if frozen?
|
|
221
225
|
pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
|
|
222
|
-
|
|
226
|
+
if !@plugins.include?(pl)
|
|
223
227
|
@plugins << pl
|
|
224
228
|
pl.load_dependencies(self, &block) if pl.respond_to?(:load_dependencies)
|
|
225
229
|
@default_options = @default_options.dup
|
|
@@ -243,6 +247,13 @@ module HTTPX
|
|
|
243
247
|
opts.connection_class.__send__(:include, pl::ConnectionMethods) if defined?(pl::ConnectionMethods)
|
|
244
248
|
pl.configure(self, &block) if pl.respond_to?(:configure)
|
|
245
249
|
|
|
250
|
+
@default_options.freeze
|
|
251
|
+
elsif options
|
|
252
|
+
# this can happen when two plugins are loaded, an one of them calls the other under the hood,
|
|
253
|
+
# albeit changing some default.
|
|
254
|
+
@default_options = @default_options.dup
|
|
255
|
+
@default_options = @default_options.merge(options)
|
|
256
|
+
|
|
246
257
|
@default_options.freeze
|
|
247
258
|
end
|
|
248
259
|
self
|
data/lib/httpx/transcoder.rb
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
module HTTPX
|
|
4
4
|
module Transcoder
|
|
5
5
|
extend Registry
|
|
6
|
+
|
|
7
|
+
def self.normalize_keys(key, value, cond = nil, &block)
|
|
8
|
+
if (cond && cond.call(value))
|
|
9
|
+
block.call(key.to_s, value)
|
|
10
|
+
elsif value.respond_to?(:to_ary)
|
|
11
|
+
if value.empty?
|
|
12
|
+
block.call("#{key}[]")
|
|
13
|
+
else
|
|
14
|
+
value.to_ary.each do |element|
|
|
15
|
+
normalize_keys("#{key}[]", element, cond, &block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
elsif value.respond_to?(:to_hash)
|
|
19
|
+
value.to_hash.each do |child_key, child_value|
|
|
20
|
+
normalize_keys("#{key}[#{child_key}]", child_value, cond, &block)
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
block.call(key.to_s, value)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
6
26
|
end
|
|
7
27
|
end
|
|
8
28
|
|
|
@@ -12,21 +12,23 @@ module HTTPX::Transcoder
|
|
|
12
12
|
|
|
13
13
|
def_delegator :@raw, :to_s
|
|
14
14
|
|
|
15
|
-
def_delegator :@raw, :
|
|
15
|
+
def_delegator :@raw, :to_str
|
|
16
16
|
|
|
17
|
-
def_delegator :@raw, :
|
|
17
|
+
def_delegator :@raw, :bytesize
|
|
18
18
|
|
|
19
19
|
def initialize(form)
|
|
20
|
-
@raw =
|
|
20
|
+
@raw = form.each_with_object("".b) do |(key, val), buf|
|
|
21
|
+
HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
|
|
22
|
+
buf << "&" unless buf.empty?
|
|
23
|
+
buf << URI.encode_www_form_component(k)
|
|
24
|
+
buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def content_type
|
|
24
30
|
"application/x-www-form-urlencoded"
|
|
25
31
|
end
|
|
26
|
-
|
|
27
|
-
def to_str
|
|
28
|
-
@raw.to_s
|
|
29
|
-
end
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def encode(form)
|
|
@@ -10,14 +10,10 @@ module HTTPX::Transcoder
|
|
|
10
10
|
class Encoder
|
|
11
11
|
extend Forwardable
|
|
12
12
|
|
|
13
|
-
def_delegator :@raw, :to_str
|
|
14
|
-
|
|
15
13
|
def_delegator :@raw, :to_s
|
|
16
14
|
|
|
17
15
|
def_delegator :@raw, :bytesize
|
|
18
16
|
|
|
19
|
-
def_delegator :@raw, :force_encoding
|
|
20
|
-
|
|
21
17
|
def initialize(json)
|
|
22
18
|
@raw = ::JSON.dump(json)
|
|
23
19
|
@charset = @raw.encoding.name.downcase
|
data/lib/httpx/utils.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Utils
|
|
5
|
+
using URIExtensions
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# The value of this field can be either an HTTP-date or a number of
|
|
10
|
+
# seconds to delay after the response is received.
|
|
11
|
+
def parse_retry_after(retry_after)
|
|
12
|
+
# first: bet on it being an integer
|
|
13
|
+
Integer(retry_after)
|
|
14
|
+
rescue ArgumentError
|
|
15
|
+
# Then it's a datetime
|
|
16
|
+
time = Time.httpdate(retry_after)
|
|
17
|
+
time - Time.now
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if RUBY_VERSION < "2.3"
|
|
21
|
+
def uri(*args)
|
|
22
|
+
URI(*args)
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
|
|
26
|
+
URIParser = URI::RFC2396_Parser.new
|
|
27
|
+
|
|
28
|
+
def uri(uri)
|
|
29
|
+
return Kernel.URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
|
|
30
|
+
|
|
31
|
+
uri = Kernel.URI(URIParser.escape(uri))
|
|
32
|
+
|
|
33
|
+
non_ascii_hostname = URIParser.unescape(uri.host)
|
|
34
|
+
|
|
35
|
+
non_ascii_hostname.force_encoding(Encoding::UTF_8)
|
|
36
|
+
|
|
37
|
+
idna_hostname = DomainName.new(non_ascii_hostname).hostname
|
|
38
|
+
|
|
39
|
+
uri.host = idna_hostname
|
|
40
|
+
uri.non_ascii_hostname = non_ascii_hostname
|
|
41
|
+
uri
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|