httpx-patched 1.6.2.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 +7 -0
- data/LICENSE.txt +191 -0
- data/README.md +162 -0
- data/doc/release_notes/0_0_1.md +7 -0
- data/doc/release_notes/0_0_2.md +9 -0
- data/doc/release_notes/0_0_3.md +9 -0
- data/doc/release_notes/0_0_4.md +7 -0
- data/doc/release_notes/0_0_5.md +5 -0
- data/doc/release_notes/0_10_0.md +66 -0
- data/doc/release_notes/0_10_1.md +37 -0
- data/doc/release_notes/0_10_2.md +5 -0
- data/doc/release_notes/0_11_0.md +74 -0
- data/doc/release_notes/0_11_1.md +5 -0
- data/doc/release_notes/0_11_2.md +5 -0
- data/doc/release_notes/0_11_3.md +5 -0
- data/doc/release_notes/0_12_0.md +55 -0
- data/doc/release_notes/0_13_0.md +58 -0
- data/doc/release_notes/0_13_1.md +5 -0
- data/doc/release_notes/0_13_2.md +9 -0
- data/doc/release_notes/0_14_0.md +79 -0
- data/doc/release_notes/0_14_1.md +7 -0
- data/doc/release_notes/0_14_2.md +6 -0
- data/doc/release_notes/0_14_3.md +5 -0
- data/doc/release_notes/0_14_4.md +5 -0
- data/doc/release_notes/0_14_5.md +11 -0
- data/doc/release_notes/0_15_0.md +53 -0
- data/doc/release_notes/0_15_1.md +8 -0
- data/doc/release_notes/0_15_2.md +9 -0
- data/doc/release_notes/0_15_3.md +5 -0
- data/doc/release_notes/0_15_4.md +5 -0
- data/doc/release_notes/0_16_0.md +93 -0
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/doc/release_notes/0_18_0.md +69 -0
- data/doc/release_notes/0_18_1.md +12 -0
- data/doc/release_notes/0_18_2.md +10 -0
- data/doc/release_notes/0_18_3.md +7 -0
- data/doc/release_notes/0_18_4.md +14 -0
- data/doc/release_notes/0_18_5.md +10 -0
- data/doc/release_notes/0_18_6.md +5 -0
- data/doc/release_notes/0_18_7.md +5 -0
- data/doc/release_notes/0_19_0.md +39 -0
- data/doc/release_notes/0_19_1.md +5 -0
- data/doc/release_notes/0_19_2.md +7 -0
- data/doc/release_notes/0_19_3.md +6 -0
- data/doc/release_notes/0_19_4.md +14 -0
- data/doc/release_notes/0_19_5.md +13 -0
- data/doc/release_notes/0_19_6.md +5 -0
- data/doc/release_notes/0_19_7.md +5 -0
- data/doc/release_notes/0_19_8.md +5 -0
- data/doc/release_notes/0_1_0.md +9 -0
- data/doc/release_notes/0_20_0.md +36 -0
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/0_2_0.md +5 -0
- data/doc/release_notes/0_2_1.md +16 -0
- data/doc/release_notes/0_3_0.md +12 -0
- data/doc/release_notes/0_3_1.md +6 -0
- data/doc/release_notes/0_4_0.md +51 -0
- data/doc/release_notes/0_4_1.md +3 -0
- data/doc/release_notes/0_5_0.md +15 -0
- data/doc/release_notes/0_5_1.md +14 -0
- data/doc/release_notes/0_6_0.md +5 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_6_2.md +6 -0
- data/doc/release_notes/0_6_3.md +13 -0
- data/doc/release_notes/0_6_4.md +21 -0
- data/doc/release_notes/0_6_5.md +22 -0
- data/doc/release_notes/0_6_6.md +19 -0
- data/doc/release_notes/0_6_7.md +5 -0
- data/doc/release_notes/0_7_0.md +46 -0
- data/doc/release_notes/0_8_0.md +27 -0
- data/doc/release_notes/0_8_1.md +8 -0
- data/doc/release_notes/0_8_2.md +7 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/doc/release_notes/1_3_2.md +6 -0
- data/doc/release_notes/1_3_3.md +5 -0
- data/doc/release_notes/1_3_4.md +6 -0
- data/doc/release_notes/1_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/doc/release_notes/1_4_2.md +20 -0
- data/doc/release_notes/1_4_3.md +11 -0
- data/doc/release_notes/1_4_4.md +14 -0
- data/doc/release_notes/1_5_0.md +126 -0
- data/doc/release_notes/1_5_1.md +6 -0
- data/doc/release_notes/1_6_0.md +50 -0
- data/doc/release_notes/1_6_1.md +17 -0
- data/doc/release_notes/1_6_2.md +11 -0
- data/lib/httpx/adapters/datadog.rb +359 -0
- data/lib/httpx/adapters/faraday.rb +303 -0
- data/lib/httpx/adapters/sentry.rb +121 -0
- data/lib/httpx/adapters/webmock.rb +175 -0
- data/lib/httpx/altsvc.rb +163 -0
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +61 -0
- data/lib/httpx/callbacks.rb +35 -0
- data/lib/httpx/chainable.rb +106 -0
- data/lib/httpx/connection/http1.rb +399 -0
- data/lib/httpx/connection/http2.rb +468 -0
- data/lib/httpx/connection.rb +954 -0
- data/lib/httpx/domain_name.rb +145 -0
- data/lib/httpx/errors.rb +111 -0
- data/lib/httpx/extensions.rb +59 -0
- data/lib/httpx/headers.rb +176 -0
- data/lib/httpx/io/ssl.rb +163 -0
- data/lib/httpx/io/tcp.rb +239 -0
- data/lib/httpx/io/udp.rb +62 -0
- data/lib/httpx/io/unix.rb +71 -0
- data/lib/httpx/io.rb +11 -0
- data/lib/httpx/loggable.rb +56 -0
- data/lib/httpx/options.rb +463 -0
- data/lib/httpx/parser/http1.rb +186 -0
- data/lib/httpx/plugins/auth/basic.rb +20 -0
- data/lib/httpx/plugins/auth/digest.rb +102 -0
- data/lib/httpx/plugins/auth/ntlm.rb +35 -0
- data/lib/httpx/plugins/auth/socks5.rb +22 -0
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +111 -0
- data/lib/httpx/plugins/aws_sigv4.rb +239 -0
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +127 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +147 -0
- data/lib/httpx/plugins/content_digest.rb +204 -0
- data/lib/httpx/plugins/cookies/cookie.rb +174 -0
- data/lib/httpx/plugins/cookies/jar.rb +95 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +143 -0
- data/lib/httpx/plugins/cookies.rb +107 -0
- data/lib/httpx/plugins/digest_auth.rb +67 -0
- data/lib/httpx/plugins/expect.rb +120 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
- data/lib/httpx/plugins/follow_redirects.rb +233 -0
- data/lib/httpx/plugins/grpc/call.rb +63 -0
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +90 -0
- data/lib/httpx/plugins/grpc/message.rb +55 -0
- data/lib/httpx/plugins/grpc.rb +282 -0
- data/lib/httpx/plugins/h2c.rb +127 -0
- data/lib/httpx/plugins/internal_telemetry.rb +107 -0
- data/lib/httpx/plugins/ntlm_auth.rb +62 -0
- data/lib/httpx/plugins/oauth.rb +183 -0
- data/lib/httpx/plugins/persistent.rb +82 -0
- data/lib/httpx/plugins/proxy/http.rb +184 -0
- data/lib/httpx/plugins/proxy/socks4.rb +135 -0
- data/lib/httpx/plugins/proxy/socks5.rb +194 -0
- data/lib/httpx/plugins/proxy/ssh.rb +94 -0
- data/lib/httpx/plugins/proxy.rb +349 -0
- data/lib/httpx/plugins/push_promise.rb +81 -0
- data/lib/httpx/plugins/query.rb +35 -0
- data/lib/httpx/plugins/rate_limiter.rb +55 -0
- data/lib/httpx/plugins/response_cache/file_store.rb +140 -0
- data/lib/httpx/plugins/response_cache/store.rb +33 -0
- data/lib/httpx/plugins/response_cache.rb +333 -0
- data/lib/httpx/plugins/retries.rb +230 -0
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +183 -0
- data/lib/httpx/plugins/stream_bidi.rb +315 -0
- data/lib/httpx/plugins/upgrade/h2.rb +64 -0
- data/lib/httpx/plugins/upgrade.rb +86 -0
- data/lib/httpx/plugins/webdav.rb +86 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pmatch_extensions.rb +33 -0
- data/lib/httpx/pool.rb +190 -0
- data/lib/httpx/punycode.rb +22 -0
- data/lib/httpx/request/body.rb +158 -0
- data/lib/httpx/request.rb +328 -0
- data/lib/httpx/resolver/entry.rb +30 -0
- data/lib/httpx/resolver/https.rb +256 -0
- data/lib/httpx/resolver/multi.rb +102 -0
- data/lib/httpx/resolver/native.rb +547 -0
- data/lib/httpx/resolver/resolver.rb +173 -0
- data/lib/httpx/resolver/system.rb +255 -0
- data/lib/httpx/resolver.rb +189 -0
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +115 -0
- data/lib/httpx/response.rb +304 -0
- data/lib/httpx/selector.rb +282 -0
- data/lib/httpx/session.rb +612 -0
- data/lib/httpx/session_extensions.rb +30 -0
- data/lib/httpx/timers.rb +133 -0
- data/lib/httpx/transcoder/body.rb +43 -0
- data/lib/httpx/transcoder/chunker.rb +115 -0
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +68 -0
- data/lib/httpx/transcoder/gzip.rb +71 -0
- data/lib/httpx/transcoder/json.rb +71 -0
- data/lib/httpx/transcoder/multipart/decoder.rb +141 -0
- data/lib/httpx/transcoder/multipart/encoder.rb +120 -0
- data/lib/httpx/transcoder/multipart/mime_type_detector.rb +78 -0
- data/lib/httpx/transcoder/multipart/part.rb +35 -0
- data/lib/httpx/transcoder/multipart.rb +31 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +75 -0
- data/lib/httpx/transcoder.rb +91 -0
- data/lib/httpx/utils.rb +75 -0
- data/lib/httpx/version.rb +5 -0
- data/lib/httpx.rb +66 -0
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +27 -0
- data/sig/callbacks.rbs +15 -0
- data/sig/chainable.rbs +55 -0
- data/sig/connection/http1.rbs +85 -0
- data/sig/connection/http2.rbs +116 -0
- data/sig/connection.rbs +169 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +69 -0
- data/sig/headers.rbs +49 -0
- data/sig/httpx.rbs +27 -0
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +72 -0
- data/sig/io/udp.rbs +25 -0
- data/sig/io/unix.rbs +26 -0
- data/sig/io.rbs +3 -0
- data/sig/loggable.rbs +17 -0
- data/sig/options.rbs +202 -0
- data/sig/parser/http1.rbs +59 -0
- data/sig/plugins/auth/basic.rbs +17 -0
- data/sig/plugins/auth/digest.rbs +25 -0
- data/sig/plugins/auth/ntlm.rbs +20 -0
- data/sig/plugins/auth/socks5.rbs +18 -0
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/aws_sdk_authentication.rbs +43 -0
- data/sig/plugins/aws_sigv4.rbs +78 -0
- data/sig/plugins/basic_auth.rbs +15 -0
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +71 -0
- data/sig/plugins/compression.rbs +57 -0
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +55 -0
- data/sig/plugins/cookies/jar.rbs +26 -0
- data/sig/plugins/cookies/set_cookie_parser.rbs +22 -0
- data/sig/plugins/cookies.rbs +28 -0
- data/sig/plugins/digest_auth.rbs +21 -0
- data/sig/plugins/expect.rbs +15 -0
- data/sig/plugins/fiber_concurrency.rbs +51 -0
- data/sig/plugins/follow_redirects.rbs +47 -0
- data/sig/plugins/grpc/call.rbs +23 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +65 -0
- data/sig/plugins/h2c.rbs +27 -0
- data/sig/plugins/ntlm_auth.rbs +21 -0
- data/sig/plugins/oauth.rbs +68 -0
- data/sig/plugins/persistent.rbs +14 -0
- data/sig/plugins/proxy/http.rbs +30 -0
- data/sig/plugins/proxy/socks4.rbs +37 -0
- data/sig/plugins/proxy/socks5.rbs +49 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/proxy.rbs +70 -0
- data/sig/plugins/push_promise.rbs +23 -0
- data/sig/plugins/query.rbs +18 -0
- data/sig/plugins/rate_limiter.rbs +13 -0
- data/sig/plugins/response_cache/file_store.rbs +19 -0
- data/sig/plugins/response_cache/store.rbs +13 -0
- data/sig/plugins/response_cache.rbs +86 -0
- data/sig/plugins/retries.rbs +66 -0
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/stream.rbs +54 -0
- data/sig/plugins/stream_bidi.rbs +68 -0
- data/sig/plugins/upgrade/h2.rbs +9 -0
- data/sig/plugins/upgrade.rbs +29 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +51 -0
- data/sig/punycode.rbs +5 -0
- data/sig/request/body.rbs +34 -0
- data/sig/request.rbs +88 -0
- data/sig/resolver/entry.rbs +13 -0
- data/sig/resolver/https.rbs +45 -0
- data/sig/resolver/multi.rbs +32 -0
- data/sig/resolver/native.rbs +74 -0
- data/sig/resolver/resolver.rbs +64 -0
- data/sig/resolver/system.rbs +34 -0
- data/sig/resolver.rbs +48 -0
- data/sig/response/body.rbs +52 -0
- data/sig/response/buffer.rbs +23 -0
- data/sig/response.rbs +103 -0
- data/sig/selector.rbs +68 -0
- data/sig/session.rbs +104 -0
- data/sig/timers.rbs +54 -0
- data/sig/transcoder/body.rbs +24 -0
- data/sig/transcoder/chunker.rbs +49 -0
- data/sig/transcoder/deflate.rbs +12 -0
- data/sig/transcoder/form.rbs +34 -0
- data/sig/transcoder/gzip.rbs +27 -0
- data/sig/transcoder/json.rbs +28 -0
- data/sig/transcoder/multipart.rbs +103 -0
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +28 -0
- data/sig/transcoder.rbs +43 -0
- data/sig/utils.rbs +19 -0
- metadata +518 -0
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "resolv"
|
|
4
|
+
require "forwardable"
|
|
5
|
+
require "httpx/io"
|
|
6
|
+
require "httpx/buffer"
|
|
7
|
+
|
|
8
|
+
module HTTPX
|
|
9
|
+
# The Connection can be watched for IO events.
|
|
10
|
+
#
|
|
11
|
+
# It contains the +io+ object to read/write from, and knows what to do when it can.
|
|
12
|
+
#
|
|
13
|
+
# It defers connecting until absolutely necessary. Connection should be triggered from
|
|
14
|
+
# the IO selector (until then, any request will be queued).
|
|
15
|
+
#
|
|
16
|
+
# A connection boots up its parser after connection is established. All pending requests
|
|
17
|
+
# will be redirected there after connection.
|
|
18
|
+
#
|
|
19
|
+
# A connection can be prevented from closing by the parser, that is, if there are pending
|
|
20
|
+
# requests. This will signal that the connection was prematurely closed, due to a possible
|
|
21
|
+
# number of conditions:
|
|
22
|
+
#
|
|
23
|
+
# * Remote peer closed the connection ("Connection: close");
|
|
24
|
+
# * Remote peer doesn't support pipelining;
|
|
25
|
+
#
|
|
26
|
+
# A connection may also route requests for a different host for which the +io+ was connected
|
|
27
|
+
# to, provided that the IP is the same and the port and scheme as well. This will allow to
|
|
28
|
+
# share the same socket to send HTTP/2 requests to different hosts.
|
|
29
|
+
#
|
|
30
|
+
class Connection
|
|
31
|
+
extend Forwardable
|
|
32
|
+
include Loggable
|
|
33
|
+
include Callbacks
|
|
34
|
+
|
|
35
|
+
using URIExtensions
|
|
36
|
+
|
|
37
|
+
def_delegator :@io, :closed?
|
|
38
|
+
|
|
39
|
+
def_delegator :@write_buffer, :empty?
|
|
40
|
+
|
|
41
|
+
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
|
|
42
|
+
|
|
43
|
+
attr_writer :current_selector
|
|
44
|
+
|
|
45
|
+
attr_accessor :current_session, :family
|
|
46
|
+
|
|
47
|
+
protected :ssl_session, :sibling
|
|
48
|
+
|
|
49
|
+
def initialize(uri, options)
|
|
50
|
+
@current_session = @current_selector =
|
|
51
|
+
@parser = @sibling = @coalesced_connection =
|
|
52
|
+
@family = @io = @ssl_session = @timeout =
|
|
53
|
+
@connected_at = @response_received_at = nil
|
|
54
|
+
|
|
55
|
+
@exhausted = @cloned = @main_sibling = false
|
|
56
|
+
|
|
57
|
+
@options = Options.new(options)
|
|
58
|
+
@type = initialize_type(uri, @options)
|
|
59
|
+
@origins = [uri.origin]
|
|
60
|
+
@origin = Utils.to_uri(uri.origin)
|
|
61
|
+
@window_size = @options.window_size
|
|
62
|
+
@read_buffer = Buffer.new(@options.buffer_size)
|
|
63
|
+
@write_buffer = Buffer.new(@options.buffer_size)
|
|
64
|
+
@pending = []
|
|
65
|
+
@inflight = 0
|
|
66
|
+
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
|
67
|
+
|
|
68
|
+
on(:error, &method(:on_error))
|
|
69
|
+
if @options.io
|
|
70
|
+
# if there's an already open IO, get its
|
|
71
|
+
# peer address, and force-initiate the parser
|
|
72
|
+
transition(:already_open)
|
|
73
|
+
@io = build_socket
|
|
74
|
+
parser
|
|
75
|
+
else
|
|
76
|
+
transition(:idle)
|
|
77
|
+
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
|
+
self.addresses = @options.addresses if @options.addresses
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def peer
|
|
108
|
+
@origin
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# this is a semi-private method, to be used by the resolver
|
|
112
|
+
# to initiate the io object.
|
|
113
|
+
def addresses=(addrs)
|
|
114
|
+
if @io
|
|
115
|
+
@io.add_addresses(addrs)
|
|
116
|
+
else
|
|
117
|
+
@io = build_socket(addrs)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def addresses
|
|
122
|
+
@io && @io.addresses
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def addresses?
|
|
126
|
+
@io && @io.addresses?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def match?(uri, options)
|
|
130
|
+
return false if !used? && (@state == :closing || @state == :closed)
|
|
131
|
+
|
|
132
|
+
(
|
|
133
|
+
@origins.include?(uri.origin) &&
|
|
134
|
+
# if there is more than one origin to match, it means that this connection
|
|
135
|
+
# was the result of coalescing. To prevent blind trust in the case where the
|
|
136
|
+
# origin came from an ORIGIN frame, we're going to verify the hostname with the
|
|
137
|
+
# SSL certificate
|
|
138
|
+
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
|
|
139
|
+
) && @options == options
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def mergeable?(connection)
|
|
143
|
+
return false if @state == :closing || @state == :closed || !@io
|
|
144
|
+
|
|
145
|
+
return false unless connection.addresses
|
|
146
|
+
|
|
147
|
+
(
|
|
148
|
+
(open? && @origin == connection.origin) ||
|
|
149
|
+
!(@io.addresses & (connection.addresses || [])).empty?
|
|
150
|
+
) && @options == connection.options
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# coalesces +self+ into +connection+.
|
|
154
|
+
def coalesce!(connection)
|
|
155
|
+
@coalesced_connection = connection
|
|
156
|
+
|
|
157
|
+
close_sibling
|
|
158
|
+
connection.merge(self)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# coalescable connections need to be mergeable!
|
|
162
|
+
# but internally, #mergeable? is called before #coalescable?
|
|
163
|
+
def coalescable?(connection)
|
|
164
|
+
if @io.protocol == "h2" &&
|
|
165
|
+
@origin.scheme == "https" &&
|
|
166
|
+
connection.origin.scheme == "https" &&
|
|
167
|
+
@io.can_verify_peer?
|
|
168
|
+
@io.verify_hostname(connection.origin.host)
|
|
169
|
+
else
|
|
170
|
+
@origin == connection.origin
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def create_idle(options = {})
|
|
175
|
+
self.class.new(@origin, @options.merge(options))
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def merge(connection)
|
|
179
|
+
@origins |= connection.instance_variable_get(:@origins)
|
|
180
|
+
if @ssl_session.nil? && connection.ssl_session
|
|
181
|
+
@ssl_session = connection.ssl_session
|
|
182
|
+
@io.session_new_cb do |sess|
|
|
183
|
+
@ssl_session = sess
|
|
184
|
+
end if @io
|
|
185
|
+
end
|
|
186
|
+
connection.purge_pending do |req|
|
|
187
|
+
send(req)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def purge_pending(&block)
|
|
192
|
+
pendings = []
|
|
193
|
+
if @parser
|
|
194
|
+
@inflight -= @parser.pending.size
|
|
195
|
+
pendings << @parser.pending
|
|
196
|
+
end
|
|
197
|
+
pendings << @pending
|
|
198
|
+
pendings.each do |pending|
|
|
199
|
+
pending.reject!(&block)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def io_connected?
|
|
204
|
+
return @coalesced_connection.io_connected? if @coalesced_connection
|
|
205
|
+
|
|
206
|
+
@io && @io.state == :connected
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def connecting?
|
|
210
|
+
@state == :idle
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def inflight?
|
|
214
|
+
@parser && (
|
|
215
|
+
# parser may be dealing with other requests (possibly started from a different fiber)
|
|
216
|
+
!@parser.empty? ||
|
|
217
|
+
# connection may be doing connection termination handshake
|
|
218
|
+
!@write_buffer.empty?
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def interests
|
|
223
|
+
# connecting
|
|
224
|
+
if connecting?
|
|
225
|
+
connect
|
|
226
|
+
|
|
227
|
+
return @io.interests if connecting?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
return @parser.interests if @parser
|
|
231
|
+
|
|
232
|
+
nil
|
|
233
|
+
rescue StandardError => e
|
|
234
|
+
emit(:error, e)
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def to_io
|
|
239
|
+
@io.to_io
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def call
|
|
243
|
+
case @state
|
|
244
|
+
when :idle
|
|
245
|
+
connect
|
|
246
|
+
|
|
247
|
+
# when opening the tcp or ssl socket fails
|
|
248
|
+
return if @state == :closed
|
|
249
|
+
|
|
250
|
+
consume
|
|
251
|
+
when :closed
|
|
252
|
+
return
|
|
253
|
+
when :closing
|
|
254
|
+
consume
|
|
255
|
+
transition(:closed)
|
|
256
|
+
when :open
|
|
257
|
+
consume
|
|
258
|
+
end
|
|
259
|
+
nil
|
|
260
|
+
rescue StandardError => e
|
|
261
|
+
@write_buffer.clear
|
|
262
|
+
emit(:error, e)
|
|
263
|
+
raise e
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def close
|
|
267
|
+
transition(:active) if @state == :inactive
|
|
268
|
+
|
|
269
|
+
@parser.close if @parser
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def terminate
|
|
273
|
+
case @state
|
|
274
|
+
when :idle
|
|
275
|
+
purge_after_closed
|
|
276
|
+
emit(:terminate)
|
|
277
|
+
when :closed
|
|
278
|
+
@connected_at = nil
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
close
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# bypasses the state machine to force closing of connections still connecting.
|
|
285
|
+
# **only** used for Happy Eyeballs v2.
|
|
286
|
+
def force_reset(cloned = false)
|
|
287
|
+
@state = :closing
|
|
288
|
+
@cloned = cloned
|
|
289
|
+
transition(:closed)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def reset
|
|
293
|
+
return if @state == :closing || @state == :closed
|
|
294
|
+
|
|
295
|
+
transition(:closing)
|
|
296
|
+
|
|
297
|
+
transition(:closed)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def send(request)
|
|
301
|
+
return @coalesced_connection.send(request) if @coalesced_connection
|
|
302
|
+
|
|
303
|
+
if @parser && !@write_buffer.full?
|
|
304
|
+
if @response_received_at && @keep_alive_timeout &&
|
|
305
|
+
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
|
306
|
+
# when pushing a request into an existing connection, we have to check whether there
|
|
307
|
+
# is the possibility that the connection might have extended the keep alive timeout.
|
|
308
|
+
# for such cases, we want to ping for availability before deciding to shovel requests.
|
|
309
|
+
log(level: 3) { "keep alive timeout expired, pinging connection..." }
|
|
310
|
+
@pending << request
|
|
311
|
+
transition(:active) if @state == :inactive
|
|
312
|
+
parser.ping
|
|
313
|
+
request.ping!
|
|
314
|
+
return
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
send_request_to_parser(request)
|
|
318
|
+
else
|
|
319
|
+
@pending << request
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def timeout
|
|
324
|
+
return if @state == :closed || @state == :inactive
|
|
325
|
+
|
|
326
|
+
return @timeout if @timeout
|
|
327
|
+
|
|
328
|
+
return @options.timeout[:connect_timeout] if @state == :idle
|
|
329
|
+
|
|
330
|
+
@options.timeout[:operation_timeout]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def idling
|
|
334
|
+
purge_after_closed
|
|
335
|
+
@write_buffer.clear
|
|
336
|
+
transition(:idle)
|
|
337
|
+
@parser = nil if @parser
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def used?
|
|
341
|
+
@connected_at
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def deactivate
|
|
345
|
+
transition(:inactive)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def open?
|
|
349
|
+
@state == :open || @state == :inactive
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def handle_socket_timeout(interval)
|
|
353
|
+
error = OperationTimeoutError.new(interval, "timed out while waiting on select")
|
|
354
|
+
error.set_backtrace(caller)
|
|
355
|
+
on_error(error)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def sibling=(connection)
|
|
359
|
+
@sibling = connection
|
|
360
|
+
|
|
361
|
+
return unless connection
|
|
362
|
+
|
|
363
|
+
@main_sibling = connection.sibling.nil?
|
|
364
|
+
|
|
365
|
+
return unless @main_sibling
|
|
366
|
+
|
|
367
|
+
connection.sibling = self
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def handle_connect_error(error)
|
|
371
|
+
return handle_error(error) unless @sibling && @sibling.connecting?
|
|
372
|
+
|
|
373
|
+
@sibling.merge(self)
|
|
374
|
+
|
|
375
|
+
force_reset(true)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def disconnect
|
|
379
|
+
return unless @current_session && @current_selector
|
|
380
|
+
|
|
381
|
+
emit(:close)
|
|
382
|
+
@current_session = @current_selector = nil
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# :nocov:
|
|
386
|
+
def inspect
|
|
387
|
+
"#<#{self.class}:#{object_id} " \
|
|
388
|
+
"@origin=#{@origin} " \
|
|
389
|
+
"@state=#{@state} " \
|
|
390
|
+
"@pending=#{@pending.size} " \
|
|
391
|
+
"@io=#{@io}>"
|
|
392
|
+
end
|
|
393
|
+
# :nocov:
|
|
394
|
+
|
|
395
|
+
private
|
|
396
|
+
|
|
397
|
+
def connect
|
|
398
|
+
transition(:open)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def consume
|
|
402
|
+
return unless @io
|
|
403
|
+
|
|
404
|
+
catch(:called) do
|
|
405
|
+
epiped = false
|
|
406
|
+
loop do
|
|
407
|
+
# connection may have
|
|
408
|
+
return if @state == :idle
|
|
409
|
+
|
|
410
|
+
parser.consume
|
|
411
|
+
|
|
412
|
+
# we exit if there's no more requests to process
|
|
413
|
+
#
|
|
414
|
+
# this condition takes into account:
|
|
415
|
+
#
|
|
416
|
+
# * the number of inflight requests
|
|
417
|
+
# * the number of pending requests
|
|
418
|
+
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
419
|
+
if @pending.empty? && @inflight.zero? && @write_buffer.empty?
|
|
420
|
+
log(level: 3) { "NO MORE REQUESTS..." }
|
|
421
|
+
return
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
@timeout = @current_timeout
|
|
425
|
+
|
|
426
|
+
read_drained = false
|
|
427
|
+
write_drained = nil
|
|
428
|
+
|
|
429
|
+
#
|
|
430
|
+
# tight read loop.
|
|
431
|
+
#
|
|
432
|
+
# read as much of the socket as possible.
|
|
433
|
+
#
|
|
434
|
+
# this tight loop reads all the data it can from the socket and pipes it to
|
|
435
|
+
# its parser.
|
|
436
|
+
#
|
|
437
|
+
loop do
|
|
438
|
+
siz = @io.read(@window_size, @read_buffer)
|
|
439
|
+
log(level: 3, color: :cyan) { "IO READ: #{siz} bytes... (wsize: #{@window_size}, rbuffer: #{@read_buffer.bytesize})" }
|
|
440
|
+
unless siz
|
|
441
|
+
@write_buffer.clear
|
|
442
|
+
|
|
443
|
+
ex = EOFError.new("descriptor closed")
|
|
444
|
+
ex.set_backtrace(caller)
|
|
445
|
+
on_error(ex)
|
|
446
|
+
return
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# socket has been drained. mark and exit the read loop.
|
|
450
|
+
if siz.zero?
|
|
451
|
+
read_drained = @read_buffer.empty?
|
|
452
|
+
epiped = false
|
|
453
|
+
break
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
parser << @read_buffer.to_s
|
|
457
|
+
|
|
458
|
+
# continue reading if possible.
|
|
459
|
+
break if interests == :w && !epiped
|
|
460
|
+
|
|
461
|
+
# exit the read loop if connection is preparing to be closed
|
|
462
|
+
break if @state == :closing || @state == :closed
|
|
463
|
+
|
|
464
|
+
# exit #consume altogether if all outstanding requests have been dealt with
|
|
465
|
+
return if @pending.empty? && @inflight.zero?
|
|
466
|
+
end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
|
|
467
|
+
|
|
468
|
+
#
|
|
469
|
+
# tight write loop.
|
|
470
|
+
#
|
|
471
|
+
# flush as many bytes as the sockets allow.
|
|
472
|
+
#
|
|
473
|
+
loop do
|
|
474
|
+
# buffer has been drainned, mark and exit the write loop.
|
|
475
|
+
if @write_buffer.empty?
|
|
476
|
+
# we only mark as drained on the first loop
|
|
477
|
+
write_drained = write_drained.nil? && @inflight.positive?
|
|
478
|
+
|
|
479
|
+
break
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
begin
|
|
483
|
+
siz = @io.write(@write_buffer)
|
|
484
|
+
rescue Errno::EPIPE
|
|
485
|
+
# this can happen if we still have bytes in the buffer to send to the server, but
|
|
486
|
+
# the server wants to respond immediately with some message, or an error. An example is
|
|
487
|
+
# when one's uploading a big file to an unintended endpoint, and the server stops the
|
|
488
|
+
# consumption, and responds immediately with an authorization of even method not allowed error.
|
|
489
|
+
# at this point, we have to let the connection switch to read-mode.
|
|
490
|
+
log(level: 2) { "pipe broken, could not flush buffer..." }
|
|
491
|
+
epiped = true
|
|
492
|
+
read_drained = false
|
|
493
|
+
break
|
|
494
|
+
end
|
|
495
|
+
log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
|
|
496
|
+
unless siz
|
|
497
|
+
@write_buffer.clear
|
|
498
|
+
|
|
499
|
+
ex = EOFError.new("descriptor closed")
|
|
500
|
+
ex.set_backtrace(caller)
|
|
501
|
+
on_error(ex)
|
|
502
|
+
return
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# socket closed for writing. mark and exit the write loop.
|
|
506
|
+
if siz.zero?
|
|
507
|
+
write_drained = !@write_buffer.empty?
|
|
508
|
+
break
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# exit write loop if marked to consume from peer, or is closing.
|
|
512
|
+
break if interests == :r || @state == :closing || @state == :closed
|
|
513
|
+
|
|
514
|
+
write_drained = false
|
|
515
|
+
end unless (ints = interests) == :r
|
|
516
|
+
|
|
517
|
+
send_pending if @state == :open
|
|
518
|
+
|
|
519
|
+
# return if socket is drained
|
|
520
|
+
next unless (ints != :r || read_drained) && (ints != :w || write_drained)
|
|
521
|
+
|
|
522
|
+
# gotta go back to the event loop. It happens when:
|
|
523
|
+
#
|
|
524
|
+
# * the socket is drained of bytes or it's not the interest of the conn to read;
|
|
525
|
+
# * theres nothing more to write, or it's not in the interest of the conn to write;
|
|
526
|
+
log(level: 3) { "(#{ints}): WAITING FOR EVENTS..." }
|
|
527
|
+
return
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def send_pending
|
|
533
|
+
while !@write_buffer.full? && (request = @pending.shift)
|
|
534
|
+
send_request_to_parser(request)
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def parser
|
|
539
|
+
@parser ||= build_parser
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def send_request_to_parser(request)
|
|
543
|
+
@inflight += 1
|
|
544
|
+
request.peer_address = @io.ip && @io.ip.address
|
|
545
|
+
set_request_timeouts(request)
|
|
546
|
+
|
|
547
|
+
parser.send(request)
|
|
548
|
+
|
|
549
|
+
return unless @state == :inactive
|
|
550
|
+
|
|
551
|
+
transition(:active)
|
|
552
|
+
# mark request as ping, as this inactive connection may have been
|
|
553
|
+
# closed by the server, and we don't want that to influence retry
|
|
554
|
+
# bookkeeping.
|
|
555
|
+
request.ping!
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def build_parser(protocol = @io.protocol)
|
|
559
|
+
parser = parser_type(protocol).new(@write_buffer, @options)
|
|
560
|
+
set_parser_callbacks(parser)
|
|
561
|
+
parser
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def set_parser_callbacks(parser)
|
|
565
|
+
parser.on(:response) do |request, response|
|
|
566
|
+
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
|
567
|
+
emit(:altsvc, alt_origin, origin, alt_params)
|
|
568
|
+
end
|
|
569
|
+
@response_received_at = Utils.now
|
|
570
|
+
@inflight -= 1
|
|
571
|
+
response.finish!
|
|
572
|
+
request.emit(:response, response)
|
|
573
|
+
end
|
|
574
|
+
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
|
575
|
+
emit(:altsvc, alt_origin, origin, alt_params)
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
parser.on(:pong, &method(:send_pending))
|
|
579
|
+
|
|
580
|
+
parser.on(:promise) do |request, stream|
|
|
581
|
+
request.emit(:promise, parser, stream)
|
|
582
|
+
end
|
|
583
|
+
parser.on(:exhausted) do
|
|
584
|
+
@exhausted = true
|
|
585
|
+
current_session = @current_session
|
|
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
|
|
594
|
+
|
|
595
|
+
case @state
|
|
596
|
+
when :closed
|
|
597
|
+
idling
|
|
598
|
+
@exhausted = false
|
|
599
|
+
when :closing
|
|
600
|
+
once(:closed) do
|
|
601
|
+
idling
|
|
602
|
+
@exhausted = false
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
parser.on(:origin) do |origin|
|
|
607
|
+
@origins |= [origin]
|
|
608
|
+
end
|
|
609
|
+
parser.on(:close) do |force|
|
|
610
|
+
if force
|
|
611
|
+
reset
|
|
612
|
+
emit(:terminate)
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
parser.on(:close_handshake) do
|
|
616
|
+
consume
|
|
617
|
+
end
|
|
618
|
+
parser.on(:reset) do
|
|
619
|
+
@pending.concat(parser.pending) unless parser.empty?
|
|
620
|
+
current_session = @current_session
|
|
621
|
+
current_selector = @current_selector
|
|
622
|
+
reset
|
|
623
|
+
unless @pending.empty?
|
|
624
|
+
idling
|
|
625
|
+
@current_session = current_session
|
|
626
|
+
@current_selector = current_selector
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
parser.on(:current_timeout) do
|
|
630
|
+
@current_timeout = @timeout = parser.timeout
|
|
631
|
+
end
|
|
632
|
+
parser.on(:timeout) do |tout|
|
|
633
|
+
@timeout = tout
|
|
634
|
+
end
|
|
635
|
+
parser.on(:error) do |request, error|
|
|
636
|
+
case error
|
|
637
|
+
when :http_1_1_required
|
|
638
|
+
current_session = @current_session
|
|
639
|
+
current_selector = @current_selector
|
|
640
|
+
parser.close
|
|
641
|
+
|
|
642
|
+
other_connection = current_session.find_connection(@origin, current_selector,
|
|
643
|
+
@options.merge(ssl: { alpn_protocols: %w[http/1.1] }))
|
|
644
|
+
other_connection.merge(self)
|
|
645
|
+
request.transition(:idle)
|
|
646
|
+
other_connection.send(request)
|
|
647
|
+
next
|
|
648
|
+
when OperationTimeoutError
|
|
649
|
+
# request level timeouts should take precedence
|
|
650
|
+
next unless request.active_timeouts.empty?
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
@inflight -= 1
|
|
654
|
+
response = ErrorResponse.new(request, error)
|
|
655
|
+
request.response = response
|
|
656
|
+
request.emit(:response, response)
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def transition(nextstate)
|
|
661
|
+
handle_transition(nextstate)
|
|
662
|
+
rescue Errno::ECONNABORTED,
|
|
663
|
+
Errno::ECONNREFUSED,
|
|
664
|
+
Errno::ECONNRESET,
|
|
665
|
+
Errno::EADDRNOTAVAIL,
|
|
666
|
+
Errno::EHOSTUNREACH,
|
|
667
|
+
Errno::EINVAL,
|
|
668
|
+
Errno::ENETUNREACH,
|
|
669
|
+
Errno::EPIPE,
|
|
670
|
+
Errno::ENOENT,
|
|
671
|
+
SocketError,
|
|
672
|
+
IOError => e
|
|
673
|
+
# connect errors, exit gracefully
|
|
674
|
+
error = ConnectionError.new(e.message)
|
|
675
|
+
error.set_backtrace(e.backtrace)
|
|
676
|
+
handle_connect_error(error) if connecting?
|
|
677
|
+
@state = :closed
|
|
678
|
+
purge_after_closed
|
|
679
|
+
disconnect
|
|
680
|
+
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
|
681
|
+
# connect errors, exit gracefully
|
|
682
|
+
handle_error(e)
|
|
683
|
+
handle_connect_error(e) if connecting?
|
|
684
|
+
@state = :closed
|
|
685
|
+
purge_after_closed
|
|
686
|
+
disconnect
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def handle_transition(nextstate)
|
|
690
|
+
case nextstate
|
|
691
|
+
when :idle
|
|
692
|
+
@timeout = @current_timeout = @options.timeout[:connect_timeout]
|
|
693
|
+
|
|
694
|
+
@connected_at = @response_received_at = nil
|
|
695
|
+
when :open
|
|
696
|
+
return if @state == :closed
|
|
697
|
+
|
|
698
|
+
@io.connect
|
|
699
|
+
close_sibling if @io.state == :connected
|
|
700
|
+
|
|
701
|
+
return unless @io.connected?
|
|
702
|
+
|
|
703
|
+
@connected_at = Utils.now
|
|
704
|
+
|
|
705
|
+
send_pending
|
|
706
|
+
|
|
707
|
+
@timeout = @current_timeout = parser.timeout
|
|
708
|
+
emit(:open)
|
|
709
|
+
when :inactive
|
|
710
|
+
return unless @state == :open
|
|
711
|
+
|
|
712
|
+
# do not deactivate connection in use
|
|
713
|
+
return if @inflight.positive? || @parser.waiting_for_ping?
|
|
714
|
+
when :closing
|
|
715
|
+
return unless @state == :idle || @state == :open
|
|
716
|
+
|
|
717
|
+
unless @write_buffer.empty?
|
|
718
|
+
# preset state before handshake, as error callbacks
|
|
719
|
+
# may take it back here.
|
|
720
|
+
@state = nextstate
|
|
721
|
+
# handshakes, try sending
|
|
722
|
+
consume
|
|
723
|
+
@write_buffer.clear
|
|
724
|
+
return
|
|
725
|
+
end
|
|
726
|
+
when :closed
|
|
727
|
+
return unless @state == :closing
|
|
728
|
+
return unless @write_buffer.empty?
|
|
729
|
+
|
|
730
|
+
purge_after_closed
|
|
731
|
+
disconnect if @pending.empty?
|
|
732
|
+
|
|
733
|
+
when :already_open
|
|
734
|
+
nextstate = :open
|
|
735
|
+
# the first check for given io readiness must still use a timeout.
|
|
736
|
+
# connect is the reasonable choice in such a case.
|
|
737
|
+
@timeout = @options.timeout[:connect_timeout]
|
|
738
|
+
send_pending
|
|
739
|
+
when :active
|
|
740
|
+
return unless @state == :inactive
|
|
741
|
+
|
|
742
|
+
nextstate = :open
|
|
743
|
+
|
|
744
|
+
# activate
|
|
745
|
+
@current_session.select_connection(self, @current_selector)
|
|
746
|
+
end
|
|
747
|
+
log(level: 3) { "#{@state} -> #{nextstate}" }
|
|
748
|
+
@state = nextstate
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def close_sibling
|
|
752
|
+
return unless @sibling
|
|
753
|
+
|
|
754
|
+
if @sibling.io_connected?
|
|
755
|
+
reset
|
|
756
|
+
# TODO: transition connection to closed
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
unless @sibling.state == :closed
|
|
760
|
+
merge(@sibling) unless @main_sibling
|
|
761
|
+
@sibling.force_reset(true)
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
@sibling = nil
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def purge_after_closed
|
|
768
|
+
@io.close if @io
|
|
769
|
+
@read_buffer.clear
|
|
770
|
+
@timeout = nil
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
def initialize_type(uri, options)
|
|
774
|
+
options.transport || begin
|
|
775
|
+
case uri.scheme
|
|
776
|
+
when "http"
|
|
777
|
+
"tcp"
|
|
778
|
+
when "https"
|
|
779
|
+
"ssl"
|
|
780
|
+
else
|
|
781
|
+
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
|
|
787
|
+
def build_altsvc_connection(alt_origin, origin, alt_params)
|
|
788
|
+
# do not allow security downgrades on altsvc negotiation
|
|
789
|
+
return if @origin.scheme == "https" && alt_origin.scheme != "https"
|
|
790
|
+
|
|
791
|
+
altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
|
|
792
|
+
|
|
793
|
+
# altsvc already exists, somehow it wasn't advertised, probably noop
|
|
794
|
+
return unless altsvc
|
|
795
|
+
|
|
796
|
+
alt_options = @options.merge(ssl: @options.ssl.merge(hostname: URI(origin).host))
|
|
797
|
+
|
|
798
|
+
connection = @current_session.find_connection(alt_origin, @current_selector, alt_options)
|
|
799
|
+
|
|
800
|
+
# advertised altsvc is the same origin being used, ignore
|
|
801
|
+
return if connection == self
|
|
802
|
+
|
|
803
|
+
connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
|
|
804
|
+
|
|
805
|
+
log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
|
|
806
|
+
|
|
807
|
+
connection.merge(self)
|
|
808
|
+
terminate
|
|
809
|
+
rescue UnsupportedSchemeError
|
|
810
|
+
altsvc["noop"] = true
|
|
811
|
+
nil
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
def build_socket(addrs = nil)
|
|
815
|
+
case @type
|
|
816
|
+
when "tcp"
|
|
817
|
+
TCP.new(peer, addrs, @options)
|
|
818
|
+
when "ssl"
|
|
819
|
+
SSL.new(peer, addrs, @options) do |sock|
|
|
820
|
+
sock.ssl_session = @ssl_session
|
|
821
|
+
sock.session_new_cb do |sess|
|
|
822
|
+
@ssl_session = sess
|
|
823
|
+
|
|
824
|
+
sock.ssl_session = sess
|
|
825
|
+
end
|
|
826
|
+
end
|
|
827
|
+
when "unix"
|
|
828
|
+
path = Array(addrs).first
|
|
829
|
+
|
|
830
|
+
path = String(path) if path
|
|
831
|
+
|
|
832
|
+
UNIX.new(peer, path, @options)
|
|
833
|
+
else
|
|
834
|
+
raise Error, "unsupported transport (#{@type})"
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
|
|
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
|
+
def handle_error(error, request = nil)
|
|
857
|
+
parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
|
|
858
|
+
while (req = @pending.shift)
|
|
859
|
+
next if request && req == request
|
|
860
|
+
|
|
861
|
+
response = ErrorResponse.new(req, error)
|
|
862
|
+
req.response = response
|
|
863
|
+
req.emit(:response, response)
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
return unless request
|
|
867
|
+
|
|
868
|
+
@inflight -= 1
|
|
869
|
+
response = ErrorResponse.new(request, error)
|
|
870
|
+
request.response = response
|
|
871
|
+
request.emit(:response, response)
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
def set_request_timeouts(request)
|
|
875
|
+
set_request_write_timeout(request)
|
|
876
|
+
set_request_read_timeout(request)
|
|
877
|
+
set_request_request_timeout(request)
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
def set_request_read_timeout(request)
|
|
881
|
+
read_timeout = request.read_timeout
|
|
882
|
+
|
|
883
|
+
return if read_timeout.nil? || read_timeout.infinite?
|
|
884
|
+
|
|
885
|
+
set_request_timeout(:read_timeout, request, read_timeout, :done, :response) do
|
|
886
|
+
read_timeout_callback(request, read_timeout)
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
def set_request_write_timeout(request)
|
|
891
|
+
write_timeout = request.write_timeout
|
|
892
|
+
|
|
893
|
+
return if write_timeout.nil? || write_timeout.infinite?
|
|
894
|
+
|
|
895
|
+
set_request_timeout(:write_timeout, request, write_timeout, :headers, %i[done response]) do
|
|
896
|
+
write_timeout_callback(request, write_timeout)
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
def set_request_request_timeout(request)
|
|
901
|
+
request_timeout = request.request_timeout
|
|
902
|
+
|
|
903
|
+
return if request_timeout.nil? || request_timeout.infinite?
|
|
904
|
+
|
|
905
|
+
set_request_timeout(:request_timeout, request, request_timeout, :headers, :complete) do
|
|
906
|
+
read_timeout_callback(request, request_timeout, RequestTimeoutError)
|
|
907
|
+
end
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
def write_timeout_callback(request, write_timeout)
|
|
911
|
+
return if request.state == :done
|
|
912
|
+
|
|
913
|
+
@write_buffer.clear
|
|
914
|
+
error = WriteTimeoutError.new(request, nil, write_timeout)
|
|
915
|
+
|
|
916
|
+
on_error(error, request)
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
def read_timeout_callback(request, read_timeout, error_type = ReadTimeoutError)
|
|
920
|
+
response = request.response
|
|
921
|
+
|
|
922
|
+
return if response && response.finished?
|
|
923
|
+
|
|
924
|
+
@write_buffer.clear
|
|
925
|
+
error = error_type.new(request, request.response, read_timeout)
|
|
926
|
+
|
|
927
|
+
on_error(error, request)
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
|
|
931
|
+
request.set_timeout_callback(start_event) do
|
|
932
|
+
timer = @current_selector.after(timeout, callback)
|
|
933
|
+
request.active_timeouts << label
|
|
934
|
+
|
|
935
|
+
Array(finish_events).each do |event|
|
|
936
|
+
# clean up request timeouts if the connection errors out
|
|
937
|
+
request.set_timeout_callback(event) do
|
|
938
|
+
timer.cancel
|
|
939
|
+
request.active_timeouts.delete(label)
|
|
940
|
+
end
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
def parser_type(protocol)
|
|
946
|
+
case protocol
|
|
947
|
+
when "h2" then @options.http2_class
|
|
948
|
+
when "http/1.1" then @options.http1_class
|
|
949
|
+
else
|
|
950
|
+
raise Error, "unsupported protocol (##{protocol})"
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
end
|
|
954
|
+
end
|