httpx 0.10.2 → 0.12.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/README.md +13 -5
- data/doc/release_notes/0_11_0.md +76 -0
- data/doc/release_notes/0_11_1.md +5 -0
- data/doc/release_notes/0_11_2.md +5 -0
- data/doc/release_notes/0_11_3.md +5 -0
- data/doc/release_notes/0_12_0.md +55 -0
- data/lib/httpx.rb +2 -1
- data/lib/httpx/adapters/datadog.rb +205 -0
- data/lib/httpx/adapters/faraday.rb +4 -8
- data/lib/httpx/adapters/webmock.rb +123 -0
- data/lib/httpx/altsvc.rb +1 -0
- data/lib/httpx/chainable.rb +1 -1
- data/lib/httpx/connection.rb +63 -15
- data/lib/httpx/connection/http1.rb +16 -5
- data/lib/httpx/connection/http2.rb +36 -29
- data/lib/httpx/domain_name.rb +1 -3
- data/lib/httpx/errors.rb +2 -0
- data/lib/httpx/headers.rb +1 -0
- data/lib/httpx/io.rb +16 -3
- data/lib/httpx/io/ssl.rb +7 -13
- data/lib/httpx/io/tcp.rb +9 -8
- data/lib/httpx/io/tls.rb +218 -0
- data/lib/httpx/io/tls/box.rb +365 -0
- data/lib/httpx/io/tls/context.rb +199 -0
- data/lib/httpx/io/tls/ffi.rb +390 -0
- data/lib/httpx/io/udp.rb +4 -3
- data/lib/httpx/parser/http1.rb +4 -4
- data/lib/httpx/plugins/aws_sdk_authentication.rb +81 -0
- data/lib/httpx/plugins/aws_sigv4.rb +218 -0
- data/lib/httpx/plugins/compression.rb +1 -1
- data/lib/httpx/plugins/compression/deflate.rb +2 -5
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
- data/lib/httpx/plugins/expect.rb +33 -8
- data/lib/httpx/plugins/internal_telemetry.rb +93 -0
- data/lib/httpx/plugins/multipart.rb +42 -35
- data/lib/httpx/plugins/multipart/encoder.rb +110 -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 +1 -1
- data/lib/httpx/plugins/proxy/http.rb +1 -1
- data/lib/httpx/plugins/proxy/socks4.rb +8 -0
- data/lib/httpx/plugins/proxy/socks5.rb +11 -2
- data/lib/httpx/plugins/push_promise.rb +5 -4
- data/lib/httpx/plugins/retries.rb +1 -1
- data/lib/httpx/plugins/stream.rb +3 -5
- data/lib/httpx/pool.rb +0 -1
- data/lib/httpx/registry.rb +1 -7
- data/lib/httpx/request.rb +32 -12
- data/lib/httpx/resolver.rb +7 -4
- data/lib/httpx/resolver/https.rb +7 -13
- data/lib/httpx/resolver/native.rb +10 -6
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/response.rb +9 -2
- data/lib/httpx/selector.rb +6 -0
- data/lib/httpx/session.rb +40 -20
- data/lib/httpx/transcoder.rb +6 -4
- data/lib/httpx/transcoder/body.rb +3 -5
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http1.rbs +2 -2
- data/sig/connection/http2.rbs +8 -7
- data/sig/headers.rbs +3 -0
- data/sig/plugins/aws_sdk_authentication.rbs +17 -0
- data/sig/plugins/aws_sigv4.rbs +65 -0
- data/sig/plugins/multipart.rbs +27 -4
- data/sig/plugins/push_promise.rbs +1 -1
- data/sig/request.rbs +1 -1
- data/sig/resolver/https.rbs +2 -0
- data/sig/response.rbs +1 -1
- data/sig/session.rbs +1 -1
- data/sig/transcoder.rbs +2 -2
- data/sig/transcoder/body.rbs +2 -0
- data/sig/transcoder/form.rbs +7 -1
- data/sig/transcoder/json.rbs +3 -1
- metadata +50 -47
- data/sig/missing.rbs +0 -12
@@ -43,7 +43,7 @@ module HTTPX
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def __on_promise_request(parser, stream, h)
|
46
|
-
log(level: 1) do
|
46
|
+
log(level: 1, color: :yellow) do
|
47
47
|
# :nocov:
|
48
48
|
h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
|
49
49
|
# :nocov:
|
@@ -57,6 +57,8 @@ module HTTPX
|
|
57
57
|
request.merge_headers(headers)
|
58
58
|
promise_headers[stream] = request
|
59
59
|
parser.pending.delete(request)
|
60
|
+
parser.streams[request] = stream
|
61
|
+
request.transition(:done)
|
60
62
|
else
|
61
63
|
stream.refuse
|
62
64
|
end
|
@@ -67,11 +69,10 @@ module HTTPX
|
|
67
69
|
return unless request
|
68
70
|
|
69
71
|
parser.__send__(:on_stream_headers, stream, request, h)
|
70
|
-
request.transition(:done)
|
71
72
|
response = request.response
|
72
73
|
response.mark_as_pushed!
|
73
|
-
stream.on(:data, &parser.method(:on_stream_data).curry[stream, request])
|
74
|
-
stream.on(:close, &parser.method(:on_stream_close).curry[stream, request])
|
74
|
+
stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
|
75
|
+
stream.on(:close, &parser.method(:on_stream_close).curry(3)[stream, request])
|
75
76
|
end
|
76
77
|
end
|
77
78
|
end
|
data/lib/httpx/plugins/stream.rb
CHANGED
@@ -119,11 +119,9 @@ module HTTPX
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def method_missing(meth, *args, &block)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
super
|
126
|
-
end
|
122
|
+
return super unless @options.response_class.public_method_defined?(meth)
|
123
|
+
|
124
|
+
response.__send__(meth, *args, &block)
|
127
125
|
end
|
128
126
|
end
|
129
127
|
end
|
data/lib/httpx/pool.rb
CHANGED
data/lib/httpx/registry.rb
CHANGED
@@ -62,13 +62,7 @@ module HTTPX
|
|
62
62
|
handler = @registry.fetch(tag)
|
63
63
|
raise(Error, "#{tag} is not registered in #{self}") unless handler
|
64
64
|
|
65
|
-
|
66
|
-
when Symbol, String
|
67
|
-
obj = const_get(handler)
|
68
|
-
@registry[tag] = obj
|
69
|
-
else
|
70
|
-
handler
|
71
|
-
end
|
65
|
+
handler
|
72
66
|
end
|
73
67
|
|
74
68
|
# @param [Object] tag the identifier for the handler in the registry
|
data/lib/httpx/request.rb
CHANGED
@@ -81,6 +81,10 @@ module HTTPX
|
|
81
81
|
def response=(response)
|
82
82
|
return unless response
|
83
83
|
|
84
|
+
if response.status == 100
|
85
|
+
@informational_status = response.status
|
86
|
+
return
|
87
|
+
end
|
84
88
|
@response = response
|
85
89
|
end
|
86
90
|
|
@@ -170,6 +174,12 @@ module HTTPX
|
|
170
174
|
end
|
171
175
|
end
|
172
176
|
|
177
|
+
def rewind
|
178
|
+
return if empty?
|
179
|
+
|
180
|
+
@body.rewind if @body.respond_to?(:rewind)
|
181
|
+
end
|
182
|
+
|
173
183
|
def empty?
|
174
184
|
return true if @body.nil?
|
175
185
|
return false if chunked?
|
@@ -207,11 +217,22 @@ module HTTPX
|
|
207
217
|
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
208
218
|
end
|
209
219
|
# :nocov:
|
220
|
+
|
221
|
+
def respond_to_missing?(meth, *args)
|
222
|
+
@body.respond_to?(meth, *args) || super
|
223
|
+
end
|
224
|
+
|
225
|
+
def method_missing(meth, *args, &block)
|
226
|
+
return super unless @body.respond_to?(meth)
|
227
|
+
|
228
|
+
@body.__send__(meth, *args, &block)
|
229
|
+
end
|
210
230
|
end
|
211
231
|
|
212
232
|
def transition(nextstate)
|
213
233
|
case nextstate
|
214
234
|
when :idle
|
235
|
+
@body.rewind
|
215
236
|
@response = nil
|
216
237
|
@drainer = nil
|
217
238
|
when :headers
|
@@ -221,28 +242,27 @@ module HTTPX
|
|
221
242
|
@state == :expect
|
222
243
|
|
223
244
|
if @headers.key?("expect")
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
245
|
+
if @informational_status && @informational_status == 100
|
246
|
+
# check for 100 Continue response, and deallocate the var
|
247
|
+
# if @informational_status == 100
|
248
|
+
# @response = nil
|
249
|
+
# end
|
250
|
+
else
|
251
|
+
return if @state == :expect # do not re-set it
|
252
|
+
|
253
|
+
nextstate = :expect
|
233
254
|
end
|
234
255
|
end
|
235
256
|
when :done
|
236
257
|
return if @state == :expect
|
237
258
|
end
|
238
259
|
@state = nextstate
|
239
|
-
emit(@state)
|
260
|
+
emit(@state, self)
|
240
261
|
nil
|
241
262
|
end
|
242
263
|
|
243
264
|
def expects?
|
244
|
-
@headers["expect"] == "100-continue" &&
|
245
|
-
@response && @response.status == 100
|
265
|
+
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
246
266
|
end
|
247
267
|
|
248
268
|
class ProcIO
|
data/lib/httpx/resolver.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "resolv"
|
4
|
-
require "httpx/resolver/resolver_mixin"
|
5
|
-
require "httpx/resolver/system"
|
6
|
-
require "httpx/resolver/native"
|
7
|
-
require "httpx/resolver/https"
|
8
4
|
|
9
5
|
module HTTPX
|
10
6
|
module Resolver
|
11
7
|
extend Registry
|
12
8
|
|
9
|
+
RESOLVE_TIMEOUT = 5
|
10
|
+
|
11
|
+
require "httpx/resolver/resolver_mixin"
|
12
|
+
require "httpx/resolver/system"
|
13
|
+
require "httpx/resolver/native"
|
14
|
+
require "httpx/resolver/https"
|
15
|
+
|
13
16
|
register :system, System
|
14
17
|
register :native, Native
|
15
18
|
register :https, HTTPS
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -37,12 +37,14 @@ module HTTPX
|
|
37
37
|
@connections = []
|
38
38
|
@uri = URI(@resolver_options[:uri])
|
39
39
|
@uri_addresses = nil
|
40
|
+
@resolver = Resolv::DNS.new
|
41
|
+
@resolver.timeouts = @resolver_options.fetch(:timeouts, Resolver::RESOLVE_TIMEOUT)
|
40
42
|
end
|
41
43
|
|
42
44
|
def <<(connection)
|
43
45
|
return if @uri.origin == connection.origin.to_s
|
44
46
|
|
45
|
-
@uri_addresses ||=
|
47
|
+
@uri_addresses ||= ip_resolve(@uri.host) || system_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
|
46
48
|
|
47
49
|
if @uri_addresses.empty?
|
48
50
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
@@ -53,20 +55,12 @@ module HTTPX
|
|
53
55
|
early_resolve(connection) || resolve(connection)
|
54
56
|
end
|
55
57
|
|
56
|
-
def timeout
|
57
|
-
@connections.map(&:timeout).min
|
58
|
-
end
|
59
|
-
|
60
58
|
def closed?
|
61
|
-
|
62
|
-
|
63
|
-
resolver_connection.closed?
|
59
|
+
true
|
64
60
|
end
|
65
61
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
resolver_connection.__send__(__method__)
|
62
|
+
def empty?
|
63
|
+
true
|
70
64
|
end
|
71
65
|
|
72
66
|
private
|
@@ -99,7 +93,7 @@ module HTTPX
|
|
99
93
|
log { "resolver: query #{type} for #{hostname}" }
|
100
94
|
begin
|
101
95
|
request = build_request(hostname, type)
|
102
|
-
request.on(:response, &method(:on_response).curry[request])
|
96
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
103
97
|
request.on(:promise, &method(:on_promise))
|
104
98
|
@requests[request] = connection
|
105
99
|
resolver_connection.send(request)
|
@@ -9,7 +9,6 @@ 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,
|
@@ -19,7 +18,7 @@ module HTTPX
|
|
19
18
|
{
|
20
19
|
**Resolv::DNS::Config.default_config_hash,
|
21
20
|
packet_size: 512,
|
22
|
-
timeouts: RESOLVE_TIMEOUT,
|
21
|
+
timeouts: Resolver::RESOLVE_TIMEOUT,
|
23
22
|
record_types: RECORD_TYPES.keys,
|
24
23
|
}.freeze
|
25
24
|
else
|
@@ -27,7 +26,7 @@ module HTTPX
|
|
27
26
|
nameserver: nil,
|
28
27
|
**Resolv::DNS::Config.default_config_hash,
|
29
28
|
packet_size: 512,
|
30
|
-
timeouts: RESOLVE_TIMEOUT,
|
29
|
+
timeouts: Resolver::RESOLVE_TIMEOUT,
|
31
30
|
record_types: RECORD_TYPES.keys,
|
32
31
|
}.freeze
|
33
32
|
end
|
@@ -148,14 +147,19 @@ module HTTPX
|
|
148
147
|
queries[h] = connection
|
149
148
|
next
|
150
149
|
end
|
150
|
+
|
151
151
|
@timeouts[host].shift
|
152
152
|
if @timeouts[host].empty?
|
153
153
|
@timeouts.delete(host)
|
154
154
|
@connections.delete(connection)
|
155
|
-
|
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)
|
156
159
|
else
|
160
|
+
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
157
161
|
connections << connection
|
158
|
-
|
162
|
+
queries[h] = connection
|
159
163
|
end
|
160
164
|
end
|
161
165
|
@queries = queries
|
@@ -279,7 +283,7 @@ module HTTPX
|
|
279
283
|
@io.connect
|
280
284
|
return unless @io.connected?
|
281
285
|
|
282
|
-
resolve if @queries.empty?
|
286
|
+
resolve if @queries.empty? && !@connections.empty?
|
283
287
|
when :closed
|
284
288
|
return unless @state == :open
|
285
289
|
|
@@ -20,7 +20,7 @@ module HTTPX
|
|
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
@@ -268,8 +268,15 @@ module HTTPX
|
|
268
268
|
@error.message
|
269
269
|
end
|
270
270
|
|
271
|
-
|
272
|
-
|
271
|
+
if Exception.method_defined?(:full_message)
|
272
|
+
def to_s
|
273
|
+
@error.full_message
|
274
|
+
end
|
275
|
+
else
|
276
|
+
def to_s
|
277
|
+
"#{@error.message} (#{@error.class})\n" \
|
278
|
+
"#{@error.backtrace.join("\n") if @error.backtrace}"
|
279
|
+
end
|
273
280
|
end
|
274
281
|
|
275
282
|
def raise_for_status
|
data/lib/httpx/selector.rb
CHANGED
@@ -69,6 +69,11 @@ class HTTPX::Selector
|
|
69
69
|
|
70
70
|
if @selectables.empty?
|
71
71
|
@selectables = selectables
|
72
|
+
|
73
|
+
# do not run event loop if there's nothing to wait on.
|
74
|
+
# this might happen if connect failed and connection was unregistered.
|
75
|
+
return if (!r || r.empty?) && (!w || w.empty?)
|
76
|
+
|
72
77
|
break
|
73
78
|
else
|
74
79
|
@selectables = [*selectables, @selectables]
|
@@ -117,6 +122,7 @@ class HTTPX::Selector
|
|
117
122
|
yield io
|
118
123
|
rescue IOError, SystemCallError
|
119
124
|
@selectables.reject!(&:closed?)
|
125
|
+
raise unless @selectables.empty?
|
120
126
|
end
|
121
127
|
|
122
128
|
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
|
@@ -136,23 +136,20 @@ module HTTPX
|
|
136
136
|
def build_requests(*args, options)
|
137
137
|
request_options = @options.merge(options)
|
138
138
|
|
139
|
-
requests =
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
end
|
154
|
-
else
|
155
|
-
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
|
156
153
|
end
|
157
154
|
raise ArgumentError, "wrong number of URIs (given 0, expect 1..+1)" if requests.empty?
|
158
155
|
|
@@ -202,7 +199,18 @@ module HTTPX
|
|
202
199
|
responses << response
|
203
200
|
requests.shift
|
204
201
|
|
205
|
-
break if requests.empty?
|
202
|
+
break if requests.empty?
|
203
|
+
|
204
|
+
next unless pool.empty?
|
205
|
+
|
206
|
+
# in some cases, the pool of connections might have been drained because there was some
|
207
|
+
# handshake error, and the error responses have already been emitted, but there was no
|
208
|
+
# opportunity to traverse the requests, hence we're returning only a fraction of the errors
|
209
|
+
# we were supposed to. This effectively fetches the existing responses and return them.
|
210
|
+
while (request = requests.shift)
|
211
|
+
responses << fetch_response(request, connections, request_options)
|
212
|
+
end
|
213
|
+
break
|
206
214
|
end
|
207
215
|
responses
|
208
216
|
ensure
|
@@ -272,7 +280,19 @@ module HTTPX
|
|
272
280
|
end
|
273
281
|
# :nocov:
|
274
282
|
end
|
283
|
+
end
|
284
|
+
|
285
|
+
unless ENV.grep(/https?_proxy$/i).empty?
|
286
|
+
proxy_session = plugin(:proxy)
|
287
|
+
::HTTPX.send(:remove_const, :Session)
|
288
|
+
::HTTPX.send(:const_set, :Session, proxy_session.class)
|
289
|
+
end
|
275
290
|
|
276
|
-
|
291
|
+
# :nocov:
|
292
|
+
if Session.default_options.debug_level > 2
|
293
|
+
proxy_session = plugin(:internal_telemetry)
|
294
|
+
::HTTPX.send(:remove_const, :Session)
|
295
|
+
::HTTPX.send(:const_set, :Session, proxy_session.class)
|
277
296
|
end
|
297
|
+
# :nocov:
|
278
298
|
end
|
data/lib/httpx/transcoder.rb
CHANGED
@@ -4,18 +4,20 @@ module HTTPX
|
|
4
4
|
module Transcoder
|
5
5
|
extend Registry
|
6
6
|
|
7
|
-
def self.normalize_keys(key, value, &block)
|
8
|
-
if
|
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)
|
9
11
|
if value.empty?
|
10
12
|
block.call("#{key}[]")
|
11
13
|
else
|
12
14
|
value.to_ary.each do |element|
|
13
|
-
normalize_keys("#{key}[]", element, &block)
|
15
|
+
normalize_keys("#{key}[]", element, cond, &block)
|
14
16
|
end
|
15
17
|
end
|
16
18
|
elsif value.respond_to?(:to_hash)
|
17
19
|
value.to_hash.each do |child_key, child_value|
|
18
|
-
normalize_keys("#{key}[#{child_key}]", child_value, &block)
|
20
|
+
normalize_keys("#{key}[#{child_key}]", child_value, cond, &block)
|
19
21
|
end
|
20
22
|
else
|
21
23
|
block.call(key.to_s, value)
|