httpx 1.7.6 → 1.7.7
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_7_7.md +17 -0
- data/lib/httpx/adapters/faraday.rb +0 -10
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/connection/http1.rb +37 -31
- data/lib/httpx/connection/http2.rb +13 -13
- data/lib/httpx/connection.rb +4 -4
- data/lib/httpx/io/ssl.rb +4 -4
- data/lib/httpx/loggable.rb +1 -1
- data/lib/httpx/parser/http1.rb +0 -1
- data/lib/httpx/plugins/auth/digest.rb +6 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +1 -0
- data/lib/httpx/plugins/cookies/cookie.rb +0 -1
- data/lib/httpx/plugins/digest_auth.rb +3 -1
- data/lib/httpx/plugins/follow_redirects.rb +13 -1
- data/lib/httpx/plugins/h2c.rb +2 -12
- data/lib/httpx/plugins/proxy/http.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +1 -1
- data/lib/httpx/plugins/response_cache.rb +11 -4
- data/lib/httpx/plugins/retries.rb +5 -5
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/tracing.rb +1 -6
- data/lib/httpx/plugins/upgrade/h2.rb +1 -11
- data/lib/httpx/plugins/upgrade.rb +17 -17
- data/lib/httpx/request.rb +15 -2
- data/lib/httpx/selector.rb +6 -1
- data/lib/httpx/session.rb +27 -23
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http1.rbs +3 -1
- data/sig/connection/http2.rbs +1 -1
- data/sig/loggable.rbs +1 -1
- data/sig/plugins/auth/basic.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +1 -1
- data/sig/plugins/auth/ntlm.rbs +2 -0
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/proxy.rbs +1 -0
- data/sig/plugins/response_cache.rbs +2 -0
- data/sig/plugins/tracing.rbs +1 -1
- data/sig/request.rbs +3 -0
- data/sig/session.rbs +0 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64a496153f6b67985861f18f1ab86793d356bfb1f49045346e55c4df8a991881
|
|
4
|
+
data.tar.gz: 449bcbe6d7acf74e744f296dbfdf2530fe411b66bfecfba5552e5f6185465ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '02781b0d9ce574816cb07722aa5f22383c75872b2d782ba9bfc651b9b94b5ffb03860f01cfebdd2de5d38d28e40f7388595b751d549f7ec791e5bf4a9fd3b9e9'
|
|
7
|
+
data.tar.gz: 7f5de973f936e7fa6265918d7d8facf2602367c85cbc90c3ad09b92b98c01f34f9f9513a0faff0498f945d12cb41938b232bf74b96a45eb03ab680fd721ed43f
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 1.7.7
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* ssl cert info will also be redacted when `:debug_redact` option is enabled.
|
|
6
|
+
* fix performance regression of #receive_requests recent loop change (which also improved performance of previous baseline).
|
|
7
|
+
* header/data logs indexed by request (instead of parser).
|
|
8
|
+
|
|
9
|
+
## Bugfixes
|
|
10
|
+
|
|
11
|
+
* proxy: fixed issue when basic auth to proxy failed with 407 and it'd raise NoMethodError when retrying (marked as unretriable now).
|
|
12
|
+
* selector: prevent removal of wrong selectable when previous closed via `#on_close` during selectables traversal.
|
|
13
|
+
* http1: only reset requests which weren't completed (prevent issuing errors twice).
|
|
14
|
+
* http1: forego clearing buffer on reset (avoid deleting potential next request bytes).
|
|
15
|
+
* response_cache: reset cached response age on successful cache revalidation, as per RFC.
|
|
16
|
+
* rate_limiter: do not apply jitter when delay comes from `retry-after` header.
|
|
17
|
+
* digest: digest header parsing issues results in an error response (instead of an exception raised).
|
|
@@ -182,15 +182,6 @@ module Faraday
|
|
|
182
182
|
@on_response
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
|
-
|
|
186
|
-
def on_complete(&blk)
|
|
187
|
-
if blk
|
|
188
|
-
@on_complete = blk
|
|
189
|
-
self
|
|
190
|
-
else
|
|
191
|
-
@on_complete
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
185
|
end
|
|
195
186
|
|
|
196
187
|
include RequestMixin
|
|
@@ -224,7 +215,6 @@ module Faraday
|
|
|
224
215
|
Array(responses).each_with_index do |response, index|
|
|
225
216
|
handler = @handlers[index]
|
|
226
217
|
handler.on_response.call(response)
|
|
227
|
-
handler.on_complete.call(handler.env) if handler.on_complete
|
|
228
218
|
end
|
|
229
219
|
end
|
|
230
220
|
end
|
|
@@ -132,7 +132,7 @@ module WebMock
|
|
|
132
132
|
request.transition(:done)
|
|
133
133
|
response.finish!
|
|
134
134
|
request.response = response
|
|
135
|
-
request.
|
|
135
|
+
request.emit_response(response)
|
|
136
136
|
request_signature.headers = request.headers.to_h
|
|
137
137
|
|
|
138
138
|
response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
|
|
@@ -54,9 +54,12 @@ module HTTPX
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def reset_requests
|
|
57
|
-
requests
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
@requests.reverse_each do |request|
|
|
58
|
+
next if request.response
|
|
59
|
+
|
|
60
|
+
request.transition(:idle)
|
|
61
|
+
@pending.unshift(request)
|
|
62
|
+
end
|
|
60
63
|
@requests.clear
|
|
61
64
|
end
|
|
62
65
|
|
|
@@ -116,30 +119,32 @@ module HTTPX
|
|
|
116
119
|
end
|
|
117
120
|
|
|
118
121
|
def on_headers(h)
|
|
119
|
-
@request = @requests.first
|
|
122
|
+
request = @request = @requests.first
|
|
120
123
|
|
|
121
|
-
return if
|
|
124
|
+
return if request.response
|
|
122
125
|
|
|
123
|
-
log(level: 2) { "headers received" }
|
|
124
|
-
headers =
|
|
125
|
-
response =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
|
|
130
|
-
log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
|
|
126
|
+
request.log(level: 2) { "headers received" }
|
|
127
|
+
headers = request.options.headers_class.new(h)
|
|
128
|
+
response = request.options.response_class.new(request,
|
|
129
|
+
@parser.status_code,
|
|
130
|
+
@parser.http_version.join("."),
|
|
131
|
+
headers)
|
|
132
|
+
request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
|
|
133
|
+
request.log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
|
|
131
134
|
|
|
132
|
-
|
|
135
|
+
request.response = response
|
|
133
136
|
on_complete if response.finished?
|
|
134
137
|
end
|
|
135
138
|
|
|
136
139
|
def on_trailers(h)
|
|
137
|
-
|
|
140
|
+
request = @request
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
log(level: 2) { "trailer headers received" }
|
|
142
|
+
return unless request
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
response = request.response
|
|
145
|
+
|
|
146
|
+
request.log(level: 2) { "trailer headers received" }
|
|
147
|
+
request.log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
|
|
143
148
|
response.merge_headers(h)
|
|
144
149
|
end
|
|
145
150
|
|
|
@@ -149,8 +154,9 @@ module HTTPX
|
|
|
149
154
|
return unless request
|
|
150
155
|
|
|
151
156
|
begin
|
|
152
|
-
log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
|
153
|
-
log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
|
|
157
|
+
request.log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
|
158
|
+
request.log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
|
|
159
|
+
|
|
154
160
|
response = request.response
|
|
155
161
|
|
|
156
162
|
response << chunk
|
|
@@ -166,7 +172,7 @@ module HTTPX
|
|
|
166
172
|
|
|
167
173
|
return unless request
|
|
168
174
|
|
|
169
|
-
log(level: 2) { "parsing complete" }
|
|
175
|
+
request.log(level: 2) { "parsing complete" }
|
|
170
176
|
dispatch(request)
|
|
171
177
|
end
|
|
172
178
|
|
|
@@ -293,7 +299,7 @@ module HTTPX
|
|
|
293
299
|
return if @max_concurrent_requests == 1
|
|
294
300
|
|
|
295
301
|
@requests.each do |r|
|
|
296
|
-
r.transition(:idle)
|
|
302
|
+
r.transition(:idle) if r.response.nil?
|
|
297
303
|
|
|
298
304
|
# when we disable pipelining, we still want to try keep-alive.
|
|
299
305
|
# only when keep-alive with one request fails, do we fallback to
|
|
@@ -362,10 +368,10 @@ module HTTPX
|
|
|
362
368
|
def join_headers(request)
|
|
363
369
|
headline = join_headline(request)
|
|
364
370
|
@buffer << headline << CRLF
|
|
365
|
-
log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
|
371
|
+
request.log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
|
366
372
|
extra_headers = set_protocol_headers(request)
|
|
367
|
-
join_headers2(request.headers.each(extra_headers))
|
|
368
|
-
log { "<- " }
|
|
373
|
+
join_headers2(request, request.headers.each(extra_headers))
|
|
374
|
+
request.log { "<- " }
|
|
369
375
|
@buffer << CRLF
|
|
370
376
|
end
|
|
371
377
|
|
|
@@ -373,8 +379,8 @@ module HTTPX
|
|
|
373
379
|
return if request.body.empty?
|
|
374
380
|
|
|
375
381
|
while (chunk = request.drain_body)
|
|
376
|
-
log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
|
377
|
-
log(level: 2, color: :green) { "<- #{log_redact_body(chunk.inspect)}" }
|
|
382
|
+
request.log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
|
383
|
+
request.log(level: 2, color: :green) { "<- #{log_redact_body(chunk.inspect)}" }
|
|
378
384
|
@buffer << chunk
|
|
379
385
|
throw(:buffer_full, request) if @buffer.full?
|
|
380
386
|
end
|
|
@@ -387,15 +393,15 @@ module HTTPX
|
|
|
387
393
|
def join_trailers(request)
|
|
388
394
|
return unless request.trailers? && request.callbacks_for?(:trailers)
|
|
389
395
|
|
|
390
|
-
join_headers2(request.trailers)
|
|
391
|
-
log { "<- " }
|
|
396
|
+
join_headers2(request, request.trailers)
|
|
397
|
+
request.log { "<- " }
|
|
392
398
|
@buffer << CRLF
|
|
393
399
|
end
|
|
394
400
|
|
|
395
|
-
def join_headers2(headers)
|
|
401
|
+
def join_headers2(request, headers)
|
|
396
402
|
headers.each do |field, value|
|
|
397
403
|
field = capitalized(field)
|
|
398
|
-
log(color: :yellow) { "<- HEADER: #{[field, log_redact_headers(value)].join(": ")}" }
|
|
404
|
+
request.log(color: :yellow) { "<- HEADER: #{[field, log_redact_headers(value)].join(": ")}" }
|
|
399
405
|
@buffer << "#{field}: #{value}#{CRLF}"
|
|
400
406
|
end
|
|
401
407
|
end
|
|
@@ -242,11 +242,11 @@ module HTTPX
|
|
|
242
242
|
extra_headers = set_protocol_headers(request)
|
|
243
243
|
|
|
244
244
|
if request.headers.key?("host")
|
|
245
|
-
log { "forbidden \"host\" header found (#{log_redact_headers(request.headers["host"])}), will use it as authority..." }
|
|
245
|
+
request.log { "forbidden \"host\" header found (#{log_redact_headers(request.headers["host"])}), will use it as authority..." }
|
|
246
246
|
extra_headers[":authority"] = request.headers["host"]
|
|
247
247
|
end
|
|
248
248
|
|
|
249
|
-
log(level: 1, color: :yellow) do
|
|
249
|
+
request.log(level: 1, color: :yellow) do
|
|
250
250
|
"\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")}"
|
|
251
251
|
end
|
|
252
252
|
stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
|
|
@@ -258,7 +258,7 @@ module HTTPX
|
|
|
258
258
|
return
|
|
259
259
|
end
|
|
260
260
|
|
|
261
|
-
log(level: 1, color: :yellow) do
|
|
261
|
+
request.log(level: 1, color: :yellow) do
|
|
262
262
|
request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
263
263
|
end
|
|
264
264
|
stream.headers(request.trailers.each, end_stream: true)
|
|
@@ -286,8 +286,8 @@ module HTTPX
|
|
|
286
286
|
end
|
|
287
287
|
|
|
288
288
|
def send_chunk(request, stream, chunk, next_chunk)
|
|
289
|
-
log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
|
|
290
|
-
log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact_body(chunk.inspect)}" }
|
|
289
|
+
request.log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
|
|
290
|
+
request.log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact_body(chunk.inspect)}" }
|
|
291
291
|
stream.data(chunk, end_stream: end_stream?(request, next_chunk))
|
|
292
292
|
end
|
|
293
293
|
|
|
@@ -303,11 +303,11 @@ module HTTPX
|
|
|
303
303
|
response = request.response
|
|
304
304
|
|
|
305
305
|
if response.is_a?(Response) && response.version == "2.0"
|
|
306
|
-
on_stream_trailers(stream, response, h)
|
|
306
|
+
on_stream_trailers(stream, request, response, h)
|
|
307
307
|
return
|
|
308
308
|
end
|
|
309
309
|
|
|
310
|
-
log(color: :yellow) do
|
|
310
|
+
request.log(color: :yellow) do
|
|
311
311
|
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
|
|
312
312
|
end
|
|
313
313
|
_, status = h.shift
|
|
@@ -319,16 +319,16 @@ module HTTPX
|
|
|
319
319
|
handle(request, stream) if request.expects?
|
|
320
320
|
end
|
|
321
321
|
|
|
322
|
-
def on_stream_trailers(stream, response, h)
|
|
323
|
-
log(color: :yellow) do
|
|
322
|
+
def on_stream_trailers(stream, request, response, h)
|
|
323
|
+
request.log(color: :yellow) do
|
|
324
324
|
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
325
325
|
end
|
|
326
326
|
response.merge_headers(h)
|
|
327
327
|
end
|
|
328
328
|
|
|
329
329
|
def on_stream_data(stream, request, data)
|
|
330
|
-
log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
|
|
331
|
-
log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
|
|
330
|
+
request.log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
|
|
331
|
+
request.log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
|
|
332
332
|
request.response << data
|
|
333
333
|
end
|
|
334
334
|
|
|
@@ -337,14 +337,14 @@ module HTTPX
|
|
|
337
337
|
stream.close
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
-
def on_stream_half_close(stream,
|
|
340
|
+
def on_stream_half_close(stream, request)
|
|
341
341
|
unless stream.send_buffer.empty?
|
|
342
342
|
stream.send_buffer.clear
|
|
343
343
|
stream.data("", end_stream: true)
|
|
344
344
|
end
|
|
345
345
|
|
|
346
346
|
# TODO: omit log line if response already here
|
|
347
|
-
log(level: 2) { "#{stream.id}: waiting for response..." }
|
|
347
|
+
request.log(level: 2) { "#{stream.id}: waiting for response..." }
|
|
348
348
|
end
|
|
349
349
|
|
|
350
350
|
def on_stream_close(stream, request, error)
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -643,7 +643,7 @@ module HTTPX
|
|
|
643
643
|
@no_more_requests_counter = 0
|
|
644
644
|
@inflight -= 1
|
|
645
645
|
response.finish!
|
|
646
|
-
request.
|
|
646
|
+
request.emit_response(response)
|
|
647
647
|
end
|
|
648
648
|
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
|
649
649
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
@@ -707,7 +707,7 @@ module HTTPX
|
|
|
707
707
|
@inflight -= 1
|
|
708
708
|
response = ErrorResponse.new(request, error)
|
|
709
709
|
request.response = response
|
|
710
|
-
request.
|
|
710
|
+
request.emit_response(response)
|
|
711
711
|
end
|
|
712
712
|
end
|
|
713
713
|
|
|
@@ -938,7 +938,7 @@ module HTTPX
|
|
|
938
938
|
@inflight -= 1
|
|
939
939
|
response = ErrorResponse.new(request, error)
|
|
940
940
|
request.response = response
|
|
941
|
-
request.
|
|
941
|
+
request.emit_response(response)
|
|
942
942
|
end
|
|
943
943
|
|
|
944
944
|
pending = @pending
|
|
@@ -955,7 +955,7 @@ module HTTPX
|
|
|
955
955
|
|
|
956
956
|
resp = ErrorResponse.new(req, error)
|
|
957
957
|
req.response = resp
|
|
958
|
-
req.
|
|
958
|
+
req.emit_response(resp)
|
|
959
959
|
end
|
|
960
960
|
end
|
|
961
961
|
|
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -165,10 +165,10 @@ module HTTPX
|
|
|
165
165
|
"SSL connection using #{@io.ssl_version} / #{Array(@io.cipher).first}\n" \
|
|
166
166
|
"ALPN, server accepted to use #{protocol}\n" \
|
|
167
167
|
"Server certificate:\n " \
|
|
168
|
-
"subject: #{server_cert.subject}\n " \
|
|
169
|
-
"start date: #{server_cert.not_before}\n " \
|
|
170
|
-
"expire date: #{server_cert.not_after}\n " \
|
|
171
|
-
"issuer: #{server_cert.issuer}\n " \
|
|
168
|
+
"subject: #{log_redact(server_cert.subject)}\n " \
|
|
169
|
+
"start date: #{log_redact(server_cert.not_before)}\n " \
|
|
170
|
+
"expire date: #{log_redact(server_cert.not_after)}\n " \
|
|
171
|
+
"issuer: #{log_redact(server_cert.issuer)}\n " \
|
|
172
172
|
"SSL certificate verify ok."
|
|
173
173
|
end
|
|
174
174
|
end
|
data/lib/httpx/loggable.rb
CHANGED
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -24,6 +24,11 @@ module HTTPX
|
|
|
24
24
|
|
|
25
25
|
def authenticate(request, authenticate)
|
|
26
26
|
"Digest #{generate_header(request.verb, request.path, authenticate)}"
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
response = ErrorResponse.new(request, e)
|
|
29
|
+
request.response = response
|
|
30
|
+
request.emit_response(response)
|
|
31
|
+
nil
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
private
|
|
@@ -77,6 +82,7 @@ module HTTPX
|
|
|
77
82
|
|
|
78
83
|
if params["algorithm"] =~ /(.*?)(-sess)?$/
|
|
79
84
|
alg = Regexp.last_match(1)
|
|
85
|
+
raise_format_error unless alg
|
|
80
86
|
algorithm = ::Digest.const_get(alg)
|
|
81
87
|
raise Error, "unknown algorithm \"#{alg}\"" unless algorithm
|
|
82
88
|
|
|
@@ -17,6 +17,7 @@ module HTTPX
|
|
|
17
17
|
@break_in = break_in
|
|
18
18
|
@circuit_breaker_half_open_drip_rate = circuit_breaker_half_open_drip_rate
|
|
19
19
|
@attempts = 0
|
|
20
|
+
@opened_at = @attempted_at = @response = nil
|
|
20
21
|
|
|
21
22
|
total_real_attempts = @max_attempts * @circuit_breaker_half_open_drip_rate
|
|
22
23
|
@drip_factor = (@max_attempts / total_real_attempts).round
|
|
@@ -155,7 +155,6 @@ module HTTPX
|
|
|
155
155
|
# Tests if it is OK to send this cookie to a given `uri`. A
|
|
156
156
|
# RuntimeError is raised if the cookie's domain is unknown.
|
|
157
157
|
def valid_for_uri?(uri)
|
|
158
|
-
uri = URI(uri)
|
|
159
158
|
# RFC 6265 5.4
|
|
160
159
|
|
|
161
160
|
return false if @secure && uri.scheme != "https"
|
|
@@ -51,7 +51,9 @@ module HTTPX
|
|
|
51
51
|
|
|
52
52
|
if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
53
53
|
request.transition(:idle)
|
|
54
|
-
|
|
54
|
+
if (auth_header = digest.authenticate(request, probe_response.headers["www-authenticate"]))
|
|
55
|
+
request.authorize(auth_header)
|
|
56
|
+
end
|
|
55
57
|
super(request)
|
|
56
58
|
else
|
|
57
59
|
probe_response
|
|
@@ -157,7 +157,7 @@ module HTTPX
|
|
|
157
157
|
response.finish!
|
|
158
158
|
retry_request.response = response
|
|
159
159
|
# request has terminated abruptly meanwhile
|
|
160
|
-
retry_request.
|
|
160
|
+
retry_request.emit_response(response)
|
|
161
161
|
else
|
|
162
162
|
log { "redirecting (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
|
163
163
|
send_request(retry_request, selector, options)
|
|
@@ -201,6 +201,17 @@ module HTTPX
|
|
|
201
201
|
# returns the top-most original HTTPX::Request from the redirect chain
|
|
202
202
|
attr_accessor :root_request
|
|
203
203
|
|
|
204
|
+
def initialize(*)
|
|
205
|
+
super
|
|
206
|
+
@redirect_request = nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def on_response_arrived=(cb)
|
|
210
|
+
@redirect_request.on_response_arrived = cb if @redirect_request
|
|
211
|
+
|
|
212
|
+
super
|
|
213
|
+
end
|
|
214
|
+
|
|
204
215
|
# returns the follow-up redirect request, or itself
|
|
205
216
|
def redirect_request
|
|
206
217
|
@redirect_request || self
|
|
@@ -210,6 +221,7 @@ module HTTPX
|
|
|
210
221
|
def redirect_request=(req)
|
|
211
222
|
@redirect_request = req
|
|
212
223
|
req.root_request = @root_request || self
|
|
224
|
+
req.on_response_arrived = @on_response_arrived
|
|
213
225
|
@response = nil
|
|
214
226
|
end
|
|
215
227
|
|
data/lib/httpx/plugins/h2c.rb
CHANGED
|
@@ -68,23 +68,13 @@ module HTTPX
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def upgrade_to_h2c(request, response)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if prev_parser
|
|
74
|
-
prev_parser.reset
|
|
75
|
-
@inflight -= prev_parser.requests.size
|
|
76
|
-
end
|
|
71
|
+
enqueue_pending_requests_from_parser(@parser)
|
|
77
72
|
|
|
78
73
|
@parser = request.options.h2c_class.new(@write_buffer, @options)
|
|
79
74
|
set_parser_callbacks(@parser)
|
|
80
|
-
@inflight += 1
|
|
75
|
+
@inflight += 1 # request is being completed below
|
|
81
76
|
@parser.upgrade(request, response)
|
|
82
77
|
@upgrade_protocol = "h2c"
|
|
83
|
-
|
|
84
|
-
prev_parser.pending.each do |req|
|
|
85
|
-
req.transition(:idle)
|
|
86
|
-
send(req)
|
|
87
|
-
end
|
|
88
78
|
end
|
|
89
79
|
|
|
90
80
|
private
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -140,7 +140,7 @@ module HTTPX
|
|
|
140
140
|
cached_response = cached_response.dup
|
|
141
141
|
cached_response.mark_as_cached!
|
|
142
142
|
request.response = cached_response
|
|
143
|
-
request.
|
|
143
|
+
request.emit_response(cached_response)
|
|
144
144
|
return
|
|
145
145
|
end
|
|
146
146
|
|
|
@@ -220,11 +220,12 @@ module HTTPX
|
|
|
220
220
|
end
|
|
221
221
|
|
|
222
222
|
module ResponseMethods
|
|
223
|
-
attr_writer :original_request
|
|
223
|
+
attr_writer :original_request, :revalidated_at
|
|
224
224
|
|
|
225
225
|
def initialize(*)
|
|
226
226
|
super
|
|
227
227
|
@cached = false
|
|
228
|
+
@revalidated_at = nil
|
|
228
229
|
end
|
|
229
230
|
|
|
230
231
|
# a copy of the request this response was originally cached from
|
|
@@ -254,6 +255,8 @@ module HTTPX
|
|
|
254
255
|
@body = cached_response.body.dup
|
|
255
256
|
|
|
256
257
|
@body.rewind
|
|
258
|
+
|
|
259
|
+
cached_response.revalidated_at = date
|
|
257
260
|
end
|
|
258
261
|
|
|
259
262
|
# A response is fresh if its age has not yet exceeded its freshness lifetime.
|
|
@@ -314,9 +317,13 @@ module HTTPX
|
|
|
314
317
|
# returns the value of the "age" header as an Integer (time since epoch).
|
|
315
318
|
# if no "age" of header exists, it returns the number of seconds since {#date}.
|
|
316
319
|
def age
|
|
317
|
-
|
|
320
|
+
if (revalidated_at = @revalidated_at)
|
|
321
|
+
(Time.now - revalidated_at).to_i
|
|
322
|
+
else
|
|
323
|
+
return @headers["age"].to_i if @headers.key?("age")
|
|
318
324
|
|
|
319
|
-
|
|
325
|
+
(Time.now - date).to_i
|
|
326
|
+
end
|
|
320
327
|
end
|
|
321
328
|
|
|
322
329
|
# returns the value of the "date" header as a Time object
|
|
@@ -149,10 +149,6 @@ module HTTPX
|
|
|
149
149
|
prepare_to_retry(request, response)
|
|
150
150
|
|
|
151
151
|
if (retry_after = when_to_retry(request, response, options)) && retry_after.positive?
|
|
152
|
-
# apply jitter
|
|
153
|
-
if (jitter = request.options.retry_jitter)
|
|
154
|
-
retry_after = jitter.call(retry_after)
|
|
155
|
-
end
|
|
156
152
|
|
|
157
153
|
retry_start = Utils.now
|
|
158
154
|
log { "retrying after #{retry_after} secs..." }
|
|
@@ -160,7 +156,7 @@ module HTTPX
|
|
|
160
156
|
if (response = request.response)
|
|
161
157
|
response.finish!
|
|
162
158
|
# request has terminated abruptly meanwhile
|
|
163
|
-
request.
|
|
159
|
+
request.emit_response(response)
|
|
164
160
|
else
|
|
165
161
|
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
|
166
162
|
send_request(request, selector, options)
|
|
@@ -205,6 +201,10 @@ module HTTPX
|
|
|
205
201
|
def when_to_retry(request, response, options)
|
|
206
202
|
retry_after = options.retry_after
|
|
207
203
|
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
204
|
+
# apply jitter
|
|
205
|
+
if (jitter = request.options.retry_jitter)
|
|
206
|
+
retry_after = jitter.call(retry_after)
|
|
207
|
+
end
|
|
208
208
|
retry_after
|
|
209
209
|
end
|
|
210
210
|
|
|
@@ -107,7 +107,7 @@ module HTTPX
|
|
|
107
107
|
error.set_backtrace(caller)
|
|
108
108
|
response = ErrorResponse.new(request, error)
|
|
109
109
|
request.response = response
|
|
110
|
-
request.
|
|
110
|
+
request.emit_response(response)
|
|
111
111
|
response
|
|
112
112
|
end
|
|
113
113
|
allowed_requests = requests.select { |req| responses[requests.index(req)].nil? }
|
|
@@ -33,12 +33,7 @@ module HTTPX
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def upgrade_to_h2
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if prev_parser
|
|
39
|
-
prev_parser.reset
|
|
40
|
-
@inflight -= prev_parser.requests.size
|
|
41
|
-
end
|
|
36
|
+
enqueue_pending_requests_from_parser(@parser)
|
|
42
37
|
|
|
43
38
|
@parser = @options.http2_class.new(@write_buffer, @options)
|
|
44
39
|
set_parser_callbacks(@parser)
|
|
@@ -51,11 +46,6 @@ module HTTPX
|
|
|
51
46
|
# while the parser is already here.
|
|
52
47
|
purge_after_closed
|
|
53
48
|
transition(:idle)
|
|
54
|
-
|
|
55
|
-
prev_parser.requests.each do |req|
|
|
56
|
-
req.transition(:idle)
|
|
57
|
-
send(req)
|
|
58
|
-
end
|
|
59
49
|
end
|
|
60
50
|
end
|
|
61
51
|
end
|
|
@@ -33,32 +33,32 @@ module HTTPX
|
|
|
33
33
|
def fetch_response(request, selector, options)
|
|
34
34
|
response = super
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
return response unless response.is_a?(Response)
|
|
36
|
+
return unless response
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
return response unless response.is_a?(Response)
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
return response unless response.headers.key?("upgrade")
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
return response unless upgrade_protocol && options.upgrade_handlers.key?(upgrade_protocol)
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
protocol_handler = options.upgrade_handlers[upgrade_protocol]
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
connection = find_connection(request.uri, selector, options)
|
|
48
|
+
return response unless protocol_handler
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
log { "upgrading to #{upgrade_protocol}..." }
|
|
51
|
+
connection = find_connection(request.uri, selector, options)
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
# do not upgrade already upgraded connections
|
|
54
|
+
return if connection.upgrade_protocol == upgrade_protocol
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
protocol_handler.call(connection, request, response)
|
|
57
|
+
|
|
58
|
+
# keep in the loop if the server is switching, unless
|
|
59
|
+
# the connection has been hijacked, in which case you want
|
|
60
|
+
# to terminante immediately
|
|
61
|
+
return fetch_response(request, selector, options) if response.status == 101 && !connection.hijacked
|
|
62
62
|
|
|
63
63
|
response
|
|
64
64
|
end
|
data/lib/httpx/request.rb
CHANGED
|
@@ -45,6 +45,9 @@ module HTTPX
|
|
|
45
45
|
# the connection the request is currently being sent to (none if before or after transaction)
|
|
46
46
|
attr_writer :connection
|
|
47
47
|
|
|
48
|
+
# callback triggered when a response (which may not be the final response) was assigned to the request.
|
|
49
|
+
attr_writer :on_response_arrived
|
|
50
|
+
|
|
48
51
|
attr_writer :persistent
|
|
49
52
|
|
|
50
53
|
attr_reader :active_timeouts
|
|
@@ -94,7 +97,9 @@ module HTTPX
|
|
|
94
97
|
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
|
95
98
|
|
|
96
99
|
@state = :idle
|
|
97
|
-
@connection = @response =
|
|
100
|
+
@connection = @response =
|
|
101
|
+
@drainer = @peer_address =
|
|
102
|
+
@informational_status = @on_response_arrived = nil
|
|
98
103
|
@ping = false
|
|
99
104
|
@persistent = @options.persistent
|
|
100
105
|
@active_timeouts = []
|
|
@@ -330,9 +335,17 @@ module HTTPX
|
|
|
330
335
|
else
|
|
331
336
|
response = ErrorResponse.new(self, error)
|
|
332
337
|
self.response = response
|
|
333
|
-
|
|
338
|
+
emit_response(response)
|
|
334
339
|
end
|
|
335
340
|
end
|
|
341
|
+
|
|
342
|
+
def emit_response(response)
|
|
343
|
+
emit(:response, response)
|
|
344
|
+
|
|
345
|
+
return unless @on_response_arrived
|
|
346
|
+
|
|
347
|
+
@on_response_arrived.call
|
|
348
|
+
end
|
|
336
349
|
end
|
|
337
350
|
end
|
|
338
351
|
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -147,7 +147,12 @@ module HTTPX
|
|
|
147
147
|
|
|
148
148
|
is_closed = io.state == :closed
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
if is_closed
|
|
151
|
+
# the process by which io was closed may have already triggered the on_close callback,
|
|
152
|
+
# which already deregistered the io. this check prevents it from deleting the wrong io,
|
|
153
|
+
# because of https://bugs.ruby-lang.org/issues/22021 .
|
|
154
|
+
next(@selectables.include?(io))
|
|
155
|
+
end
|
|
151
156
|
|
|
152
157
|
if interests
|
|
153
158
|
io.log(level: 2) do
|
data/lib/httpx/session.rb
CHANGED
|
@@ -192,11 +192,8 @@ module HTTPX
|
|
|
192
192
|
when :idle
|
|
193
193
|
do_init_connection(connection, selector)
|
|
194
194
|
when :open
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
else
|
|
198
|
-
pin(connection, selector)
|
|
199
|
-
end
|
|
195
|
+
# external io
|
|
196
|
+
select_connection(connection, selector)
|
|
200
197
|
when :closing, :closed
|
|
201
198
|
connection.idling
|
|
202
199
|
if connection.addresses?
|
|
@@ -262,7 +259,7 @@ module HTTPX
|
|
|
262
259
|
|
|
263
260
|
response = ErrorResponse.new(request, error)
|
|
264
261
|
request.response = response
|
|
265
|
-
request.
|
|
262
|
+
request.emit_response(response)
|
|
266
263
|
end
|
|
267
264
|
|
|
268
265
|
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
|
|
@@ -303,7 +300,6 @@ module HTTPX
|
|
|
303
300
|
def send_requests(*requests)
|
|
304
301
|
selector = get_current_selector { Selector.new }
|
|
305
302
|
begin
|
|
306
|
-
_send_requests(requests, selector)
|
|
307
303
|
receive_requests(requests, selector)
|
|
308
304
|
ensure
|
|
309
305
|
unless @wrapped
|
|
@@ -316,42 +312,50 @@ module HTTPX
|
|
|
316
312
|
end
|
|
317
313
|
end
|
|
318
314
|
|
|
319
|
-
# sends an array of HTTPX::Request objects
|
|
320
|
-
def _send_requests(requests, selector)
|
|
321
|
-
requests.each do |request|
|
|
322
|
-
send_request(request, selector)
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
315
|
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
|
|
327
316
|
def receive_requests(requests, selector)
|
|
328
|
-
|
|
329
|
-
|
|
317
|
+
pending_idxs = [] #: Array[Integer]
|
|
318
|
+
pending = 0
|
|
319
|
+
|
|
320
|
+
waiting = false
|
|
321
|
+
|
|
322
|
+
responses = requests.each_with_index.map do |request, idx|
|
|
323
|
+
send_request(request, selector)
|
|
324
|
+
|
|
330
325
|
fetch_response(request, selector, request.options).tap do |response|
|
|
331
|
-
|
|
326
|
+
if response.nil?
|
|
327
|
+
pending += 1
|
|
328
|
+
request.on_response_arrived = lambda do
|
|
329
|
+
pending_idxs << idx if waiting
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
332
|
end
|
|
333
333
|
end
|
|
334
334
|
|
|
335
|
-
until
|
|
335
|
+
until pending.zero? || selector.empty?
|
|
336
336
|
# loop on selector until at least one response has been received.
|
|
337
|
+
waiting = true
|
|
337
338
|
catch(:coalesced) { selector.next_tick }
|
|
339
|
+
waiting = false
|
|
338
340
|
|
|
339
|
-
|
|
340
|
-
next unless response.nil?
|
|
341
|
-
|
|
341
|
+
while (idx = pending_idxs.shift)
|
|
342
342
|
request = requests[idx]
|
|
343
343
|
|
|
344
344
|
response = fetch_response(request, selector, request.options)
|
|
345
345
|
|
|
346
|
+
# stop on first pending response. this avoids traversing pending idxs all the way
|
|
347
|
+
# (which is more expensive in the beginning, when the array is larger and N) while
|
|
348
|
+
# making the next loop cheaper (because we're dropping).
|
|
346
349
|
next unless response
|
|
347
350
|
|
|
348
351
|
request.complete!(response)
|
|
349
352
|
responses[idx] = response
|
|
350
|
-
|
|
353
|
+
request.on_response_arrived = nil
|
|
354
|
+
pending -= 1
|
|
351
355
|
end
|
|
352
356
|
end
|
|
353
357
|
|
|
354
|
-
raise Error, "something went wrong, responses not found and requests not resent" unless
|
|
358
|
+
raise Error, "something went wrong, responses not found and requests not resent" unless pending.zero?
|
|
355
359
|
|
|
356
360
|
responses
|
|
357
361
|
end
|
data/lib/httpx/version.rb
CHANGED
data/sig/connection/http1.rbs
CHANGED
|
@@ -74,11 +74,13 @@ module HTTPX
|
|
|
74
74
|
|
|
75
75
|
def handle: (Request request) -> void
|
|
76
76
|
|
|
77
|
+
def join_headline: (Request request) -> String
|
|
78
|
+
|
|
77
79
|
def join_headers: (Request request) -> void
|
|
78
80
|
|
|
79
81
|
def join_trailers: (Request request) -> void
|
|
80
82
|
|
|
81
|
-
def join_headers2: (_Each[[String, String]] headers) -> void
|
|
83
|
+
def join_headers2: (Request request, _Each[[String, String]] headers) -> void
|
|
82
84
|
|
|
83
85
|
def join_body: (Request request) -> void
|
|
84
86
|
|
data/sig/connection/http2.rbs
CHANGED
|
@@ -76,7 +76,7 @@ module HTTPX
|
|
|
76
76
|
|
|
77
77
|
def on_stream_headers: (::HTTP2::Stream stream, Request request, Array[[String, String]] headers) -> void
|
|
78
78
|
|
|
79
|
-
def on_stream_trailers: (::HTTP2::Stream stream, Response response, Array[[String, String]] headers) -> void
|
|
79
|
+
def on_stream_trailers: (::HTTP2::Stream stream, Request request, Response response, Array[[String, String]] headers) -> void
|
|
80
80
|
|
|
81
81
|
def on_stream_data: (::HTTP2::Stream stream, Request request, String data) -> void
|
|
82
82
|
|
data/sig/loggable.rbs
CHANGED
data/sig/plugins/auth/basic.rbs
CHANGED
data/sig/plugins/auth/digest.rbs
CHANGED
data/sig/plugins/auth/ntlm.rbs
CHANGED
data/sig/plugins/proxy.rbs
CHANGED
data/sig/plugins/tracing.rbs
CHANGED
data/sig/request.rbs
CHANGED
|
@@ -19,6 +19,7 @@ module HTTPX
|
|
|
19
19
|
attr_reader active_timeouts: Array[Symbol]
|
|
20
20
|
|
|
21
21
|
attr_writer connection: Connection?
|
|
22
|
+
attr_writer on_response_arrived: (^() -> void)?
|
|
22
23
|
attr_accessor peer_address: (String | IPAddr)?
|
|
23
24
|
|
|
24
25
|
attr_writer persistent: bool
|
|
@@ -84,6 +85,8 @@ module HTTPX
|
|
|
84
85
|
|
|
85
86
|
def handle_error: (StandardError error) -> void
|
|
86
87
|
|
|
88
|
+
def emit_response: (response response) -> void
|
|
89
|
+
|
|
87
90
|
private
|
|
88
91
|
|
|
89
92
|
def initialize_body: (Options options) -> Transcoder::_Encoder?
|
data/sig/session.rbs
CHANGED
|
@@ -62,8 +62,6 @@ module HTTPX
|
|
|
62
62
|
|
|
63
63
|
def send_requests: (*Request) -> Array[response]
|
|
64
64
|
|
|
65
|
-
def _send_requests: (Array[Request] requests, Selector selector) -> void
|
|
66
|
-
|
|
67
65
|
def receive_requests: (Array[Request] requests, Selector selector) -> Array[response]
|
|
68
66
|
|
|
69
67
|
def early_resolve: (resolver resolver, Connection connection) -> bool
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: httpx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.7.
|
|
4
|
+
version: 1.7.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tiago Cardoso
|
|
@@ -166,6 +166,7 @@ extra_rdoc_files:
|
|
|
166
166
|
- doc/release_notes/1_7_4.md
|
|
167
167
|
- doc/release_notes/1_7_5.md
|
|
168
168
|
- doc/release_notes/1_7_6.md
|
|
169
|
+
- doc/release_notes/1_7_7.md
|
|
169
170
|
files:
|
|
170
171
|
- LICENSE.txt
|
|
171
172
|
- README.md
|
|
@@ -304,6 +305,7 @@ files:
|
|
|
304
305
|
- doc/release_notes/1_7_4.md
|
|
305
306
|
- doc/release_notes/1_7_5.md
|
|
306
307
|
- doc/release_notes/1_7_6.md
|
|
308
|
+
- doc/release_notes/1_7_7.md
|
|
307
309
|
- lib/httpx.rb
|
|
308
310
|
- lib/httpx/adapters/datadog.rb
|
|
309
311
|
- lib/httpx/adapters/faraday.rb
|