httpx 0.10.0 → 0.11.2
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/README.md +11 -3
- 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_11_1.md +1 -0
- data/doc/release_notes/0_11_2.md +5 -0
- data/lib/httpx/adapters/datadog.rb +205 -0
- data/lib/httpx/adapters/faraday.rb +0 -2
- data/lib/httpx/adapters/webmock.rb +123 -0
- data/lib/httpx/chainable.rb +8 -7
- data/lib/httpx/connection.rb +4 -15
- data/lib/httpx/connection/http1.rb +14 -1
- data/lib/httpx/connection/http2.rb +15 -16
- data/lib/httpx/domain_name.rb +1 -3
- data/lib/httpx/errors.rb +3 -1
- data/lib/httpx/headers.rb +1 -0
- data/lib/httpx/io/ssl.rb +4 -8
- data/lib/httpx/io/udp.rb +4 -3
- data/lib/httpx/plugins/compression.rb +1 -1
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
- data/lib/httpx/plugins/expect.rb +33 -8
- data/lib/httpx/plugins/multipart.rb +42 -23
- 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/proxy.rb +16 -2
- data/lib/httpx/plugins/proxy/socks4.rb +14 -16
- data/lib/httpx/plugins/proxy/socks5.rb +3 -2
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/pool.rb +8 -14
- data/lib/httpx/request.rb +22 -12
- data/lib/httpx/resolver.rb +7 -6
- data/lib/httpx/resolver/https.rb +18 -23
- data/lib/httpx/resolver/native.rb +22 -19
- data/lib/httpx/resolver/resolver_mixin.rb +4 -2
- data/lib/httpx/resolver/system.rb +3 -3
- data/lib/httpx/selector.rb +9 -13
- data/lib/httpx/session.rb +24 -21
- data/lib/httpx/transcoder.rb +20 -0
- data/lib/httpx/transcoder/form.rb +9 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/connection.rbs +84 -1
- data/sig/connection/http1.rbs +66 -0
- data/sig/connection/http2.rbs +73 -0
- data/sig/headers.rbs +3 -0
- data/sig/httpx.rbs +1 -0
- data/sig/options.rbs +3 -3
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/compression.rbs +1 -1
- data/sig/plugins/compression/brotli.rbs +1 -1
- data/sig/plugins/compression/deflate.rbs +1 -1
- data/sig/plugins/compression/gzip.rbs +1 -1
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/multipart.rbs +29 -4
- data/sig/plugins/persistent.rbs +1 -1
- data/sig/plugins/proxy.rbs +2 -2
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/pool.rbs +36 -2
- data/sig/request.rbs +2 -2
- 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 +2 -2
- data/sig/selector.rbs +20 -0
- data/sig/session.rbs +3 -3
- data/sig/transcoder.rbs +4 -2
- data/sig/transcoder/body.rbs +2 -0
- data/sig/transcoder/form.rbs +8 -2
- data/sig/transcoder/json.rbs +3 -1
- metadata +47 -48
- data/lib/httpx/resolver/options.rb +0 -25
- data/sig/missing.rbs +0 -12
- data/sig/test.rbs +0 -9
data/lib/httpx/resolver/https.rb
CHANGED
@@ -21,6 +21,7 @@ module HTTPX
|
|
21
21
|
DEFAULTS = {
|
22
22
|
uri: NAMESERVER,
|
23
23
|
use_get: false,
|
24
|
+
record_types: RECORD_TYPES.keys,
|
24
25
|
}.freeze
|
25
26
|
|
26
27
|
def_delegator :@connections, :empty?
|
@@ -29,27 +30,29 @@ module HTTPX
|
|
29
30
|
|
30
31
|
def initialize(options)
|
31
32
|
@options = Options.new(options)
|
32
|
-
@resolver_options =
|
33
|
-
@_record_types = Hash.new { |types, host| types[host] =
|
33
|
+
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
34
|
+
@_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
|
34
35
|
@queries = {}
|
35
36
|
@requests = {}
|
36
37
|
@connections = []
|
37
|
-
@uri = URI(@resolver_options
|
38
|
+
@uri = URI(@resolver_options[:uri])
|
38
39
|
@uri_addresses = nil
|
40
|
+
@resolver = Resolv::DNS.new
|
41
|
+
@resolver.timeouts = @resolver_options.fetch(:timeouts, Resolver::RESOLVE_TIMEOUT)
|
39
42
|
end
|
40
43
|
|
41
44
|
def <<(connection)
|
42
45
|
return if @uri.origin == connection.origin.to_s
|
43
46
|
|
44
|
-
@uri_addresses ||=
|
47
|
+
@uri_addresses ||= ip_resolve(@uri.host) || system_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
|
45
48
|
|
46
49
|
if @uri_addresses.empty?
|
47
|
-
ex = ResolveError.new("Can't resolve #{
|
50
|
+
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
48
51
|
ex.set_backtrace(caller)
|
49
|
-
|
50
|
-
else
|
51
|
-
early_resolve(connection) || resolve(connection)
|
52
|
+
throw(:resolve_error, ex)
|
52
53
|
end
|
54
|
+
|
55
|
+
early_resolve(connection) || resolve(connection)
|
53
56
|
end
|
54
57
|
|
55
58
|
def timeout
|
@@ -70,12 +73,6 @@ module HTTPX
|
|
70
73
|
|
71
74
|
private
|
72
75
|
|
73
|
-
def connect
|
74
|
-
return if @queries.empty?
|
75
|
-
|
76
|
-
resolver_connection.__send__(__method__)
|
77
|
-
end
|
78
|
-
|
79
76
|
def pool
|
80
77
|
Thread.current[:httpx_connection_pool] ||= Pool.new
|
81
78
|
end
|
@@ -100,10 +97,12 @@ module HTTPX
|
|
100
97
|
hostname = connection.origin.host
|
101
98
|
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
102
99
|
end
|
103
|
-
type = @_record_types[hostname].first
|
100
|
+
type = @_record_types[hostname].first || "A"
|
104
101
|
log { "resolver: query #{type} for #{hostname}" }
|
105
102
|
begin
|
106
103
|
request = build_request(hostname, type)
|
104
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
105
|
+
request.on(:promise, &method(:on_promise))
|
107
106
|
@requests[request] = connection
|
108
107
|
resolver_connection.send(request)
|
109
108
|
@queries[hostname] = connection
|
@@ -118,9 +117,7 @@ module HTTPX
|
|
118
117
|
rescue StandardError => e
|
119
118
|
connection = @requests[request]
|
120
119
|
hostname = @queries.key(connection)
|
121
|
-
|
122
|
-
error.set_backtrace(e.backtrace)
|
123
|
-
emit(:error, connection, error)
|
120
|
+
emit_resolve_error(connection, hostname, e)
|
124
121
|
else
|
125
122
|
parse(response)
|
126
123
|
ensure
|
@@ -143,7 +140,7 @@ module HTTPX
|
|
143
140
|
return
|
144
141
|
end
|
145
142
|
end
|
146
|
-
if answers.empty?
|
143
|
+
if answers.nil? || answers.empty?
|
147
144
|
host, connection = @queries.first
|
148
145
|
@_record_types[host].shift
|
149
146
|
if @_record_types[host].empty?
|
@@ -177,7 +174,7 @@ module HTTPX
|
|
177
174
|
next unless connection # probably a retried query for which there's an answer
|
178
175
|
|
179
176
|
@connections.delete(connection)
|
180
|
-
Resolver.cached_lookup_set(hostname, addresses) if @resolver_options
|
177
|
+
Resolver.cached_lookup_set(hostname, addresses) if @resolver_options[:cache]
|
181
178
|
emit_addresses(connection, addresses.map { |addr| addr["data"] })
|
182
179
|
end
|
183
180
|
end
|
@@ -191,7 +188,7 @@ module HTTPX
|
|
191
188
|
rklass = @options.request_class
|
192
189
|
payload = Resolver.encode_dns_query(hostname, type: RECORD_TYPES[type])
|
193
190
|
|
194
|
-
if @resolver_options
|
191
|
+
if @resolver_options[:use_get]
|
195
192
|
params = URI.decode_www_form(uri.query.to_s)
|
196
193
|
params << ["type", type]
|
197
194
|
params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
|
@@ -202,8 +199,6 @@ module HTTPX
|
|
202
199
|
request.headers["content-type"] = "application/dns-message"
|
203
200
|
end
|
204
201
|
request.headers["accept"] = "application/dns-message"
|
205
|
-
request.on(:response, &method(:on_response).curry[request])
|
206
|
-
request.on(:promise, &method(:on_promise))
|
207
202
|
request
|
208
203
|
end
|
209
204
|
|
@@ -9,18 +9,16 @@ module HTTPX
|
|
9
9
|
include Resolver::ResolverMixin
|
10
10
|
using URIExtensions
|
11
11
|
|
12
|
-
RESOLVE_TIMEOUT = 5
|
13
12
|
RECORD_TYPES = {
|
14
13
|
"A" => Resolv::DNS::Resource::IN::A,
|
15
14
|
"AAAA" => Resolv::DNS::Resource::IN::AAAA,
|
16
15
|
}.freeze
|
17
16
|
|
18
|
-
# :nocov:
|
19
17
|
DEFAULTS = if RUBY_VERSION < "2.2"
|
20
18
|
{
|
21
19
|
**Resolv::DNS::Config.default_config_hash,
|
22
20
|
packet_size: 512,
|
23
|
-
timeouts: RESOLVE_TIMEOUT,
|
21
|
+
timeouts: Resolver::RESOLVE_TIMEOUT,
|
24
22
|
record_types: RECORD_TYPES.keys,
|
25
23
|
}.freeze
|
26
24
|
else
|
@@ -28,7 +26,7 @@ module HTTPX
|
|
28
26
|
nameserver: nil,
|
29
27
|
**Resolv::DNS::Config.default_config_hash,
|
30
28
|
packet_size: 512,
|
31
|
-
timeouts: RESOLVE_TIMEOUT,
|
29
|
+
timeouts: Resolver::RESOLVE_TIMEOUT,
|
32
30
|
record_types: RECORD_TYPES.keys,
|
33
31
|
}.freeze
|
34
32
|
end
|
@@ -44,7 +42,6 @@ module HTTPX
|
|
44
42
|
false
|
45
43
|
end
|
46
44
|
end if DEFAULTS[:nameserver]
|
47
|
-
# :nocov:
|
48
45
|
|
49
46
|
DNS_PORT = 53
|
50
47
|
|
@@ -53,15 +50,15 @@ module HTTPX
|
|
53
50
|
def initialize(options)
|
54
51
|
@options = Options.new(options)
|
55
52
|
@ns_index = 0
|
56
|
-
@resolver_options =
|
57
|
-
@nameserver = @resolver_options
|
58
|
-
@_timeouts = Array(@resolver_options
|
53
|
+
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
54
|
+
@nameserver = @resolver_options[:nameserver]
|
55
|
+
@_timeouts = Array(@resolver_options[:timeouts])
|
59
56
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
60
|
-
@_record_types = Hash.new { |types, host| types[host] = @resolver_options
|
57
|
+
@_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
|
61
58
|
@connections = []
|
62
59
|
@queries = {}
|
63
60
|
@read_buffer = "".b
|
64
|
-
@write_buffer = Buffer.new(@resolver_options
|
61
|
+
@write_buffer = Buffer.new(@resolver_options[:packet_size])
|
65
62
|
@state = :idle
|
66
63
|
end
|
67
64
|
|
@@ -111,9 +108,9 @@ module HTTPX
|
|
111
108
|
return if early_resolve(connection)
|
112
109
|
|
113
110
|
if @nameserver.nil?
|
114
|
-
ex = ResolveError.new("
|
111
|
+
ex = ResolveError.new("No available nameserver")
|
115
112
|
ex.set_backtrace(caller)
|
116
|
-
|
113
|
+
throw(:resolve_error, ex)
|
117
114
|
else
|
118
115
|
@connections << connection
|
119
116
|
resolve
|
@@ -150,21 +147,26 @@ module HTTPX
|
|
150
147
|
queries[h] = connection
|
151
148
|
next
|
152
149
|
end
|
150
|
+
|
153
151
|
@timeouts[host].shift
|
154
152
|
if @timeouts[host].empty?
|
155
153
|
@timeouts.delete(host)
|
156
154
|
@connections.delete(connection)
|
157
|
-
|
155
|
+
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
156
|
+
# resolve timeout, including from the previous retries.
|
157
|
+
raise ResolveTimeoutError.new(loop_time, "Timed out")
|
158
|
+
# raise NativeResolveError.new(connection, host)
|
158
159
|
else
|
160
|
+
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
159
161
|
connections << connection
|
160
|
-
|
162
|
+
queries[h] = connection
|
161
163
|
end
|
162
164
|
end
|
163
165
|
@queries = queries
|
164
166
|
connections.each { |ch| resolve(ch) }
|
165
167
|
end
|
166
168
|
|
167
|
-
def dread(wsize = @resolver_options
|
169
|
+
def dread(wsize = @resolver_options[:packet_size])
|
168
170
|
loop do
|
169
171
|
siz = @io.read(wsize, @read_buffer)
|
170
172
|
return unless siz && siz.positive?
|
@@ -199,13 +201,14 @@ module HTTPX
|
|
199
201
|
end
|
200
202
|
end
|
201
203
|
|
202
|
-
if addresses.empty?
|
204
|
+
if addresses.nil? || addresses.empty?
|
203
205
|
hostname, connection = @queries.first
|
204
206
|
@_record_types[hostname].shift
|
205
207
|
if @_record_types[hostname].empty?
|
206
208
|
@queries.delete(hostname)
|
207
209
|
@_record_types.delete(hostname)
|
208
210
|
@connections.delete(connection)
|
211
|
+
|
209
212
|
raise NativeResolveError.new(connection, hostname)
|
210
213
|
end
|
211
214
|
else
|
@@ -223,7 +226,7 @@ module HTTPX
|
|
223
226
|
end
|
224
227
|
else
|
225
228
|
@connections.delete(connection)
|
226
|
-
Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options
|
229
|
+
Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options[:cache]
|
227
230
|
emit_addresses(connection, addresses.map { |addr| addr["data"] })
|
228
231
|
end
|
229
232
|
end
|
@@ -243,7 +246,7 @@ module HTTPX
|
|
243
246
|
log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
|
244
247
|
end
|
245
248
|
@queries[hostname] = connection
|
246
|
-
type = @_record_types[hostname].first
|
249
|
+
type = @_record_types[hostname].first || "A"
|
247
250
|
log { "resolver: query #{type} for #{hostname}" }
|
248
251
|
begin
|
249
252
|
@write_buffer << Resolver.encode_dns_query(hostname, type: RECORD_TYPES[type])
|
@@ -280,7 +283,7 @@ module HTTPX
|
|
280
283
|
@io.connect
|
281
284
|
return unless @io.connected?
|
282
285
|
|
283
|
-
resolve if @queries.empty?
|
286
|
+
resolve if @queries.empty? && !@connections.empty?
|
284
287
|
when :closed
|
285
288
|
return unless @state == :open
|
286
289
|
|
@@ -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/selector.rb
CHANGED
@@ -4,19 +4,14 @@ require "io/wait"
|
|
4
4
|
|
5
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
|
@@ -122,6 +117,7 @@ class HTTPX::Selector
|
|
122
117
|
yield io
|
123
118
|
rescue IOError, SystemCallError
|
124
119
|
@selectables.reject!(&:closed?)
|
120
|
+
raise unless @selectables.empty?
|
125
121
|
end
|
126
122
|
|
127
123
|
def select(interval, &block)
|
data/lib/httpx/session.rb
CHANGED
@@ -41,7 +41,7 @@ module HTTPX
|
|
41
41
|
def build_request(verb, uri, options = EMPTY_HASH)
|
42
42
|
rklass = @options.request_class
|
43
43
|
request = rklass.new(verb, uri, @options.merge(options).merge(persistent: @persistent))
|
44
|
-
request.on(:response, &method(:on_response).curry[request])
|
44
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
45
45
|
request.on(:promise, &method(:on_promise))
|
46
46
|
request
|
47
47
|
end
|
@@ -77,10 +77,16 @@ module HTTPX
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def set_connection_callbacks(connection, connections, options)
|
80
|
-
connection.on(:
|
81
|
-
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)
|
82
87
|
connections << other_connection
|
83
|
-
|
88
|
+
misdirected_request.transition(:idle)
|
89
|
+
other_connection.send(misdirected_request)
|
84
90
|
end
|
85
91
|
connection.on(:altsvc) do |alt_origin, origin, alt_params|
|
86
92
|
other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
|
@@ -130,23 +136,20 @@ module HTTPX
|
|
130
136
|
def build_requests(*args, options)
|
131
137
|
request_options = @options.merge(options)
|
132
138
|
|
133
|
-
requests =
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
148
|
-
else
|
149
|
-
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
|
150
153
|
end
|
151
154
|
raise ArgumentError, "wrong number of URIs (given 0, expect 1..+1)" if requests.empty?
|
152
155
|
|
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,10 +12,18 @@ module HTTPX::Transcoder
|
|
12
12
|
|
13
13
|
def_delegator :@raw, :to_s
|
14
14
|
|
15
|
+
def_delegator :@raw, :to_str
|
16
|
+
|
15
17
|
def_delegator :@raw, :bytesize
|
16
18
|
|
17
19
|
def initialize(form)
|
18
|
-
@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
|
19
27
|
end
|
20
28
|
|
21
29
|
def content_type
|