httpx 1.3.4 → 1.4.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/1_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +18 -6
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +12 -8
- data/lib/httpx/connection.rb +192 -22
- data/lib/httpx/errors.rb +12 -0
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +26 -16
- data/lib/httpx/plugins/aws_sigv4.rb +31 -16
- data/lib/httpx/plugins/callbacks.rb +12 -2
- data/lib/httpx/plugins/circuit_breaker.rb +0 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/expect.rb +4 -3
- data/lib/httpx/plugins/follow_redirects.rb +7 -8
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +27 -0
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +17 -19
- data/lib/httpx/plugins/proxy.rb +91 -93
- data/lib/httpx/plugins/retries.rb +5 -8
- data/lib/httpx/plugins/upgrade.rb +5 -10
- data/lib/httpx/plugins/webdav.rb +6 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pool.rb +73 -244
- data/lib/httpx/request/body.rb +25 -26
- data/lib/httpx/request.rb +7 -1
- data/lib/httpx/resolver/https.rb +15 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +66 -25
- data/lib/httpx/resolver/resolver.rb +59 -15
- data/lib/httpx/resolver/system.rb +31 -15
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +160 -95
- data/lib/httpx/session.rb +273 -140
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder/utils/inflater.rb +2 -0
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +20 -21
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +29 -3
- data/sig/errors.rbs +6 -0
- data/sig/loggable.rbs +2 -0
- data/sig/options.rbs +7 -0
- data/sig/plugins/aws_sigv4.rbs +8 -2
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +9 -0
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -11
- data/sig/plugins/push_promise.rbs +3 -0
- data/sig/plugins/rate_limiter.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +25 -33
- data/sig/request/body.rbs +5 -9
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +2 -2
- data/sig/resolver/resolver.rbs +21 -2
- data/sig/resolver.rbs +5 -1
- data/sig/response/buffer.rbs +1 -1
- data/sig/selector.rbs +30 -4
- data/sig/session.rbs +47 -18
- data/sig/transcoder/body.rbs +2 -4
- data/sig/transcoder/chunker.rbs +1 -1
- data/sig/transcoder/deflate.rbs +1 -0
- data/sig/transcoder/form.rbs +8 -0
- data/sig/transcoder/gzip.rbs +4 -1
- data/sig/transcoder/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +3 -3
- metadata +12 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -31,31 +31,53 @@ module HTTPX
|
|
31
31
|
end
|
32
32
|
|
33
33
|
class Parameters
|
34
|
-
attr_reader :uri, :username, :password, :scheme
|
34
|
+
attr_reader :uri, :username, :password, :scheme, :no_proxy
|
35
35
|
|
36
|
-
def initialize(uri
|
37
|
-
@
|
38
|
-
@
|
39
|
-
|
36
|
+
def initialize(uri: nil, scheme: nil, username: nil, password: nil, no_proxy: nil, **extra)
|
37
|
+
@no_proxy = Array(no_proxy) if no_proxy
|
38
|
+
@uris = Array(uri)
|
39
|
+
uri = @uris.first
|
40
40
|
|
41
|
-
|
41
|
+
@username = username
|
42
|
+
@password = password
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
return
|
44
|
+
@ns = 0
|
45
|
+
|
46
|
+
if uri
|
47
|
+
@uri = uri.is_a?(URI::Generic) ? uri : URI(uri)
|
48
|
+
@username ||= @uri.user
|
49
|
+
@password ||= @uri.password
|
50
50
|
end
|
51
51
|
|
52
52
|
@scheme = scheme
|
53
53
|
|
54
|
-
|
54
|
+
return unless @uri && @username && @password
|
55
55
|
|
56
|
-
|
56
|
+
@authenticator = nil
|
57
|
+
@scheme ||= infer_default_auth_scheme(@uri)
|
58
|
+
|
59
|
+
return unless @scheme
|
57
60
|
|
58
|
-
@authenticator =
|
61
|
+
@authenticator = load_authenticator(@scheme, @username, @password, **extra)
|
62
|
+
end
|
63
|
+
|
64
|
+
def shift
|
65
|
+
# TODO: this operation must be synchronized
|
66
|
+
@ns += 1
|
67
|
+
@uri = @uris[@ns]
|
68
|
+
|
69
|
+
return unless @uri
|
70
|
+
|
71
|
+
@uri = URI(@uri) unless @uri.is_a?(URI::Generic)
|
72
|
+
|
73
|
+
scheme = infer_default_auth_scheme(@uri)
|
74
|
+
|
75
|
+
return unless scheme != @scheme
|
76
|
+
|
77
|
+
@scheme = scheme
|
78
|
+
@username = username || @uri.user
|
79
|
+
@password = password || @uri.password
|
80
|
+
@authenticator = load_authenticator(scheme, @username, @password)
|
59
81
|
end
|
60
82
|
|
61
83
|
def can_authenticate?(*args)
|
@@ -87,6 +109,25 @@ module HTTPX
|
|
87
109
|
super
|
88
110
|
end
|
89
111
|
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def infer_default_auth_scheme(uri)
|
116
|
+
case uri.scheme
|
117
|
+
when "socks5"
|
118
|
+
uri.scheme
|
119
|
+
when "http", "https"
|
120
|
+
"basic"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def load_authenticator(scheme, username, password, **extra)
|
125
|
+
auth_scheme = scheme.to_s.capitalize
|
126
|
+
|
127
|
+
require_relative "auth/#{scheme}" unless defined?(Authentication) && Authentication.const_defined?(auth_scheme, false)
|
128
|
+
|
129
|
+
Authentication.const_get(auth_scheme).new(username, password, **extra)
|
130
|
+
end
|
90
131
|
end
|
91
132
|
|
92
133
|
# adds support for the following options:
|
@@ -95,7 +136,7 @@ module HTTPX
|
|
95
136
|
# *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
|
96
137
|
module OptionsMethods
|
97
138
|
def option_proxy(value)
|
98
|
-
value.is_a?(Parameters) ? value : Hash[value]
|
139
|
+
value.is_a?(Parameters) ? value : Parameters.new(**Hash[value])
|
99
140
|
end
|
100
141
|
|
101
142
|
def option_supported_proxy_protocols(value)
|
@@ -106,97 +147,68 @@ module HTTPX
|
|
106
147
|
end
|
107
148
|
|
108
149
|
module InstanceMethods
|
109
|
-
|
110
|
-
|
111
|
-
def find_connection(request, connections, options)
|
150
|
+
def find_connection(request_uri, selector, options)
|
112
151
|
return super unless options.respond_to?(:proxy)
|
113
152
|
|
114
|
-
|
115
|
-
|
116
|
-
proxy_options = proxy_options(uri, options)
|
117
|
-
|
118
|
-
return super(request, connections, proxy_options) unless proxy_options.proxy
|
119
|
-
|
120
|
-
connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
|
121
|
-
unless connections.nil? || connections.include?(connection)
|
122
|
-
connections << connection
|
123
|
-
set_connection_callbacks(connection, connections, options)
|
153
|
+
if (next_proxy = request_uri.find_proxy)
|
154
|
+
return super(request_uri, selector, options.merge(proxy: Parameters.new(uri: next_proxy)))
|
124
155
|
end
|
125
|
-
connection
|
126
|
-
end
|
127
|
-
|
128
|
-
def proxy_options(request_uri, options)
|
129
|
-
proxy_opts = if (next_proxy = request_uri.find_proxy)
|
130
|
-
{ uri: next_proxy }
|
131
|
-
else
|
132
|
-
proxy = options.proxy
|
133
|
-
|
134
|
-
return options unless proxy
|
135
|
-
|
136
|
-
return options.merge(proxy: nil) unless proxy.key?(:uri)
|
137
156
|
|
138
|
-
|
157
|
+
proxy = options.proxy
|
139
158
|
|
140
|
-
|
141
|
-
raise Error, "Failed to connect to proxy" unless next_proxy
|
159
|
+
return super unless proxy
|
142
160
|
|
143
|
-
|
161
|
+
next_proxy = proxy.uri
|
144
162
|
|
145
|
-
|
146
|
-
"#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
|
163
|
+
raise Error, "Failed to connect to proxy" unless next_proxy
|
147
164
|
|
148
|
-
|
165
|
+
raise Error,
|
166
|
+
"#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
|
149
167
|
|
150
|
-
|
151
|
-
|
168
|
+
if (no_proxy = proxy.no_proxy)
|
169
|
+
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
152
170
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
proxy.merge(uri: next_proxy)
|
171
|
+
# TODO: setting proxy to nil leaks the connection object in the pool
|
172
|
+
return super(request_uri, selector, options.merge(proxy: nil)) unless URI::Generic.use_proxy?(request_uri.host, next_proxy.host,
|
173
|
+
next_proxy.port, no_proxy)
|
158
174
|
end
|
159
175
|
|
160
|
-
|
161
|
-
|
162
|
-
options.merge(proxy: proxy)
|
176
|
+
super(request_uri, selector, options.merge(proxy: proxy))
|
163
177
|
end
|
164
178
|
|
165
|
-
|
166
|
-
response = super
|
179
|
+
private
|
167
180
|
|
168
|
-
|
169
|
-
|
181
|
+
def fetch_response(request, selector, options)
|
182
|
+
response = super
|
170
183
|
|
171
|
-
|
184
|
+
if response.is_a?(ErrorResponse) && proxy_error?(request, response, options)
|
185
|
+
options.proxy.shift
|
172
186
|
|
173
187
|
# return last error response if no more proxies to try
|
174
|
-
return response if
|
188
|
+
return response if options.proxy.uri.nil?
|
175
189
|
|
176
190
|
log { "failed connecting to proxy, trying next..." }
|
177
191
|
request.transition(:idle)
|
178
|
-
send_request(request,
|
192
|
+
send_request(request, selector, options)
|
179
193
|
return
|
180
194
|
end
|
181
195
|
response
|
182
196
|
end
|
183
197
|
|
184
|
-
def proxy_error?(_request, response)
|
198
|
+
def proxy_error?(_request, response, options)
|
199
|
+
return false unless options.proxy
|
200
|
+
|
185
201
|
error = response.error
|
186
202
|
case error
|
187
203
|
when NativeResolveError
|
188
|
-
|
204
|
+
proxy_uri = URI(options.proxy.uri)
|
189
205
|
|
190
|
-
|
191
|
-
|
192
|
-
origin = error.connection.origin
|
206
|
+
peer = error.connection.peer
|
193
207
|
|
194
208
|
# failed resolving proxy domain
|
195
|
-
|
209
|
+
peer.host == proxy_uri.host && peer.port == proxy_uri.port
|
196
210
|
when ResolveError
|
197
|
-
|
198
|
-
|
199
|
-
proxy_uri = URI(@_proxy_uris.first)
|
211
|
+
proxy_uri = URI(options.proxy.uri)
|
200
212
|
|
201
213
|
error.message.end_with?(proxy_uri.to_s)
|
202
214
|
when *PROXY_ERRORS
|
@@ -217,25 +229,11 @@ module HTTPX
|
|
217
229
|
|
218
230
|
# redefining the connection origin as the proxy's URI,
|
219
231
|
# as this will be used as the tcp peer ip.
|
220
|
-
proxy_uri = URI(@options.proxy.uri)
|
221
|
-
@origin.host = proxy_uri.host
|
222
|
-
@origin.port = proxy_uri.port
|
232
|
+
@proxy_uri = URI(@options.proxy.uri)
|
223
233
|
end
|
224
234
|
|
225
|
-
def
|
226
|
-
|
227
|
-
|
228
|
-
if @io.protocol == "h2" &&
|
229
|
-
@origin.scheme == "https" &&
|
230
|
-
connection.origin.scheme == "https" &&
|
231
|
-
@io.can_verify_peer?
|
232
|
-
# in proxied connections, .origin is the proxy ; Given names
|
233
|
-
# are stored in .origins, this is what is used.
|
234
|
-
origin = URI(connection.origins.first)
|
235
|
-
@io.verify_hostname(origin.host)
|
236
|
-
else
|
237
|
-
@origin == connection.origin
|
238
|
-
end
|
235
|
+
def peer
|
236
|
+
@proxy_uri || super
|
239
237
|
end
|
240
238
|
|
241
239
|
def connecting?
|
@@ -261,7 +259,7 @@ module HTTPX
|
|
261
259
|
@state = :open
|
262
260
|
|
263
261
|
super
|
264
|
-
emit(:close)
|
262
|
+
# emit(:close)
|
265
263
|
end
|
266
264
|
|
267
265
|
private
|
@@ -94,7 +94,7 @@ module HTTPX
|
|
94
94
|
|
95
95
|
private
|
96
96
|
|
97
|
-
def fetch_response(request,
|
97
|
+
def fetch_response(request, selector, options)
|
98
98
|
response = super
|
99
99
|
|
100
100
|
if response &&
|
@@ -124,20 +124,17 @@ module HTTPX
|
|
124
124
|
|
125
125
|
retry_start = Utils.now
|
126
126
|
log { "retrying after #{retry_after} secs..." }
|
127
|
-
|
128
|
-
deactivate_connection(request, connections, options)
|
129
|
-
|
130
|
-
pool.after(retry_after) do
|
127
|
+
selector.after(retry_after) do
|
131
128
|
if request.response
|
132
129
|
# request has terminated abruptly meanwhile
|
133
130
|
request.emit(:response, request.response)
|
134
131
|
else
|
135
132
|
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
136
|
-
send_request(request,
|
133
|
+
send_request(request, selector, options)
|
137
134
|
end
|
138
135
|
end
|
139
136
|
else
|
140
|
-
send_request(request,
|
137
|
+
send_request(request, selector, options)
|
141
138
|
end
|
142
139
|
|
143
140
|
return
|
@@ -153,7 +150,7 @@ module HTTPX
|
|
153
150
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
|
154
151
|
end
|
155
152
|
|
156
|
-
def proxy_error?(request, response)
|
153
|
+
def proxy_error?(request, response, _)
|
157
154
|
super && !request.retries.positive?
|
158
155
|
end
|
159
156
|
|
@@ -28,7 +28,7 @@ module HTTPX
|
|
28
28
|
end
|
29
29
|
|
30
30
|
module InstanceMethods
|
31
|
-
def fetch_response(request,
|
31
|
+
def fetch_response(request, selector, options)
|
32
32
|
response = super
|
33
33
|
|
34
34
|
if response
|
@@ -45,7 +45,7 @@ module HTTPX
|
|
45
45
|
return response unless protocol_handler
|
46
46
|
|
47
47
|
log { "upgrading to #{upgrade_protocol}..." }
|
48
|
-
connection = find_connection(request,
|
48
|
+
connection = find_connection(request.uri, selector, options)
|
49
49
|
|
50
50
|
# do not upgrade already upgraded connections
|
51
51
|
return if connection.upgrade_protocol == upgrade_protocol
|
@@ -60,14 +60,6 @@ module HTTPX
|
|
60
60
|
|
61
61
|
response
|
62
62
|
end
|
63
|
-
|
64
|
-
def close(*args)
|
65
|
-
return super if args.empty?
|
66
|
-
|
67
|
-
connections, = args
|
68
|
-
|
69
|
-
pool.close(connections.reject(&:hijacked))
|
70
|
-
end
|
71
63
|
end
|
72
64
|
|
73
65
|
module ConnectionMethods
|
@@ -75,6 +67,9 @@ module HTTPX
|
|
75
67
|
|
76
68
|
def hijack_io
|
77
69
|
@hijacked = true
|
70
|
+
|
71
|
+
# connection is taken away from selector and not given back to the pool.
|
72
|
+
@current_session.deselect_connection(self, @current_selector, true)
|
78
73
|
end
|
79
74
|
end
|
80
75
|
end
|
data/lib/httpx/plugins/webdav.rb
CHANGED
@@ -8,6 +8,10 @@ module HTTPX
|
|
8
8
|
# https://gitlab.com/os85/httpx/wikis/WebDav
|
9
9
|
#
|
10
10
|
module WebDav
|
11
|
+
def self.configure(klass)
|
12
|
+
klass.plugin(:xml)
|
13
|
+
end
|
14
|
+
|
11
15
|
module InstanceMethods
|
12
16
|
def copy(src, dest)
|
13
17
|
request("COPY", src, headers: { "destination" => @options.origin.merge(dest) })
|
@@ -43,6 +47,8 @@ module HTTPX
|
|
43
47
|
ensure
|
44
48
|
unlock(path, lock_token)
|
45
49
|
end
|
50
|
+
|
51
|
+
response
|
46
52
|
end
|
47
53
|
|
48
54
|
def unlock(path, lock_token)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# This plugin supports request XML encoding/response decoding using the nokogiri gem.
|
7
|
+
#
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/XML
|
9
|
+
#
|
10
|
+
module XML
|
11
|
+
MIME_TYPES = %r{\b(application|text)/(.+\+)?xml\b}.freeze
|
12
|
+
module Transcoder
|
13
|
+
module_function
|
14
|
+
|
15
|
+
class Encoder
|
16
|
+
def initialize(xml)
|
17
|
+
@raw = xml
|
18
|
+
end
|
19
|
+
|
20
|
+
def content_type
|
21
|
+
charset = @raw.respond_to?(:encoding) && @raw.encoding ? @raw.encoding.to_s.downcase : "utf-8"
|
22
|
+
"application/xml; charset=#{charset}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def bytesize
|
26
|
+
@raw.to_s.bytesize
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@raw.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def encode(xml)
|
35
|
+
Encoder.new(xml)
|
36
|
+
end
|
37
|
+
|
38
|
+
def decode(response)
|
39
|
+
content_type = response.content_type.mime_type
|
40
|
+
|
41
|
+
raise HTTPX::Error, "invalid form mime type (#{content_type})" unless MIME_TYPES.match?(content_type)
|
42
|
+
|
43
|
+
Nokogiri::XML.method(:parse)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def load_dependencies(*)
|
49
|
+
require "nokogiri"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module ResponseMethods
|
54
|
+
# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
|
55
|
+
# "application/xml" (requires the "nokogiri" gem).
|
56
|
+
def xml
|
57
|
+
decode(Transcoder)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module RequestBodyClassMethods
|
62
|
+
# ..., xml: Nokogiri::XML::Node #=> xml encoder
|
63
|
+
def initialize_body(params)
|
64
|
+
if (xml = params.delete(:xml))
|
65
|
+
# @type var xml: Nokogiri::XML::Node | String
|
66
|
+
return Transcoder.encode(xml)
|
67
|
+
end
|
68
|
+
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
register_plugin(:xml, XML)
|
75
|
+
end
|
76
|
+
end
|