httpx 1.3.4 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/1_4_0.md +43 -0
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +11 -5
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +11 -7
- data/lib/httpx/connection.rb +128 -16
- 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/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 +16 -12
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +12 -19
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +36 -13
- data/lib/httpx/resolver/resolver.rb +49 -11
- data/lib/httpx/resolver/system.rb +29 -11
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +164 -95
- data/lib/httpx/session.rb +296 -139
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- 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 +19 -20
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +14 -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 -1
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +0 -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 +45 -18
- data/sig/transcoder/body.rbs +1 -1
- 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 +2 -2
- data/sig/transcoder/utils/deflater.rbs +2 -2
- metadata +10 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
@@ -89,7 +89,7 @@ module HTTPX
|
|
89
89
|
sts = "#{algo_line}" \
|
90
90
|
"\n#{datetime}" \
|
91
91
|
"\n#{credential_scope}" \
|
92
|
-
"\n#{hexdigest(creq)}"
|
92
|
+
"\n#{OpenSSL::Digest.new(@algorithm).hexdigest(creq)}"
|
93
93
|
|
94
94
|
# signature
|
95
95
|
k_date = hmac("#{upper_provider_prefix}#{@credentials.password}", date)
|
@@ -110,22 +110,38 @@ module HTTPX
|
|
110
110
|
private
|
111
111
|
|
112
112
|
def hexdigest(value)
|
113
|
-
|
114
|
-
# files, pathnames
|
115
|
-
OpenSSL::Digest.new(@algorithm).file(value.to_path).hexdigest
|
116
|
-
elsif value.respond_to?(:each)
|
117
|
-
digest = OpenSSL::Digest.new(@algorithm)
|
118
|
-
|
119
|
-
mb_buffer = value.each.with_object("".b) do |chunk, buffer|
|
120
|
-
buffer << chunk
|
121
|
-
break if buffer.bytesize >= 1024 * 1024
|
122
|
-
end
|
113
|
+
digest = OpenSSL::Digest.new(@algorithm)
|
123
114
|
|
124
|
-
|
125
|
-
value.
|
126
|
-
|
115
|
+
if value.respond_to?(:read)
|
116
|
+
if value.respond_to?(:to_path)
|
117
|
+
# files, pathnames
|
118
|
+
digest.file(value.to_path).hexdigest
|
119
|
+
else
|
120
|
+
# gzipped request bodies
|
121
|
+
raise Error, "request body must be rewindable" unless value.respond_to?(:rewind)
|
122
|
+
|
123
|
+
buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
124
|
+
begin
|
125
|
+
IO.copy_stream(value, buffer)
|
126
|
+
buffer.flush
|
127
|
+
|
128
|
+
digest.file(buffer.to_path).hexdigest
|
129
|
+
ensure
|
130
|
+
value.rewind
|
131
|
+
buffer.close
|
132
|
+
buffer.unlink
|
133
|
+
end
|
134
|
+
end
|
127
135
|
else
|
128
|
-
|
136
|
+
# error on endless generators
|
137
|
+
raise Error, "hexdigest for endless enumerators is not supported" if value.unbounded_body?
|
138
|
+
|
139
|
+
mb_buffer = value.each.with_object("".b) do |chunk, b|
|
140
|
+
b << chunk
|
141
|
+
break if b.bytesize >= 1024 * 1024
|
142
|
+
end
|
143
|
+
|
144
|
+
digest.hexdigest(mb_buffer)
|
129
145
|
end
|
130
146
|
end
|
131
147
|
|
@@ -142,7 +158,6 @@ module HTTPX
|
|
142
158
|
def load_dependencies(*)
|
143
159
|
require "set"
|
144
160
|
require "digest/sha2"
|
145
|
-
require "openssl"
|
146
161
|
end
|
147
162
|
|
148
163
|
def configure(klass)
|
@@ -31,12 +31,16 @@ module HTTPX
|
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
-
def
|
35
|
-
|
34
|
+
def do_init_connection(connection, selector)
|
35
|
+
super
|
36
36
|
connection.on(:open) do
|
37
|
+
next unless connection.current_session == self
|
38
|
+
|
37
39
|
emit_or_callback_error(:connection_opened, connection.origin, connection.io.socket)
|
38
40
|
end
|
39
41
|
connection.on(:close) do
|
42
|
+
next unless connection.current_session == self
|
43
|
+
|
40
44
|
emit_or_callback_error(:connection_closed, connection.origin) if connection.used?
|
41
45
|
end
|
42
46
|
|
@@ -84,6 +88,12 @@ module HTTPX
|
|
84
88
|
rescue CallbackError => e
|
85
89
|
raise e.cause
|
86
90
|
end
|
91
|
+
|
92
|
+
def close(*)
|
93
|
+
super
|
94
|
+
rescue CallbackError => e
|
95
|
+
raise e.cause
|
96
|
+
end
|
87
97
|
end
|
88
98
|
end
|
89
99
|
register_plugin :callbacks, Callbacks
|
@@ -32,11 +32,6 @@ module HTTPX
|
|
32
32
|
@circuit_store = CircuitStore.new(@options)
|
33
33
|
end
|
34
34
|
|
35
|
-
def initialize_dup(orig)
|
36
|
-
super
|
37
|
-
@circuit_store = orig.instance_variable_get(:@circuit_store).dup
|
38
|
-
end
|
39
|
-
|
40
35
|
%i[circuit_open].each do |meth|
|
41
36
|
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
42
37
|
def on_#{meth}(&blk) # def on_circuit_open(&blk)
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# This plugin adds `Content-Digest` headers to requests
|
7
|
+
# and can validate these headers on responses
|
8
|
+
#
|
9
|
+
# https://datatracker.ietf.org/doc/html/rfc9530
|
10
|
+
#
|
11
|
+
module ContentDigest
|
12
|
+
class Error < HTTPX::Error; end
|
13
|
+
|
14
|
+
# Error raised on response "content-digest" header validation.
|
15
|
+
class ValidationError < Error
|
16
|
+
attr_reader :response
|
17
|
+
|
18
|
+
def initialize(message, response)
|
19
|
+
super(message)
|
20
|
+
@response = response
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MissingContentDigestError < ValidationError; end
|
25
|
+
class InvalidContentDigestError < ValidationError; end
|
26
|
+
|
27
|
+
SUPPORTED_ALGORITHMS = {
|
28
|
+
"sha-256" => OpenSSL::Digest::SHA256,
|
29
|
+
"sha-512" => OpenSSL::Digest::SHA512,
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def extra_options(options)
|
34
|
+
options.merge(encode_content_digest: true, validate_content_digest: false, content_digest_algorithm: "sha-256")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# add support for the following options:
|
39
|
+
#
|
40
|
+
# :content_digest_algorithm :: the digest algorithm to use. Currently supports `sha-256` and `sha-512`. (defaults to `sha-256`)
|
41
|
+
# :encode_content_digest :: whether a <tt>Content-Digest</tt> header should be computed for the request;
|
42
|
+
# can also be a callable object (i.e. <tt>->(req) { ... }</tt>, defaults to <tt>true</tt>)
|
43
|
+
# :validate_content_digest :: whether a <tt>Content-Digest</tt> header in the response should be validated;
|
44
|
+
# can also be a callable object (i.e. <tt>->(res) { ... }</tt>, defaults to <tt>false</tt>)
|
45
|
+
module OptionsMethods
|
46
|
+
def option_content_digest_algorithm(value)
|
47
|
+
raise TypeError, ":content_digest_algorithm must be one of 'sha-256', 'sha-512'" unless SUPPORTED_ALGORITHMS.key?(value)
|
48
|
+
|
49
|
+
value
|
50
|
+
end
|
51
|
+
|
52
|
+
def option_encode_content_digest(value)
|
53
|
+
value
|
54
|
+
end
|
55
|
+
|
56
|
+
def option_validate_content_digest(value)
|
57
|
+
value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module ResponseBodyMethods
|
62
|
+
attr_reader :content_digest_buffer
|
63
|
+
|
64
|
+
def initialize(response, options)
|
65
|
+
super
|
66
|
+
|
67
|
+
return unless response.headers.key?("content-digest")
|
68
|
+
|
69
|
+
should_validate = options.validate_content_digest
|
70
|
+
should_validate = should_validate.call(response) if should_validate.respond_to?(:call)
|
71
|
+
|
72
|
+
return unless should_validate
|
73
|
+
|
74
|
+
@content_digest_buffer = Response::Buffer.new(
|
75
|
+
threshold_size: @options.body_threshold_size,
|
76
|
+
bytesize: @length,
|
77
|
+
encoding: @encoding
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def write(chunk)
|
82
|
+
@content_digest_buffer.write(chunk) if @content_digest_buffer
|
83
|
+
super
|
84
|
+
end
|
85
|
+
|
86
|
+
def close
|
87
|
+
if @content_digest_buffer
|
88
|
+
@content_digest_buffer.close
|
89
|
+
@content_digest_buffer = nil
|
90
|
+
end
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module InstanceMethods
|
96
|
+
def build_request(*)
|
97
|
+
request = super
|
98
|
+
|
99
|
+
return request if request.empty?
|
100
|
+
|
101
|
+
return request if request.headers.key?("content-digest")
|
102
|
+
|
103
|
+
perform_encoding = @options.encode_content_digest
|
104
|
+
perform_encoding = perform_encoding.call(request) if perform_encoding.respond_to?(:call)
|
105
|
+
|
106
|
+
return request unless perform_encoding
|
107
|
+
|
108
|
+
digest = base64digest(request.body)
|
109
|
+
request.headers.add("content-digest", "#{@options.content_digest_algorithm}=:#{digest}:")
|
110
|
+
|
111
|
+
request
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def fetch_response(request, _, _)
|
117
|
+
response = super
|
118
|
+
return response unless response.is_a?(Response)
|
119
|
+
|
120
|
+
perform_validation = @options.validate_content_digest
|
121
|
+
perform_validation = perform_validation.call(response) if perform_validation.respond_to?(:call)
|
122
|
+
|
123
|
+
validate_content_digest(response) if perform_validation
|
124
|
+
|
125
|
+
response
|
126
|
+
rescue ValidationError => e
|
127
|
+
ErrorResponse.new(request, e)
|
128
|
+
end
|
129
|
+
|
130
|
+
def validate_content_digest(response)
|
131
|
+
content_digest_header = response.headers["content-digest"]
|
132
|
+
|
133
|
+
raise MissingContentDigestError.new("response is missing a `content-digest` header", response) unless content_digest_header
|
134
|
+
|
135
|
+
digests = extract_content_digests(content_digest_header)
|
136
|
+
|
137
|
+
included_algorithms = SUPPORTED_ALGORITHMS.keys & digests.keys
|
138
|
+
|
139
|
+
raise MissingContentDigestError.new("unsupported algorithms: #{digests.keys.join(", ")}", response) if included_algorithms.empty?
|
140
|
+
|
141
|
+
content_buffer = response.body.content_digest_buffer
|
142
|
+
|
143
|
+
included_algorithms.each do |algorithm|
|
144
|
+
digest = SUPPORTED_ALGORITHMS.fetch(algorithm).new
|
145
|
+
digest_received = digests[algorithm]
|
146
|
+
digest_computed =
|
147
|
+
if content_buffer.respond_to?(:to_path)
|
148
|
+
content_buffer.flush
|
149
|
+
digest.file(content_buffer.to_path).base64digest
|
150
|
+
else
|
151
|
+
digest.base64digest(content_buffer.to_s)
|
152
|
+
end
|
153
|
+
|
154
|
+
raise InvalidContentDigestError.new("#{algorithm} digest does not match content",
|
155
|
+
response) unless digest_received == digest_computed
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def extract_content_digests(header)
|
160
|
+
header.split(",").to_h do |entry|
|
161
|
+
algorithm, digest = entry.split("=", 2)
|
162
|
+
raise Error, "#{entry} is an invalid digest format" unless algorithm && digest
|
163
|
+
|
164
|
+
[algorithm, digest.byteslice(1..-2)]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def base64digest(body)
|
169
|
+
digest = SUPPORTED_ALGORITHMS.fetch(@options.content_digest_algorithm).new
|
170
|
+
|
171
|
+
if body.respond_to?(:read)
|
172
|
+
if body.respond_to?(:to_path)
|
173
|
+
digest.file(body.to_path).base64digest
|
174
|
+
else
|
175
|
+
raise ContentDigestError, "request body must be rewindable" unless body.respond_to?(:rewind)
|
176
|
+
|
177
|
+
buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
178
|
+
begin
|
179
|
+
IO.copy_stream(body, buffer)
|
180
|
+
buffer.flush
|
181
|
+
|
182
|
+
digest.file(buffer.to_path).base64digest
|
183
|
+
ensure
|
184
|
+
body.rewind
|
185
|
+
buffer.close
|
186
|
+
buffer.unlink
|
187
|
+
end
|
188
|
+
end
|
189
|
+
else
|
190
|
+
raise ContentDigestError, "base64digest for endless enumerators is not supported" if body.unbounded_body?
|
191
|
+
|
192
|
+
buffer = "".b
|
193
|
+
body.each { |chunk| buffer << chunk }
|
194
|
+
|
195
|
+
digest.base64digest(buffer)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
register_plugin :content_digest, ContentDigest
|
201
|
+
end
|
202
|
+
end
|
data/lib/httpx/plugins/expect.rb
CHANGED
@@ -96,15 +96,16 @@ module HTTPX
|
|
96
96
|
end
|
97
97
|
|
98
98
|
module InstanceMethods
|
99
|
-
def fetch_response(request,
|
100
|
-
response =
|
99
|
+
def fetch_response(request, selector, options)
|
100
|
+
response = super
|
101
|
+
|
101
102
|
return unless response
|
102
103
|
|
103
104
|
if response.is_a?(Response) && response.status == 417 && request.headers.key?("expect")
|
104
105
|
response.close
|
105
106
|
request.headers.delete("expect")
|
106
107
|
request.transition(:idle)
|
107
|
-
send_request(request,
|
108
|
+
send_request(request, selector, options)
|
108
109
|
return
|
109
110
|
end
|
110
111
|
|
@@ -64,9 +64,9 @@ module HTTPX
|
|
64
64
|
|
65
65
|
private
|
66
66
|
|
67
|
-
def fetch_response(request,
|
67
|
+
def fetch_response(request, selector, options)
|
68
68
|
redirect_request = request.redirect_request
|
69
|
-
response = super(redirect_request,
|
69
|
+
response = super(redirect_request, selector, options)
|
70
70
|
return unless response
|
71
71
|
|
72
72
|
max_redirects = redirect_request.max_redirects
|
@@ -146,20 +146,19 @@ module HTTPX
|
|
146
146
|
#
|
147
147
|
redirect_after = Utils.parse_retry_after(redirect_after)
|
148
148
|
|
149
|
+
retry_start = Utils.now
|
149
150
|
log { "redirecting after #{redirect_after} secs..." }
|
150
|
-
|
151
|
-
deactivate_connection(request, connections, options)
|
152
|
-
|
153
|
-
pool.after(redirect_after) do
|
151
|
+
selector.after(redirect_after) do
|
154
152
|
if request.response
|
155
153
|
# request has terminated abruptly meanwhile
|
156
154
|
retry_request.emit(:response, request.response)
|
157
155
|
else
|
158
|
-
|
156
|
+
log { "redirecting (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
157
|
+
send_request(retry_request, selector, options)
|
159
158
|
end
|
160
159
|
end
|
161
160
|
else
|
162
|
-
send_request(retry_request,
|
161
|
+
send_request(retry_request, selector, options)
|
163
162
|
end
|
164
163
|
nil
|
165
164
|
end
|
data/lib/httpx/plugins/h2c.rb
CHANGED
@@ -25,26 +25,6 @@ module HTTPX
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
module InstanceMethods
|
29
|
-
def send_requests(*requests)
|
30
|
-
upgrade_request, *remainder = requests
|
31
|
-
|
32
|
-
return super unless VALID_H2C_VERBS.include?(upgrade_request.verb) && upgrade_request.scheme == "http"
|
33
|
-
|
34
|
-
connection = pool.find_connection(upgrade_request.uri, upgrade_request.options)
|
35
|
-
|
36
|
-
return super if connection && connection.upgrade_protocol == "h2c"
|
37
|
-
|
38
|
-
# build upgrade request
|
39
|
-
upgrade_request.headers.add("connection", "upgrade")
|
40
|
-
upgrade_request.headers.add("connection", "http2-settings")
|
41
|
-
upgrade_request.headers["upgrade"] = "h2c"
|
42
|
-
upgrade_request.headers["http2-settings"] = ::HTTP2::Client.settings_header(upgrade_request.options.http2_settings)
|
43
|
-
|
44
|
-
super(upgrade_request, *remainder)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
28
|
class H2CParser < Connection::HTTP2
|
49
29
|
def upgrade(request, response)
|
50
30
|
# skip checks, it is assumed that this is the first
|
@@ -65,6 +45,29 @@ module HTTPX
|
|
65
45
|
module ConnectionMethods
|
66
46
|
using URIExtensions
|
67
47
|
|
48
|
+
def initialize(*)
|
49
|
+
super
|
50
|
+
@h2c_handshake = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def send(request)
|
54
|
+
return super if @h2c_handshake
|
55
|
+
|
56
|
+
return super unless VALID_H2C_VERBS.include?(request.verb) && request.scheme == "http"
|
57
|
+
|
58
|
+
return super if @upgrade_protocol == "h2c"
|
59
|
+
|
60
|
+
@h2c_handshake = true
|
61
|
+
|
62
|
+
# build upgrade request
|
63
|
+
request.headers.add("connection", "upgrade")
|
64
|
+
request.headers.add("connection", "http2-settings")
|
65
|
+
request.headers["upgrade"] = "h2c"
|
66
|
+
request.headers["http2-settings"] = ::HTTP2::Client.settings_header(request.options.http2_settings)
|
67
|
+
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
68
71
|
def upgrade_to_h2c(request, response)
|
69
72
|
prev_parser = @parser
|
70
73
|
|
@@ -76,6 +76,14 @@ module HTTPX
|
|
76
76
|
meter_elapsed_time("Session -> response") if response
|
77
77
|
response
|
78
78
|
end
|
79
|
+
|
80
|
+
def coalesce_connections(conn1, conn2, selector, *)
|
81
|
+
result = super
|
82
|
+
|
83
|
+
meter_elapsed_time("Connection##{conn2.object_id} coalescing to Connection##{conn1.object_id}") if result
|
84
|
+
|
85
|
+
result
|
86
|
+
end
|
79
87
|
end
|
80
88
|
|
81
89
|
module RequestMethods
|
@@ -103,6 +111,25 @@ module HTTPX
|
|
103
111
|
meter_elapsed_time("Connection##{object_id}[#{@origin}]: #{state} -> #{nextstate}") if nextstate == @state
|
104
112
|
end
|
105
113
|
end
|
114
|
+
|
115
|
+
module PoolMethods
|
116
|
+
def self.included(klass)
|
117
|
+
klass.prepend TrackTimeMethods
|
118
|
+
super
|
119
|
+
end
|
120
|
+
|
121
|
+
def checkout_connection(request_uri, options)
|
122
|
+
super.tap do |connection|
|
123
|
+
meter_elapsed_time("Pool##{object_id}: checked out connection for Connection##{connection.object_id}[#{connection.origin}]}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def checkin_connection(connection)
|
128
|
+
super.tap do
|
129
|
+
meter_elapsed_time("Pool##{object_id}: checked in connection for Connection##{connection.object_id}[#{connection.origin}]}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
106
133
|
end
|
107
134
|
register_plugin :internal_telemetry, InternalTelemetry
|
108
135
|
end
|
@@ -30,6 +30,22 @@ module HTTPX
|
|
30
30
|
def self.extra_options(options)
|
31
31
|
options.merge(persistent: true)
|
32
32
|
end
|
33
|
+
|
34
|
+
module InstanceMethods
|
35
|
+
private
|
36
|
+
|
37
|
+
def get_current_selector
|
38
|
+
super(&nil) || begin
|
39
|
+
return unless block_given?
|
40
|
+
|
41
|
+
default = yield
|
42
|
+
|
43
|
+
set_current_selector(default)
|
44
|
+
|
45
|
+
default
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
33
49
|
end
|
34
50
|
register_plugin :persistent, Persistent
|
35
51
|
end
|
@@ -23,29 +23,19 @@ module HTTPX
|
|
23
23
|
with(proxy: opts.merge(scheme: "ntlm"))
|
24
24
|
end
|
25
25
|
|
26
|
-
def fetch_response(request,
|
26
|
+
def fetch_response(request, selector, options)
|
27
27
|
response = super
|
28
28
|
|
29
29
|
if response &&
|
30
30
|
response.is_a?(Response) &&
|
31
31
|
response.status == 407 &&
|
32
32
|
!request.headers.key?("proxy-authorization") &&
|
33
|
-
response.headers.key?("proxy-authenticate")
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
conn.match?(uri, proxy_options)
|
40
|
-
end
|
41
|
-
|
42
|
-
if connection && connection.options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
|
43
|
-
request.transition(:idle)
|
44
|
-
request.headers["proxy-authorization"] =
|
45
|
-
connection.options.proxy.authenticate(request, response.headers["proxy-authenticate"])
|
46
|
-
send_request(request, connections)
|
47
|
-
return
|
48
|
-
end
|
33
|
+
response.headers.key?("proxy-authenticate") && options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
|
34
|
+
request.transition(:idle)
|
35
|
+
request.headers["proxy-authorization"] =
|
36
|
+
options.proxy.authenticate(request, response.headers["proxy-authenticate"])
|
37
|
+
send_request(request, selector, options)
|
38
|
+
return
|
49
39
|
end
|
50
40
|
|
51
41
|
response
|
@@ -74,7 +64,14 @@ module HTTPX
|
|
74
64
|
parser = @parser
|
75
65
|
parser.extend(ProxyParser)
|
76
66
|
parser.on(:response, &method(:__http_on_connect))
|
77
|
-
parser.on(:close)
|
67
|
+
parser.on(:close) do |force|
|
68
|
+
next unless @parser
|
69
|
+
|
70
|
+
if force
|
71
|
+
reset
|
72
|
+
emit(:terminate)
|
73
|
+
end
|
74
|
+
end
|
78
75
|
parser.on(:reset) do
|
79
76
|
if parser.empty?
|
80
77
|
reset
|
@@ -95,8 +92,9 @@ module HTTPX
|
|
95
92
|
|
96
93
|
case @state
|
97
94
|
when :connecting
|
98
|
-
@parser
|
95
|
+
parser = @parser
|
99
96
|
@parser = nil
|
97
|
+
parser.close
|
100
98
|
when :idle
|
101
99
|
@parser.callbacks.clear
|
102
100
|
set_parser_callbacks(@parser)
|