httpx 0.16.1 → 0.17.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/doc/release_notes/0_17_0.md +49 -0
- data/lib/httpx/adapters/webmock.rb +2 -2
- data/lib/httpx/chainable.rb +1 -1
- data/lib/httpx/connection/http1.rb +15 -9
- data/lib/httpx/connection/http2.rb +13 -10
- data/lib/httpx/connection.rb +4 -5
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/options.rb +28 -6
- data/lib/httpx/parser/http1.rb +10 -6
- data/lib/httpx/plugins/digest_authentication.rb +4 -4
- data/lib/httpx/plugins/h2c.rb +7 -3
- 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 +14 -0
- 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/stream.rb +2 -3
- data/lib/httpx/registry.rb +1 -1
- data/lib/httpx/request.rb +6 -7
- data/lib/httpx/resolver/resolver_mixin.rb +2 -1
- data/lib/httpx/response.rb +37 -30
- data/lib/httpx/selector.rb +4 -2
- data/lib/httpx/session.rb +15 -13
- 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 +2 -2
- data/lib/httpx/version.rb +1 -1
- data/sig/buffer.rbs +2 -2
- data/sig/chainable.rbs +6 -1
- data/sig/connection/http1.rbs +10 -4
- data/sig/connection/http2.rbs +16 -5
- data/sig/connection.rbs +4 -4
- data/sig/headers.rbs +19 -18
- data/sig/options.rbs +13 -5
- data/sig/parser/http1.rbs +3 -3
- 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/request.rbs +11 -8
- data/sig/resolver/native.rbs +4 -2
- data/sig/resolver/resolver_mixin.rbs +1 -1
- data/sig/resolver/system.rbs +1 -1
- data/sig/response.rbs +8 -2
- data/sig/selector.rbs +8 -6
- data/sig/session.rbs +8 -14
- data/sig/transcoder/form.rbs +1 -0
- data/sig/transcoder/json.rbs +1 -0
- data/sig/transcoder.rbs +5 -4
- metadata +5 -2
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?
|
@@ -68,8 +70,31 @@ module HTTPX
|
|
68
70
|
raise HTTPError, self
|
69
71
|
end
|
70
72
|
|
73
|
+
def json(options = nil)
|
74
|
+
decode("json", options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def form
|
78
|
+
decode("form")
|
79
|
+
end
|
80
|
+
|
71
81
|
private
|
72
82
|
|
83
|
+
def decode(format, options = nil)
|
84
|
+
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
|
85
|
+
transcoder = Transcoder.registry(format)
|
86
|
+
|
87
|
+
raise Error, "no decoder available for \"#{format}\"" unless transcoder.respond_to?(:decode)
|
88
|
+
|
89
|
+
decoder = transcoder.decode(self)
|
90
|
+
|
91
|
+
raise Error, "no decoder available for \"#{format}\"" unless decoder
|
92
|
+
|
93
|
+
decoder.call(self, options)
|
94
|
+
rescue Registry::Error
|
95
|
+
raise Error, "no decoder available for \"#{format}\""
|
96
|
+
end
|
97
|
+
|
73
98
|
def no_data?
|
74
99
|
@status < 200 ||
|
75
100
|
@status == 204 ||
|
@@ -93,16 +118,6 @@ module HTTPX
|
|
93
118
|
@length = 0
|
94
119
|
@buffer = nil
|
95
120
|
@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
121
|
end
|
107
122
|
|
108
123
|
def closed?
|
@@ -264,30 +279,22 @@ module HTTPX
|
|
264
279
|
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
|
265
280
|
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
|
266
281
|
|
267
|
-
|
268
|
-
|
269
|
-
def initialize(mime_type, charset)
|
270
|
-
@mime_type = mime_type
|
271
|
-
@charset = charset
|
282
|
+
def initialize(header_value)
|
283
|
+
@header_value = header_value
|
272
284
|
end
|
273
285
|
|
274
|
-
|
275
|
-
|
276
|
-
def parse(str)
|
277
|
-
new(mime_type(str), charset(str))
|
278
|
-
end
|
286
|
+
def mime_type
|
287
|
+
return @mime_type if defined?(@mime_type)
|
279
288
|
|
280
|
-
|
289
|
+
m = @header_value.to_s[MIME_TYPE_RE, 1]
|
290
|
+
m && @mime_type = m.strip.downcase
|
291
|
+
end
|
281
292
|
|
282
|
-
|
283
|
-
|
284
|
-
m && m.strip.downcase
|
285
|
-
end
|
293
|
+
def charset
|
294
|
+
return @charset if defined?(@charset)
|
286
295
|
|
287
|
-
|
288
|
-
|
289
|
-
m && m.strip.delete('"')
|
290
|
-
end
|
296
|
+
m = @header_value.to_s[CHARSET_RE, 1]
|
297
|
+
m && @charset = m.strip.delete('"')
|
291
298
|
end
|
292
299
|
end
|
293
300
|
|
data/lib/httpx/selector.rb
CHANGED
@@ -86,7 +86,7 @@ class HTTPX::Selector
|
|
86
86
|
|
87
87
|
readers, writers = IO.select(r, w, nil, interval)
|
88
88
|
|
89
|
-
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil?
|
89
|
+
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil? && interval
|
90
90
|
rescue IOError, SystemCallError
|
91
91
|
@selectables.reject!(&:closed?)
|
92
92
|
retry
|
@@ -109,6 +109,8 @@ class HTTPX::Selector
|
|
109
109
|
def select_one(interval)
|
110
110
|
io = @selectables.first
|
111
111
|
|
112
|
+
return unless io
|
113
|
+
|
112
114
|
interests = io.interests
|
113
115
|
|
114
116
|
result = case interests
|
@@ -118,7 +120,7 @@ class HTTPX::Selector
|
|
118
120
|
when nil then return
|
119
121
|
end
|
120
122
|
|
121
|
-
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result
|
123
|
+
raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result || interval.nil?
|
122
124
|
|
123
125
|
yield io
|
124
126
|
rescue IOError, SystemCallError
|
data/lib/httpx/session.rb
CHANGED
@@ -32,7 +32,7 @@ module HTTPX
|
|
32
32
|
raise ArgumentError, "must perform at least one request" if args.empty?
|
33
33
|
|
34
34
|
requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
|
35
|
-
responses = send_requests(*requests
|
35
|
+
responses = send_requests(*requests)
|
36
36
|
return responses.first if responses.size == 1
|
37
37
|
|
38
38
|
responses
|
@@ -40,7 +40,8 @@ module HTTPX
|
|
40
40
|
|
41
41
|
def build_request(verb, uri, options = EMPTY_HASH)
|
42
42
|
rklass = @options.request_class
|
43
|
-
|
43
|
+
options = @options.merge(options) unless options.is_a?(Options)
|
44
|
+
request = rklass.new(verb, uri, options.merge(persistent: @persistent))
|
44
45
|
request.on(:response, &method(:on_response).curry(2)[request])
|
45
46
|
request.on(:promise, &method(:on_promise))
|
46
47
|
request
|
@@ -174,37 +175,38 @@ module HTTPX
|
|
174
175
|
end
|
175
176
|
end
|
176
177
|
|
177
|
-
def send_requests(*requests
|
178
|
-
|
179
|
-
|
180
|
-
connections = _send_requests(requests, request_options)
|
181
|
-
receive_requests(requests, connections, request_options)
|
178
|
+
def send_requests(*requests)
|
179
|
+
connections = _send_requests(requests)
|
180
|
+
receive_requests(requests, connections)
|
182
181
|
end
|
183
182
|
|
184
|
-
def _send_requests(requests
|
183
|
+
def _send_requests(requests)
|
185
184
|
connections = []
|
186
185
|
|
187
186
|
requests.each do |request|
|
188
187
|
error = catch(:resolve_error) do
|
189
|
-
connection = find_connection(request, connections, options)
|
188
|
+
connection = find_connection(request, connections, request.options)
|
190
189
|
connection.send(request)
|
191
190
|
end
|
192
191
|
next unless error.is_a?(ResolveError)
|
193
192
|
|
194
|
-
request.emit(:response, ErrorResponse.new(request, error, options))
|
193
|
+
request.emit(:response, ErrorResponse.new(request, error, request.options))
|
195
194
|
end
|
196
195
|
|
197
196
|
connections
|
198
197
|
end
|
199
198
|
|
200
|
-
def receive_requests(requests, connections
|
199
|
+
def receive_requests(requests, connections)
|
201
200
|
responses = []
|
202
201
|
|
203
202
|
begin
|
204
203
|
# guarantee ordered responses
|
205
204
|
loop do
|
206
205
|
request = requests.first
|
207
|
-
|
206
|
+
|
207
|
+
return responses unless request
|
208
|
+
|
209
|
+
pool.next_tick until (response = fetch_response(request, connections, request.options))
|
208
210
|
|
209
211
|
responses << response
|
210
212
|
requests.shift
|
@@ -218,7 +220,7 @@ module HTTPX
|
|
218
220
|
# opportunity to traverse the requests, hence we're returning only a fraction of the errors
|
219
221
|
# we were supposed to. This effectively fetches the existing responses and return them.
|
220
222
|
while (request = requests.shift)
|
221
|
-
responses << fetch_response(request, connections, options)
|
223
|
+
responses << fetch_response(request, connections, request.options)
|
222
224
|
end
|
223
225
|
break
|
224
226
|
end
|
@@ -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 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 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
@@ -28,9 +28,9 @@ module HTTPX
|
|
28
28
|
URIParser = URI::RFC2396_Parser.new
|
29
29
|
|
30
30
|
def to_uri(uri)
|
31
|
-
return
|
31
|
+
return URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
|
32
32
|
|
33
|
-
uri =
|
33
|
+
uri = URI(URIParser.escape(uri))
|
34
34
|
|
35
35
|
non_ascii_hostname = URIParser.unescape(uri.host)
|
36
36
|
|
data/lib/httpx/version.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
|
data/sig/chainable.rbs
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
module HTTPX
|
2
2
|
module Chainable
|
3
|
-
def request: (*
|
3
|
+
def request: (*Request, **untyped) -> Array[response]
|
4
|
+
| (Request, **untyped) -> response
|
5
|
+
| (verb | string, uri | [uri], **untyped) -> response
|
6
|
+
| (Array[[verb | string, uri] | [verb | string, uri, options]], **untyped) -> Array[response]
|
7
|
+
| (verb | string, _Each[uri | [uri, options]], **untyped) -> Array[response]
|
8
|
+
|
4
9
|
def accept: (String) -> Session
|
5
10
|
def wrap: () { (Session) -> void } -> void
|
6
11
|
|
data/sig/connection/http1.rbs
CHANGED
@@ -3,6 +3,10 @@ module HTTPX
|
|
3
3
|
include Callbacks
|
4
4
|
include Loggable
|
5
5
|
|
6
|
+
UPCASED: Hash[String, String]
|
7
|
+
MAX_REQUESTS: Integer
|
8
|
+
CRLF: String
|
9
|
+
|
6
10
|
attr_reader pending: Array[Request]
|
7
11
|
attr_reader requests: Array[Request]
|
8
12
|
|
@@ -30,11 +34,13 @@ module HTTPX
|
|
30
34
|
|
31
35
|
def handle_error: (StandardError ex) -> void
|
32
36
|
|
37
|
+
def on_start: () -> void
|
38
|
+
|
33
39
|
def on_headers: (Hash[String, Array[String]] headers) -> void
|
34
40
|
|
35
41
|
def on_trailers: (Hash[String, Array[String]] headers) -> void
|
36
42
|
|
37
|
-
def on_data: (
|
43
|
+
def on_data: (String chunk) -> void
|
38
44
|
|
39
45
|
def on_complete: () -> void
|
40
46
|
|
@@ -42,7 +48,7 @@ module HTTPX
|
|
42
48
|
|
43
49
|
def ping: () -> void
|
44
50
|
|
45
|
-
def timeout: () ->
|
51
|
+
def timeout: () -> Numeric
|
46
52
|
|
47
53
|
private
|
48
54
|
|
@@ -54,7 +60,7 @@ module HTTPX
|
|
54
60
|
|
55
61
|
def disable_pipelining: () -> void
|
56
62
|
|
57
|
-
def set_protocol_headers: (Request) -> _Each[[
|
63
|
+
def set_protocol_headers: (Request) -> _Each[[String, String]]
|
58
64
|
|
59
65
|
def headline_uri: (Request) -> String
|
60
66
|
|
@@ -64,7 +70,7 @@ module HTTPX
|
|
64
70
|
|
65
71
|
def join_trailers: (Request request) -> void
|
66
72
|
|
67
|
-
def join_headers2: (_Each[[
|
73
|
+
def join_headers2: (_Each[[String, String]] headers) -> void
|
68
74
|
|
69
75
|
def join_body: (Request request) -> void
|
70
76
|
|