httpx 1.6.2 → 1.6.3
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_6_3.md +47 -0
- data/lib/httpx/adapters/sentry.rb +1 -1
- data/lib/httpx/connection/http1.rb +9 -9
- data/lib/httpx/connection/http2.rb +14 -15
- data/lib/httpx/connection.rb +115 -102
- data/lib/httpx/extensions.rb +0 -14
- data/lib/httpx/io/ssl.rb +1 -1
- data/lib/httpx/loggable.rb +12 -2
- data/lib/httpx/options.rb +20 -0
- data/lib/httpx/plugins/callbacks.rb +15 -1
- data/lib/httpx/plugins/digest_auth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +37 -9
- data/lib/httpx/plugins/response_cache/file_store.rb +1 -0
- data/lib/httpx/plugins/response_cache.rb +13 -2
- data/lib/httpx/plugins/stream_bidi.rb +15 -6
- data/lib/httpx/pool.rb +53 -19
- data/lib/httpx/request.rb +3 -13
- data/lib/httpx/resolver/https.rb +35 -19
- data/lib/httpx/resolver/multi.rb +8 -27
- data/lib/httpx/resolver/native.rb +46 -38
- data/lib/httpx/resolver/resolver.rb +45 -28
- data/lib/httpx/resolver/system.rb +63 -39
- data/lib/httpx/selector.rb +35 -20
- data/lib/httpx/session.rb +18 -28
- data/lib/httpx/transcoder/deflate.rb +13 -8
- data/lib/httpx/transcoder/utils/body_reader.rb +1 -2
- data/lib/httpx/transcoder/utils/deflater.rb +1 -2
- data/lib/httpx/version.rb +1 -1
- data/sig/connection.rbs +12 -3
- data/sig/loggable.rbs +5 -1
- data/sig/options.rbs +5 -1
- data/sig/plugins/callbacks.rbs +3 -0
- data/sig/plugins/stream_bidi.rbs +3 -5
- data/sig/resolver/https.rbs +2 -0
- data/sig/resolver/multi.rbs +0 -9
- data/sig/resolver/native.rbs +0 -2
- data/sig/resolver/resolver.rbs +9 -8
- data/sig/resolver/system.rbs +4 -2
- data/sig/selector.rbs +2 -0
- data/sig/session.rbs +5 -3
- 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: f49b29ea3703f6f40abe3cd82d455235b0a0b50a694bd8fa55839ac471d32bbb
|
|
4
|
+
data.tar.gz: ce933bb3c35d9434f810d4fc83acb244a1ad0db187123d5f01dcbbbd83622a47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 258cb32129840347a1a37633fb6273133187c98636b63ffcc0b8064f39e3b5642d693ee5da5d7776529625fbe4b68b5193b405c2e0658b2c9b1193b49e316bdc
|
|
7
|
+
data.tar.gz: 387397ab1954b6abf6a8a54d8764fb25e786fb1bd293401e8afd57f823464f67210527fee0b9e94b34cc3d2931143a5d94598fb6241f5728df77a8f1e045a5bf
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 1.6.3
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
* allow redacting only headers, or only the body, when using `debug_redact: :headers` or `debug_redact: :body` respectively.
|
|
6
|
+
|
|
7
|
+
## Improvements
|
|
8
|
+
|
|
9
|
+
* `system` resolver now works in a non-blocking manner, initiating the dns query in a separate thread and waiting on the pipe after that (it was blocking the main thread during resolution before).
|
|
10
|
+
* reduce allocation to a sinfle shared option object when headers are passed as a session-level option, like `HTTPX.with(headers: geaders).get(...)`
|
|
11
|
+
* privilege using `String#replace` in buffer operations (instead of "clean-then-append").
|
|
12
|
+
* using `Array#unshift` instead of `Array#concat` in order to ensure that request ordering is respected in the face of an in-between error which requires reconnect-and-resend.
|
|
13
|
+
* replaced more internal callback indirection with plain method calls.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Bugfixes
|
|
17
|
+
|
|
18
|
+
* https: prevent modification of the ssl context object when performing a reconnection.
|
|
19
|
+
* compression: do not return early if the decompression buffer yields an empty string (more frequent under jruby 10).
|
|
20
|
+
* response cache: take query params into account when caching or retrieving cached responses.
|
|
21
|
+
* response cache: do not decompress cached responses on body consumption (the response bodies are cached in plaintext).
|
|
22
|
+
* native resolver: pick next timeout associated with the hostname being resolved (and not the hostnames in the queue).
|
|
23
|
+
* pool: assume that, even when signalled that a connection is available, context may be switched to a session which also checks the same connection out, before it's able to pick it up; in such a case, start from the beginning, until the pool timeout expires.
|
|
24
|
+
* session: forego bookkeeping when a connection is coalesced (instead, allow it to be dropped).
|
|
25
|
+
* digest_auth: make sure that an array is sent back if the probe response fails.
|
|
26
|
+
* alt-svc: when alt-svc handshake happens with more in-flight requests, defer termination to when these requests are made.
|
|
27
|
+
* http2: fix use of unexisting var `ex` when processing the connection closed callback.
|
|
28
|
+
* connection: fix potential session dereferencing, which allowed connections to be used across sessions, therefore bypassing needed synchronization and leading to the `undefined method 'after' for nil:NilClass` error.
|
|
29
|
+
* selector: close only selected connections (instead of all selectable connections) when an error occurs during IO readiness wait calls.
|
|
30
|
+
* resolvers: correctly propagate abrupt termination errors to the connection objects waiting for the answer.
|
|
31
|
+
* resolvers: when errors happenm force-close unresolved connections (and ensure they're both pinned to the corresponding session before the error happens, and are unpinned after error is propagated).
|
|
32
|
+
* resolvers: ensure resolvers transition to "closed" state, on all cases, when any error happens.
|
|
33
|
+
* resolvers: ensure that the next hostname is resolved when a timeout happens on the current one.
|
|
34
|
+
* native resolver: fixed duplication of the hostname to resolve in the list of candidates.
|
|
35
|
+
* https resolver: use a `system` resolver to resolve the DoH server hostname (instead of rerouting it to itself).
|
|
36
|
+
* https resolver: skip loop error reporting when error happens outside of it.
|
|
37
|
+
* https resolver: close connection on resolve errors, which prevents it from being around in the pool after termination; also deactivate it after successful use.
|
|
38
|
+
* multi resolver: do not check resolvers back into the pool if it's a multi resolver and the peer is still resolving (and do the check outside of the critical area).
|
|
39
|
+
* sentry adapter: removed usage of deprecated method which has been removed in sentry-ruby 6.0.0.
|
|
40
|
+
* selector: when coalescing connections, pin the current session before merging connections, to prevent it from registering in a selector being used in a different thread, and inadvertedly allowing it to be used across threads.
|
|
41
|
+
* session: fix: always pin connection before early-or-lazy resolution (fixes connection pool accounting under connection coalescing).
|
|
42
|
+
|
|
43
|
+
## Chores
|
|
44
|
+
|
|
45
|
+
* logging emits a timestamp as well (to monitor timeouts).
|
|
46
|
+
* `:stream_bidi` plugin: extends HTTP2 module by using plugin extensions.
|
|
47
|
+
* connection: remove session/selector references when closing a connection (prevents leaking them beyond the usage scope).
|
|
@@ -32,7 +32,7 @@ module HTTPX::Plugins
|
|
|
32
32
|
|
|
33
33
|
return unless config.propagate_traces && config.trace_propagation_targets.any? { |target| url.match?(target) }
|
|
34
34
|
|
|
35
|
-
trace =
|
|
35
|
+
trace = sentry_span.to_sentry_trace
|
|
36
36
|
request.headers[::Sentry::SENTRY_TRACE_HEADER_NAME] = trace if trace
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -44,12 +44,12 @@ module HTTPX
|
|
|
44
44
|
@max_requests = @options.max_requests || MAX_REQUESTS
|
|
45
45
|
@parser.reset!
|
|
46
46
|
@handshake_completed = false
|
|
47
|
-
@pending.
|
|
47
|
+
@pending.unshift(*@requests)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def close
|
|
51
51
|
reset
|
|
52
|
-
emit(:close
|
|
52
|
+
emit(:close)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def exhausted?
|
|
@@ -114,7 +114,7 @@ module HTTPX
|
|
|
114
114
|
@parser.http_version.join("."),
|
|
115
115
|
headers)
|
|
116
116
|
log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
|
|
117
|
-
log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{
|
|
117
|
+
log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
|
|
118
118
|
|
|
119
119
|
@request.response = response
|
|
120
120
|
on_complete if response.finished?
|
|
@@ -126,7 +126,7 @@ module HTTPX
|
|
|
126
126
|
response = @request.response
|
|
127
127
|
log(level: 2) { "trailer headers received" }
|
|
128
128
|
|
|
129
|
-
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{
|
|
129
|
+
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
|
|
130
130
|
response.merge_headers(h)
|
|
131
131
|
end
|
|
132
132
|
|
|
@@ -136,7 +136,7 @@ module HTTPX
|
|
|
136
136
|
return unless request
|
|
137
137
|
|
|
138
138
|
log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
|
139
|
-
log(level: 2, color: :green) { "-> #{
|
|
139
|
+
log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
|
|
140
140
|
response = request.response
|
|
141
141
|
|
|
142
142
|
response << chunk
|
|
@@ -182,7 +182,7 @@ module HTTPX
|
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
if exhausted?
|
|
185
|
-
@pending.
|
|
185
|
+
@pending.unshift(*@requests)
|
|
186
186
|
@requests.clear
|
|
187
187
|
|
|
188
188
|
emit(:exhausted)
|
|
@@ -236,7 +236,7 @@ module HTTPX
|
|
|
236
236
|
when /keep-alive/i
|
|
237
237
|
if @handshake_completed
|
|
238
238
|
if @max_requests.zero?
|
|
239
|
-
@pending.
|
|
239
|
+
@pending.unshift(*@requests)
|
|
240
240
|
@requests.clear
|
|
241
241
|
emit(:exhausted)
|
|
242
242
|
end
|
|
@@ -360,7 +360,7 @@ module HTTPX
|
|
|
360
360
|
|
|
361
361
|
while (chunk = request.drain_body)
|
|
362
362
|
log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
|
363
|
-
log(level: 2, color: :green) { "<- #{
|
|
363
|
+
log(level: 2, color: :green) { "<- #{log_redact_body(chunk.inspect)}" }
|
|
364
364
|
@buffer << chunk
|
|
365
365
|
throw(:buffer_full, request) if @buffer.full?
|
|
366
366
|
end
|
|
@@ -381,7 +381,7 @@ module HTTPX
|
|
|
381
381
|
def join_headers2(headers)
|
|
382
382
|
headers.each do |field, value|
|
|
383
383
|
field = capitalized(field)
|
|
384
|
-
log(color: :yellow) { "<- HEADER: #{[field,
|
|
384
|
+
log(color: :yellow) { "<- HEADER: #{[field, log_redact_headers(value)].join(": ")}" }
|
|
385
385
|
@buffer << "#{field}: #{value}#{CRLF}"
|
|
386
386
|
end
|
|
387
387
|
end
|
|
@@ -89,7 +89,7 @@ module HTTPX
|
|
|
89
89
|
@connection.goaway
|
|
90
90
|
emit(:timeout, @options.timeout[:close_handshake_timeout])
|
|
91
91
|
end
|
|
92
|
-
emit(:close
|
|
92
|
+
emit(:close)
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def empty?
|
|
@@ -234,12 +234,12 @@ module HTTPX
|
|
|
234
234
|
extra_headers = set_protocol_headers(request)
|
|
235
235
|
|
|
236
236
|
if request.headers.key?("host")
|
|
237
|
-
log { "forbidden \"host\" header found (#{
|
|
237
|
+
log { "forbidden \"host\" header found (#{log_redact_headers(request.headers["host"])}), will use it as authority..." }
|
|
238
238
|
extra_headers[":authority"] = request.headers["host"]
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
log(level: 1, color: :yellow) do
|
|
242
|
-
"\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{
|
|
242
|
+
"\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")}"
|
|
243
243
|
end
|
|
244
244
|
stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
|
|
245
245
|
end
|
|
@@ -251,7 +251,7 @@ module HTTPX
|
|
|
251
251
|
end
|
|
252
252
|
|
|
253
253
|
log(level: 1, color: :yellow) do
|
|
254
|
-
request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{
|
|
254
|
+
request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
255
255
|
end
|
|
256
256
|
stream.headers(request.trailers.each, end_stream: true)
|
|
257
257
|
end
|
|
@@ -279,7 +279,7 @@ module HTTPX
|
|
|
279
279
|
|
|
280
280
|
def send_chunk(request, stream, chunk, next_chunk)
|
|
281
281
|
log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
|
|
282
|
-
log(level: 2, color: :green) { "#{stream.id}: -> #{
|
|
282
|
+
log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact_body(chunk.inspect)}" }
|
|
283
283
|
stream.data(chunk, end_stream: end_stream?(request, next_chunk))
|
|
284
284
|
end
|
|
285
285
|
|
|
@@ -300,7 +300,7 @@ module HTTPX
|
|
|
300
300
|
end
|
|
301
301
|
|
|
302
302
|
log(color: :yellow) do
|
|
303
|
-
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{
|
|
303
|
+
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
304
304
|
end
|
|
305
305
|
_, status = h.shift
|
|
306
306
|
headers = request.options.headers_class.new(h)
|
|
@@ -313,14 +313,14 @@ module HTTPX
|
|
|
313
313
|
|
|
314
314
|
def on_stream_trailers(stream, response, h)
|
|
315
315
|
log(color: :yellow) do
|
|
316
|
-
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{
|
|
316
|
+
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
317
317
|
end
|
|
318
318
|
response.merge_headers(h)
|
|
319
319
|
end
|
|
320
320
|
|
|
321
321
|
def on_stream_data(stream, request, data)
|
|
322
322
|
log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
|
|
323
|
-
log(level: 2, color: :green) { "#{stream.id}: <- #{
|
|
323
|
+
log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
|
|
324
324
|
request.response << data
|
|
325
325
|
end
|
|
326
326
|
|
|
@@ -387,18 +387,17 @@ module HTTPX
|
|
|
387
387
|
end
|
|
388
388
|
else
|
|
389
389
|
ex = GoawayError.new(error)
|
|
390
|
+
ex.set_backtrace(caller)
|
|
391
|
+
|
|
390
392
|
@pending.unshift(*@streams.keys)
|
|
391
393
|
teardown
|
|
392
|
-
end
|
|
393
394
|
|
|
394
|
-
if ex
|
|
395
|
-
ex.set_backtrace(caller)
|
|
396
395
|
handle_error(ex)
|
|
397
396
|
end
|
|
398
397
|
end
|
|
399
398
|
return unless is_connection_closed && @streams.empty?
|
|
400
399
|
|
|
401
|
-
emit(:close
|
|
400
|
+
emit(:close) if is_connection_closed
|
|
402
401
|
end
|
|
403
402
|
|
|
404
403
|
def on_frame_sent(frame)
|
|
@@ -409,7 +408,7 @@ module HTTPX
|
|
|
409
408
|
when :data
|
|
410
409
|
frame.merge(payload: frame[:payload].bytesize)
|
|
411
410
|
when :headers, :ping
|
|
412
|
-
frame.merge(payload:
|
|
411
|
+
frame.merge(payload: log_redact_headers(frame[:payload]))
|
|
413
412
|
else
|
|
414
413
|
frame
|
|
415
414
|
end
|
|
@@ -425,7 +424,7 @@ module HTTPX
|
|
|
425
424
|
when :data
|
|
426
425
|
frame.merge(payload: frame[:payload].bytesize)
|
|
427
426
|
when :headers, :ping
|
|
428
|
-
frame.merge(payload:
|
|
427
|
+
frame.merge(payload: log_redact_headers(frame[:payload]))
|
|
429
428
|
else
|
|
430
429
|
frame
|
|
431
430
|
end
|
|
@@ -435,7 +434,7 @@ module HTTPX
|
|
|
435
434
|
|
|
436
435
|
def on_altsvc(origin, frame)
|
|
437
436
|
log(level: 2) { "#{frame[:stream]}: altsvc frame was received" }
|
|
438
|
-
log(level: 2) { "#{frame[:stream]}: #{
|
|
437
|
+
log(level: 2) { "#{frame[:stream]}: #{log_redact_headers(frame.inspect)}" }
|
|
439
438
|
alt_origin = URI.parse("#{frame[:proto]}://#{frame[:host]}:#{frame[:port]}")
|
|
440
439
|
params = { "ma" => frame[:max_age] }
|
|
441
440
|
emit(:altsvc, origin, alt_origin, origin, params)
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -34,8 +34,6 @@ module HTTPX
|
|
|
34
34
|
|
|
35
35
|
using URIExtensions
|
|
36
36
|
|
|
37
|
-
def_delegator :@io, :closed?
|
|
38
|
-
|
|
39
37
|
def_delegator :@write_buffer, :empty?
|
|
40
38
|
|
|
41
39
|
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
|
|
@@ -48,9 +46,9 @@ module HTTPX
|
|
|
48
46
|
|
|
49
47
|
def initialize(uri, options)
|
|
50
48
|
@current_session = @current_selector =
|
|
51
|
-
@parser = @sibling = @coalesced_connection =
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
@parser = @sibling = @coalesced_connection = @altsvc_connection =
|
|
50
|
+
@family = @io = @ssl_session = @timeout =
|
|
51
|
+
@connected_at = @response_received_at = nil
|
|
54
52
|
|
|
55
53
|
@exhausted = @cloned = @main_sibling = false
|
|
56
54
|
|
|
@@ -65,7 +63,6 @@ module HTTPX
|
|
|
65
63
|
@inflight = 0
|
|
66
64
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
|
67
65
|
|
|
68
|
-
on(:error, &method(:on_error))
|
|
69
66
|
if @options.io
|
|
70
67
|
# if there's an already open IO, get its
|
|
71
68
|
# peer address, and force-initiate the parser
|
|
@@ -75,32 +72,6 @@ module HTTPX
|
|
|
75
72
|
else
|
|
76
73
|
transition(:idle)
|
|
77
74
|
end
|
|
78
|
-
on(:close) do
|
|
79
|
-
next if @exhausted # it'll reset
|
|
80
|
-
|
|
81
|
-
# may be called after ":close" above, so after the connection has been checked back in.
|
|
82
|
-
# next unless @current_session
|
|
83
|
-
|
|
84
|
-
next unless @current_session
|
|
85
|
-
|
|
86
|
-
@current_session.deselect_connection(self, @current_selector, @cloned)
|
|
87
|
-
end
|
|
88
|
-
on(:terminate) do
|
|
89
|
-
next if @exhausted # it'll reset
|
|
90
|
-
|
|
91
|
-
current_session = @current_session
|
|
92
|
-
current_selector = @current_selector
|
|
93
|
-
|
|
94
|
-
# may be called after ":close" above, so after the connection has been checked back in.
|
|
95
|
-
next unless current_session && current_selector
|
|
96
|
-
|
|
97
|
-
current_session.deselect_connection(self, current_selector)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
on(:altsvc) do |alt_origin, origin, alt_params|
|
|
101
|
-
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
75
|
self.addresses = @options.addresses if @options.addresses
|
|
105
76
|
end
|
|
106
77
|
|
|
@@ -158,6 +129,10 @@ module HTTPX
|
|
|
158
129
|
connection.merge(self)
|
|
159
130
|
end
|
|
160
131
|
|
|
132
|
+
def coalesced?
|
|
133
|
+
@coalesced_connection
|
|
134
|
+
end
|
|
135
|
+
|
|
161
136
|
# coalescable connections need to be mergeable!
|
|
162
137
|
# but internally, #mergeable? is called before #coalescable?
|
|
163
138
|
def coalescable?(connection)
|
|
@@ -231,7 +206,7 @@ module HTTPX
|
|
|
231
206
|
|
|
232
207
|
nil
|
|
233
208
|
rescue StandardError => e
|
|
234
|
-
|
|
209
|
+
on_error(e)
|
|
235
210
|
nil
|
|
236
211
|
end
|
|
237
212
|
|
|
@@ -259,7 +234,9 @@ module HTTPX
|
|
|
259
234
|
nil
|
|
260
235
|
rescue StandardError => e
|
|
261
236
|
@write_buffer.clear
|
|
262
|
-
|
|
237
|
+
on_error(e)
|
|
238
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
239
|
+
force_close(true)
|
|
263
240
|
raise e
|
|
264
241
|
end
|
|
265
242
|
|
|
@@ -273,7 +250,7 @@ module HTTPX
|
|
|
273
250
|
case @state
|
|
274
251
|
when :idle
|
|
275
252
|
purge_after_closed
|
|
276
|
-
|
|
253
|
+
disconnect
|
|
277
254
|
when :closed
|
|
278
255
|
@connected_at = nil
|
|
279
256
|
end
|
|
@@ -281,6 +258,23 @@ module HTTPX
|
|
|
281
258
|
close
|
|
282
259
|
end
|
|
283
260
|
|
|
261
|
+
# bypasses state machine rules while setting the connection in the
|
|
262
|
+
# :closed state.
|
|
263
|
+
def force_close(delete_pending = false)
|
|
264
|
+
if delete_pending
|
|
265
|
+
@pending.clear
|
|
266
|
+
elsif (parser = @parser)
|
|
267
|
+
enqueue_pending_requests_from_parser(parser)
|
|
268
|
+
end
|
|
269
|
+
return if @state == :closed
|
|
270
|
+
|
|
271
|
+
@state = :closed
|
|
272
|
+
@write_buffer.clear
|
|
273
|
+
purge_after_closed
|
|
274
|
+
disconnect
|
|
275
|
+
emit(:force_closed, delete_pending)
|
|
276
|
+
end
|
|
277
|
+
|
|
284
278
|
# bypasses the state machine to force closing of connections still connecting.
|
|
285
279
|
# **only** used for Happy Eyeballs v2.
|
|
286
280
|
def force_reset(cloned = false)
|
|
@@ -368,18 +362,40 @@ module HTTPX
|
|
|
368
362
|
end
|
|
369
363
|
|
|
370
364
|
def handle_connect_error(error)
|
|
371
|
-
return
|
|
365
|
+
return on_error(error) unless @sibling && @sibling.connecting?
|
|
372
366
|
|
|
373
367
|
@sibling.merge(self)
|
|
374
368
|
|
|
375
369
|
force_reset(true)
|
|
376
370
|
end
|
|
377
371
|
|
|
372
|
+
# disconnects from the current session it's attached to
|
|
378
373
|
def disconnect
|
|
379
|
-
return
|
|
374
|
+
return if @exhausted # it'll reset
|
|
375
|
+
|
|
376
|
+
return unless (current_session = @current_session) && (current_selector = @current_selector)
|
|
380
377
|
|
|
381
|
-
emit(:close)
|
|
382
378
|
@current_session = @current_selector = nil
|
|
379
|
+
|
|
380
|
+
current_session.deselect_connection(self, current_selector, @cloned)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def on_error(error, request = nil)
|
|
384
|
+
if error.is_a?(OperationTimeoutError)
|
|
385
|
+
|
|
386
|
+
# inactive connections do not contribute to the select loop, therefore
|
|
387
|
+
# they should not fail due to such errors.
|
|
388
|
+
return if @state == :inactive
|
|
389
|
+
|
|
390
|
+
if @timeout
|
|
391
|
+
@timeout -= error.timeout
|
|
392
|
+
return unless @timeout <= 0
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
error = error.to_connection_error if connecting?
|
|
396
|
+
end
|
|
397
|
+
handle_error(error, request)
|
|
398
|
+
reset
|
|
383
399
|
end
|
|
384
400
|
|
|
385
401
|
# :nocov:
|
|
@@ -417,7 +433,11 @@ module HTTPX
|
|
|
417
433
|
# * the number of pending requests
|
|
418
434
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
419
435
|
if @pending.empty? && @inflight.zero? && @write_buffer.empty?
|
|
420
|
-
log(level: 3) { "NO MORE REQUESTS..." }
|
|
436
|
+
log(level: 3) { "NO MORE REQUESTS..." } if @parser && @parser.pending.any?
|
|
437
|
+
|
|
438
|
+
# terminate if an altsvc connection has been established
|
|
439
|
+
terminate if @altsvc_connection
|
|
440
|
+
|
|
421
441
|
return
|
|
422
442
|
end
|
|
423
443
|
|
|
@@ -462,7 +482,14 @@ module HTTPX
|
|
|
462
482
|
break if @state == :closing || @state == :closed
|
|
463
483
|
|
|
464
484
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
465
|
-
|
|
485
|
+
if @pending.empty? && @inflight.zero? && @write_buffer.empty? # rubocop:disable Style/Next
|
|
486
|
+
log(level: 3) { "NO MORE REQUESTS..." } if @parser && @parser.pending.any?
|
|
487
|
+
|
|
488
|
+
# terminate if an altsvc connection has been established
|
|
489
|
+
terminate if @altsvc_connection
|
|
490
|
+
|
|
491
|
+
return
|
|
492
|
+
end
|
|
466
493
|
end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
|
|
467
494
|
|
|
468
495
|
#
|
|
@@ -555,6 +582,17 @@ module HTTPX
|
|
|
555
582
|
request.ping!
|
|
556
583
|
end
|
|
557
584
|
|
|
585
|
+
def enqueue_pending_requests_from_parser(parser)
|
|
586
|
+
parser_pending_requests = parser.pending
|
|
587
|
+
|
|
588
|
+
return if parser_pending_requests.empty?
|
|
589
|
+
|
|
590
|
+
# the connection will be reused, so parser requests must come
|
|
591
|
+
# back to the pending list before the parser is reset.
|
|
592
|
+
@inflight -= parser_pending_requests.size
|
|
593
|
+
@pending.unshift(*parser_pending_requests)
|
|
594
|
+
end
|
|
595
|
+
|
|
558
596
|
def build_parser(protocol = @io.protocol)
|
|
559
597
|
parser = parser_type(protocol).new(@write_buffer, @options)
|
|
560
598
|
set_parser_callbacks(parser)
|
|
@@ -564,7 +602,7 @@ module HTTPX
|
|
|
564
602
|
def set_parser_callbacks(parser)
|
|
565
603
|
parser.on(:response) do |request, response|
|
|
566
604
|
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
|
567
|
-
|
|
605
|
+
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
568
606
|
end
|
|
569
607
|
@response_received_at = Utils.now
|
|
570
608
|
@inflight -= 1
|
|
@@ -572,7 +610,7 @@ module HTTPX
|
|
|
572
610
|
request.emit(:response, response)
|
|
573
611
|
end
|
|
574
612
|
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
|
575
|
-
|
|
613
|
+
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
576
614
|
end
|
|
577
615
|
|
|
578
616
|
parser.on(:pong, &method(:send_pending))
|
|
@@ -581,50 +619,31 @@ module HTTPX
|
|
|
581
619
|
request.emit(:promise, parser, stream)
|
|
582
620
|
end
|
|
583
621
|
parser.on(:exhausted) do
|
|
622
|
+
enqueue_pending_requests_from_parser(parser)
|
|
623
|
+
|
|
584
624
|
@exhausted = true
|
|
585
|
-
|
|
586
|
-
current_selector = @current_selector
|
|
587
|
-
begin
|
|
588
|
-
parser.close
|
|
589
|
-
@pending.concat(parser.pending)
|
|
590
|
-
ensure
|
|
591
|
-
@current_session = current_session
|
|
592
|
-
@current_selector = current_selector
|
|
593
|
-
end
|
|
625
|
+
parser.close
|
|
594
626
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
idling
|
|
598
|
-
@exhausted = false
|
|
599
|
-
when :closing
|
|
600
|
-
once(:closed) do
|
|
601
|
-
idling
|
|
602
|
-
@exhausted = false
|
|
603
|
-
end
|
|
604
|
-
end
|
|
627
|
+
idling
|
|
628
|
+
@exhausted = false
|
|
605
629
|
end
|
|
606
630
|
parser.on(:origin) do |origin|
|
|
607
631
|
@origins |= [origin]
|
|
608
632
|
end
|
|
609
|
-
parser.on(:close) do
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
emit(:terminate)
|
|
613
|
-
end
|
|
633
|
+
parser.on(:close) do
|
|
634
|
+
reset
|
|
635
|
+
disconnect
|
|
614
636
|
end
|
|
615
637
|
parser.on(:close_handshake) do
|
|
616
|
-
consume
|
|
638
|
+
consume unless @state == :closed
|
|
617
639
|
end
|
|
618
640
|
parser.on(:reset) do
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
current_selector = @current_selector
|
|
641
|
+
enqueue_pending_requests_from_parser(parser)
|
|
642
|
+
|
|
622
643
|
reset
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
@current_selector = current_selector
|
|
627
|
-
end
|
|
644
|
+
# :reset event only fired in http/1.1, so this guarantees
|
|
645
|
+
# that the connection will be closed here.
|
|
646
|
+
idling unless @pending.empty?
|
|
628
647
|
end
|
|
629
648
|
parser.on(:current_timeout) do
|
|
630
649
|
@current_timeout = @timeout = parser.timeout
|
|
@@ -674,16 +693,12 @@ module HTTPX
|
|
|
674
693
|
error = ConnectionError.new(e.message)
|
|
675
694
|
error.set_backtrace(e.backtrace)
|
|
676
695
|
handle_connect_error(error) if connecting?
|
|
677
|
-
|
|
678
|
-
purge_after_closed
|
|
679
|
-
disconnect
|
|
696
|
+
force_close
|
|
680
697
|
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
|
681
698
|
# connect errors, exit gracefully
|
|
682
699
|
handle_error(e)
|
|
683
700
|
handle_connect_error(e) if connecting?
|
|
684
|
-
|
|
685
|
-
purge_after_closed
|
|
686
|
-
disconnect
|
|
701
|
+
force_close
|
|
687
702
|
end
|
|
688
703
|
|
|
689
704
|
def handle_transition(nextstate)
|
|
@@ -711,6 +726,8 @@ module HTTPX
|
|
|
711
726
|
|
|
712
727
|
# do not deactivate connection in use
|
|
713
728
|
return if @inflight.positive? || @parser.waiting_for_ping?
|
|
729
|
+
|
|
730
|
+
disconnect
|
|
714
731
|
when :closing
|
|
715
732
|
return unless @state == :idle || @state == :open
|
|
716
733
|
|
|
@@ -785,6 +802,8 @@ module HTTPX
|
|
|
785
802
|
|
|
786
803
|
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
|
|
787
804
|
def build_altsvc_connection(alt_origin, origin, alt_params)
|
|
805
|
+
return if @altsvc_connection
|
|
806
|
+
|
|
788
807
|
# do not allow security downgrades on altsvc negotiation
|
|
789
808
|
return if @origin.scheme == "https" && alt_origin.scheme != "https"
|
|
790
809
|
|
|
@@ -802,10 +821,11 @@ module HTTPX
|
|
|
802
821
|
|
|
803
822
|
connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
|
|
804
823
|
|
|
805
|
-
|
|
824
|
+
@altsvc_connection = connection
|
|
825
|
+
|
|
826
|
+
log(level: 1) { "#{origin}: alt-svc connection##{connection.object_id} established to #{alt_origin}" }
|
|
806
827
|
|
|
807
828
|
connection.merge(self)
|
|
808
|
-
terminate
|
|
809
829
|
rescue UnsupportedSchemeError
|
|
810
830
|
altsvc["noop"] = true
|
|
811
831
|
nil
|
|
@@ -835,26 +855,8 @@ module HTTPX
|
|
|
835
855
|
end
|
|
836
856
|
end
|
|
837
857
|
|
|
838
|
-
def on_error(error, request = nil)
|
|
839
|
-
if error.is_a?(OperationTimeoutError)
|
|
840
|
-
|
|
841
|
-
# inactive connections do not contribute to the select loop, therefore
|
|
842
|
-
# they should not fail due to such errors.
|
|
843
|
-
return if @state == :inactive
|
|
844
|
-
|
|
845
|
-
if @timeout
|
|
846
|
-
@timeout -= error.timeout
|
|
847
|
-
return unless @timeout <= 0
|
|
848
|
-
end
|
|
849
|
-
|
|
850
|
-
error = error.to_connection_error if connecting?
|
|
851
|
-
end
|
|
852
|
-
handle_error(error, request)
|
|
853
|
-
reset
|
|
854
|
-
end
|
|
855
|
-
|
|
856
858
|
def handle_error(error, request = nil)
|
|
857
|
-
parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
|
|
859
|
+
parser.handle_error(error, request) if @parser && @parser.respond_to?(:handle_error)
|
|
858
860
|
while (req = @pending.shift)
|
|
859
861
|
next if request && req == request
|
|
860
862
|
|
|
@@ -929,6 +931,17 @@ module HTTPX
|
|
|
929
931
|
|
|
930
932
|
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
|
931
933
|
request.set_timeout_callback(start_event) do
|
|
934
|
+
unless @current_selector
|
|
935
|
+
raise Error, "request has been resend to an out-of-session connection, and this " \
|
|
936
|
+
"should never happen!!! Please report this error! " \
|
|
937
|
+
"(state:#{@state}, " \
|
|
938
|
+
"parser?:#{!!@parser}, " \
|
|
939
|
+
"bytes in write buffer?:#{!@write_buffer.empty?}, " \
|
|
940
|
+
"cloned?:#{@cloned}, " \
|
|
941
|
+
"sibling?:#{!!@sibling}, " \
|
|
942
|
+
"coalesced?:#{coalesced?})"
|
|
943
|
+
end
|
|
944
|
+
|
|
932
945
|
timer = @current_selector.after(timeout, callback)
|
|
933
946
|
request.active_timeouts << label
|
|
934
947
|
|
data/lib/httpx/extensions.rb
CHANGED
|
@@ -4,20 +4,6 @@ require "uri"
|
|
|
4
4
|
|
|
5
5
|
module HTTPX
|
|
6
6
|
module ArrayExtensions
|
|
7
|
-
module FilterMap
|
|
8
|
-
refine Array do
|
|
9
|
-
# Ruby 2.7 backport
|
|
10
|
-
def filter_map
|
|
11
|
-
return to_enum(:filter_map) unless block_given?
|
|
12
|
-
|
|
13
|
-
each_with_object([]) do |item, res|
|
|
14
|
-
processed = yield(item)
|
|
15
|
-
res << processed if processed
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end unless Array.method_defined?(:filter_map)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
7
|
module Intersect
|
|
22
8
|
refine Array do
|
|
23
9
|
# Ruby 3.1 backport
|
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -98,7 +98,7 @@ module HTTPX
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
101
|
-
if (hostname_is_ip = (@ip == @sni_hostname))
|
|
101
|
+
if (hostname_is_ip = (@ip == @sni_hostname)) && @ctx.verify_hostname
|
|
102
102
|
# IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
|
|
103
103
|
@sni_hostname = @ip.to_string
|
|
104
104
|
# IP addresses in SNI is not valid per RFC 6066, section 3.
|