httpx 0.20.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +0 -48
- data/README.md +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +5 -5
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- 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/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/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -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 +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +52 -0
- data/lib/httpx/transcoder.rb +5 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- 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 +7 -5
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +18 -4
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +7 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- data/sig/registry.rbs +0 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/connection.rb
CHANGED
@@ -29,7 +29,6 @@ module HTTPX
|
|
29
29
|
#
|
30
30
|
class Connection
|
31
31
|
extend Forwardable
|
32
|
-
include Registry
|
33
32
|
include Loggable
|
34
33
|
include Callbacks
|
35
34
|
|
@@ -38,31 +37,33 @@ module HTTPX
|
|
38
37
|
require "httpx/connection/http2"
|
39
38
|
require "httpx/connection/http1"
|
40
39
|
|
41
|
-
BUFFER_SIZE = 1 << 14
|
42
|
-
|
43
40
|
def_delegator :@io, :closed?
|
44
41
|
|
45
42
|
def_delegator :@write_buffer, :empty?
|
46
43
|
|
47
|
-
attr_reader :io, :origin, :origins, :state, :pending, :options
|
44
|
+
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
|
48
45
|
|
49
46
|
attr_writer :timers
|
50
47
|
|
51
|
-
|
52
|
-
|
48
|
+
attr_accessor :family
|
49
|
+
|
50
|
+
def initialize(uri, options)
|
53
51
|
@origins = [uri.origin]
|
54
52
|
@origin = Utils.to_uri(uri.origin)
|
55
53
|
@options = Options.new(options)
|
54
|
+
@type = initialize_type(uri, @options)
|
55
|
+
@origins = [uri.origin]
|
56
|
+
@origin = Utils.to_uri(uri.origin)
|
56
57
|
@window_size = @options.window_size
|
57
|
-
@read_buffer = Buffer.new(
|
58
|
-
@write_buffer = Buffer.new(
|
58
|
+
@read_buffer = Buffer.new(@options.buffer_size)
|
59
|
+
@write_buffer = Buffer.new(@options.buffer_size)
|
59
60
|
@pending = []
|
60
61
|
on(:error, &method(:on_error))
|
61
62
|
if @options.io
|
62
63
|
# if there's an already open IO, get its
|
63
64
|
# peer address, and force-initiate the parser
|
64
65
|
transition(:already_open)
|
65
|
-
@io =
|
66
|
+
@io = build_socket
|
66
67
|
parser
|
67
68
|
else
|
68
69
|
transition(:idle)
|
@@ -70,7 +71,8 @@ module HTTPX
|
|
70
71
|
|
71
72
|
@inflight = 0
|
72
73
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
73
|
-
|
74
|
+
|
75
|
+
@intervals = []
|
74
76
|
|
75
77
|
self.addresses = @options.addresses if @options.addresses
|
76
78
|
end
|
@@ -81,7 +83,7 @@ module HTTPX
|
|
81
83
|
if @io
|
82
84
|
@io.add_addresses(addrs)
|
83
85
|
else
|
84
|
-
@io =
|
86
|
+
@io = build_socket(addrs)
|
85
87
|
end
|
86
88
|
end
|
87
89
|
|
@@ -90,30 +92,33 @@ module HTTPX
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def match?(uri, options)
|
93
|
-
return false if @state == :closing || @state == :closed
|
94
|
-
|
95
|
-
return false if exhausted?
|
95
|
+
return false if !used? && (@state == :closing || @state == :closed)
|
96
96
|
|
97
97
|
(
|
98
|
-
(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
98
|
+
@origins.include?(uri.origin) &&
|
99
|
+
# if there is more than one origin to match, it means that this connection
|
100
|
+
# was the result of coalescing. To prevent blind trust in the case where the
|
101
|
+
# origin came from an ORIGIN frame, we're going to verify the hostname with the
|
102
|
+
# SSL certificate
|
103
|
+
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
|
104
|
+
) && @options == options
|
105
|
+
end
|
106
|
+
|
107
|
+
def expired?
|
108
|
+
return false unless @io
|
109
|
+
|
110
|
+
@io.expired?
|
107
111
|
end
|
108
112
|
|
109
113
|
def mergeable?(connection)
|
110
114
|
return false if @state == :closing || @state == :closed || !@io
|
111
115
|
|
112
|
-
return false if exhausted?
|
113
|
-
|
114
116
|
return false unless connection.addresses
|
115
117
|
|
116
|
-
|
118
|
+
(
|
119
|
+
(open? && @origin == connection.origin) ||
|
120
|
+
!(@io.addresses & (connection.addresses || [])).empty?
|
121
|
+
) && @options == connection.options
|
117
122
|
end
|
118
123
|
|
119
124
|
# coalescable connections need to be mergeable!
|
@@ -130,11 +135,17 @@ module HTTPX
|
|
130
135
|
end
|
131
136
|
|
132
137
|
def create_idle(options = {})
|
133
|
-
self.class.new(@
|
138
|
+
self.class.new(@origin, @options.merge(options))
|
134
139
|
end
|
135
140
|
|
136
141
|
def merge(connection)
|
137
142
|
@origins |= connection.instance_variable_get(:@origins)
|
143
|
+
if connection.ssl_session
|
144
|
+
@ssl_session = connection.ssl_session
|
145
|
+
@io.session_new_cb do |sess|
|
146
|
+
@ssl_session = sess
|
147
|
+
end if @io
|
148
|
+
end
|
138
149
|
connection.purge_pending do |req|
|
139
150
|
send(req)
|
140
151
|
end
|
@@ -152,24 +163,6 @@ module HTTPX
|
|
152
163
|
end
|
153
164
|
end
|
154
165
|
|
155
|
-
# checks if this is connection is an alternative service of
|
156
|
-
# +uri+
|
157
|
-
def match_altsvcs?(uri)
|
158
|
-
@origins.any? { |origin| uri.altsvc_match?(origin) } ||
|
159
|
-
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
160
|
-
origin = altsvc["origin"]
|
161
|
-
origin.altsvc_match?(uri.origin)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def match_altsvc_options?(uri, options)
|
166
|
-
return @options == options unless @options.ssl[:hostname] == uri.host
|
167
|
-
|
168
|
-
dup_options = @options.merge(ssl: { hostname: nil })
|
169
|
-
dup_options.ssl.delete(:hostname)
|
170
|
-
dup_options == options
|
171
|
-
end
|
172
|
-
|
173
166
|
def connecting?
|
174
167
|
@state == :idle
|
175
168
|
end
|
@@ -200,12 +193,14 @@ module HTTPX
|
|
200
193
|
|
201
194
|
def call
|
202
195
|
case @state
|
196
|
+
when :idle
|
197
|
+
connect
|
198
|
+
consume
|
203
199
|
when :closed
|
204
200
|
return
|
205
201
|
when :closing
|
206
202
|
consume
|
207
203
|
transition(:closed)
|
208
|
-
emit(:close)
|
209
204
|
when :open
|
210
205
|
consume
|
211
206
|
end
|
@@ -218,24 +213,38 @@ module HTTPX
|
|
218
213
|
@parser.close if @parser
|
219
214
|
end
|
220
215
|
|
216
|
+
def terminate
|
217
|
+
@connected_at = nil if @state == :closed
|
218
|
+
|
219
|
+
close
|
220
|
+
end
|
221
|
+
|
222
|
+
# bypasses the state machine to force closing of connections still connecting.
|
223
|
+
# **only** used for Happy Eyeballs v2.
|
224
|
+
def force_reset
|
225
|
+
@state = :closing
|
226
|
+
transition(:closed)
|
227
|
+
end
|
228
|
+
|
221
229
|
def reset
|
230
|
+
return if @state == :closing || @state == :closed
|
231
|
+
|
222
232
|
transition(:closing)
|
233
|
+
|
223
234
|
transition(:closed)
|
224
|
-
emit(:close)
|
225
235
|
end
|
226
236
|
|
227
237
|
def send(request)
|
228
238
|
if @parser && !@write_buffer.full?
|
229
|
-
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
230
|
-
|
231
239
|
if @response_received_at && @keep_alive_timeout &&
|
232
240
|
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
233
241
|
# when pushing a request into an existing connection, we have to check whether there
|
234
242
|
# is the possibility that the connection might have extended the keep alive timeout.
|
235
243
|
# for such cases, we want to ping for availability before deciding to shovel requests.
|
244
|
+
log(level: 3) { "keep alive timeout expired, pinging connection..." }
|
236
245
|
@pending << request
|
237
|
-
parser.ping
|
238
246
|
transition(:active) if @state == :inactive
|
247
|
+
parser.ping
|
239
248
|
return
|
240
249
|
end
|
241
250
|
|
@@ -246,28 +255,24 @@ module HTTPX
|
|
246
255
|
end
|
247
256
|
|
248
257
|
def timeout
|
249
|
-
if @
|
250
|
-
return @total_timeout unless @connected_at
|
251
|
-
|
252
|
-
elapsed_time = @total_timeout - Utils.elapsed_time(@connected_at)
|
253
|
-
|
254
|
-
if elapsed_time.negative?
|
255
|
-
ex = TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds")
|
256
|
-
ex.set_backtrace(caller)
|
257
|
-
on_error(ex)
|
258
|
-
return
|
259
|
-
end
|
260
|
-
|
261
|
-
return elapsed_time
|
262
|
-
end
|
263
|
-
|
264
|
-
return @timeout if defined?(@timeout)
|
258
|
+
return @timeout if @timeout
|
265
259
|
|
266
260
|
return @options.timeout[:connect_timeout] if @state == :idle
|
267
261
|
|
268
262
|
@options.timeout[:operation_timeout]
|
269
263
|
end
|
270
264
|
|
265
|
+
def idling
|
266
|
+
purge_after_closed
|
267
|
+
@write_buffer.clear
|
268
|
+
transition(:idle)
|
269
|
+
@parser = nil if @parser
|
270
|
+
end
|
271
|
+
|
272
|
+
def used?
|
273
|
+
@connected_at
|
274
|
+
end
|
275
|
+
|
271
276
|
def deactivate
|
272
277
|
transition(:inactive)
|
273
278
|
end
|
@@ -276,22 +281,35 @@ module HTTPX
|
|
276
281
|
@state == :open || @state == :inactive
|
277
282
|
end
|
278
283
|
|
284
|
+
def handle_socket_timeout(interval)
|
285
|
+
@intervals.delete_if(&:elapsed?)
|
286
|
+
|
287
|
+
unless @intervals.empty?
|
288
|
+
# remove the intervals which will elapse
|
289
|
+
|
290
|
+
return
|
291
|
+
end
|
292
|
+
|
293
|
+
error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
|
294
|
+
error.set_backtrace(caller)
|
295
|
+
on_error(error)
|
296
|
+
end
|
297
|
+
|
279
298
|
private
|
280
299
|
|
281
300
|
def connect
|
282
301
|
transition(:open)
|
283
302
|
end
|
284
303
|
|
285
|
-
def exhausted?
|
286
|
-
@parser && parser.exhausted?
|
287
|
-
end
|
288
|
-
|
289
304
|
def consume
|
290
305
|
return unless @io
|
291
306
|
|
292
307
|
catch(:called) do
|
293
308
|
epiped = false
|
294
309
|
loop do
|
310
|
+
# connection may have
|
311
|
+
return if @state == :idle
|
312
|
+
|
295
313
|
parser.consume
|
296
314
|
|
297
315
|
# we exit if there's no more requests to process
|
@@ -301,7 +319,7 @@ module HTTPX
|
|
301
319
|
# * the number of inflight requests
|
302
320
|
# * the number of pending requests
|
303
321
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
304
|
-
if @pending.
|
322
|
+
if @pending.empty? && @inflight.zero? && @write_buffer.empty?
|
305
323
|
log(level: 3) { "NO MORE REQUESTS..." }
|
306
324
|
return
|
307
325
|
end
|
@@ -321,7 +339,7 @@ module HTTPX
|
|
321
339
|
#
|
322
340
|
loop do
|
323
341
|
siz = @io.read(@window_size, @read_buffer)
|
324
|
-
log(level: 3, color: :cyan) { "IO READ: #{siz} bytes..." }
|
342
|
+
log(level: 3, color: :cyan) { "IO READ: #{siz} bytes... (wsize: #{@window_size}, rbuffer: #{@read_buffer.bytesize})" }
|
325
343
|
unless siz
|
326
344
|
ex = EOFError.new("descriptor closed")
|
327
345
|
ex.set_backtrace(caller)
|
@@ -345,7 +363,7 @@ module HTTPX
|
|
345
363
|
break if @state == :closing || @state == :closed
|
346
364
|
|
347
365
|
# exit #consume altogether if all outstanding requests have been dealt with
|
348
|
-
return if @pending.
|
366
|
+
return if @pending.empty? && @inflight.zero?
|
349
367
|
end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
|
350
368
|
|
351
369
|
#
|
@@ -422,15 +440,18 @@ module HTTPX
|
|
422
440
|
|
423
441
|
def send_request_to_parser(request)
|
424
442
|
@inflight += 1
|
443
|
+
request.peer_address = @io.ip
|
425
444
|
parser.send(request)
|
426
445
|
|
446
|
+
set_request_timeouts(request)
|
447
|
+
|
427
448
|
return unless @state == :inactive
|
428
449
|
|
429
450
|
transition(:active)
|
430
451
|
end
|
431
452
|
|
432
453
|
def build_parser(protocol = @io.protocol)
|
433
|
-
parser =
|
454
|
+
parser = self.class.parser_type(protocol).new(@write_buffer, @options)
|
434
455
|
set_parser_callbacks(parser)
|
435
456
|
parser
|
436
457
|
end
|
@@ -454,33 +475,25 @@ module HTTPX
|
|
454
475
|
request.emit(:promise, parser, stream)
|
455
476
|
end
|
456
477
|
parser.on(:exhausted) do
|
478
|
+
@pending.concat(parser.pending)
|
457
479
|
emit(:exhausted)
|
458
480
|
end
|
459
481
|
parser.on(:origin) do |origin|
|
460
482
|
@origins |= [origin]
|
461
483
|
end
|
462
484
|
parser.on(:close) do |force|
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
emit(:close)
|
485
|
+
if force
|
486
|
+
reset
|
487
|
+
emit(:terminate)
|
467
488
|
end
|
468
489
|
end
|
469
490
|
parser.on(:close_handshake) do
|
470
491
|
consume
|
471
492
|
end
|
472
493
|
parser.on(:reset) do
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
transition(:closing)
|
477
|
-
transition(:closed)
|
478
|
-
emit(:reset)
|
479
|
-
|
480
|
-
@parser.reset if @parser
|
481
|
-
transition(:idle)
|
482
|
-
transition(:open)
|
483
|
-
end
|
494
|
+
@pending.concat(parser.pending) unless parser.empty?
|
495
|
+
reset
|
496
|
+
idling unless @pending.empty?
|
484
497
|
end
|
485
498
|
parser.on(:current_timeout) do
|
486
499
|
@current_timeout = @timeout = parser.timeout
|
@@ -493,7 +506,7 @@ module HTTPX
|
|
493
506
|
when MisdirectedRequestError
|
494
507
|
emit(:misdirected, request)
|
495
508
|
else
|
496
|
-
response = ErrorResponse.new(request, ex
|
509
|
+
response = ErrorResponse.new(request, ex)
|
497
510
|
request.response = response
|
498
511
|
request.emit(:response, response)
|
499
512
|
end
|
@@ -502,12 +515,27 @@ module HTTPX
|
|
502
515
|
|
503
516
|
def transition(nextstate)
|
504
517
|
handle_transition(nextstate)
|
505
|
-
rescue Errno::
|
518
|
+
rescue Errno::ECONNABORTED,
|
519
|
+
Errno::ECONNREFUSED,
|
520
|
+
Errno::ECONNRESET,
|
506
521
|
Errno::EADDRNOTAVAIL,
|
507
522
|
Errno::EHOSTUNREACH,
|
508
|
-
|
523
|
+
Errno::EINVAL,
|
524
|
+
Errno::ENETUNREACH,
|
525
|
+
Errno::EPIPE,
|
526
|
+
Errno::ENOENT,
|
527
|
+
SocketError,
|
528
|
+
IOError => e
|
529
|
+
# connect errors, exit gracefully
|
530
|
+
error = ConnectionError.new(e.message)
|
531
|
+
error.set_backtrace(e.backtrace)
|
532
|
+
connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, error) : handle_error(error)
|
533
|
+
@state = :closed
|
534
|
+
emit(:close)
|
535
|
+
rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
|
509
536
|
# connect errors, exit gracefully
|
510
537
|
handle_error(e)
|
538
|
+
connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, e) : handle_error(e)
|
511
539
|
@state = :closed
|
512
540
|
emit(:close)
|
513
541
|
end
|
@@ -517,10 +545,13 @@ module HTTPX
|
|
517
545
|
when :idle
|
518
546
|
@timeout = @current_timeout = @options.timeout[:connect_timeout]
|
519
547
|
|
548
|
+
@connected_at = nil
|
520
549
|
when :open
|
521
550
|
return if @state == :closed
|
522
551
|
|
523
552
|
@io.connect
|
553
|
+
emit(:tcp_open, self) if @io.state == :connected
|
554
|
+
|
524
555
|
return unless @io.connected?
|
525
556
|
|
526
557
|
@connected_at = Utils.now
|
@@ -531,16 +562,32 @@ module HTTPX
|
|
531
562
|
emit(:open)
|
532
563
|
when :inactive
|
533
564
|
return unless @state == :open
|
534
|
-
when :closing
|
535
|
-
return unless @state == :open
|
536
565
|
|
566
|
+
# do not deactivate connection in use
|
567
|
+
return if @inflight.positive?
|
568
|
+
when :closing
|
569
|
+
return unless @state == :idle || @state == :open
|
570
|
+
|
571
|
+
unless @write_buffer.empty?
|
572
|
+
# preset state before handshake, as error callbacks
|
573
|
+
# may take it back here.
|
574
|
+
@state = nextstate
|
575
|
+
# handshakes, try sending
|
576
|
+
consume
|
577
|
+
@write_buffer.clear
|
578
|
+
return
|
579
|
+
end
|
537
580
|
when :closed
|
538
581
|
return unless @state == :closing
|
539
582
|
return unless @write_buffer.empty?
|
540
583
|
|
541
584
|
purge_after_closed
|
585
|
+
emit(:close) if @pending.empty?
|
542
586
|
when :already_open
|
543
587
|
nextstate = :open
|
588
|
+
# the first check for given io readiness must still use a timeout.
|
589
|
+
# connect is the reasonable choice in such a case.
|
590
|
+
@timeout = @options.timeout[:connect_timeout]
|
544
591
|
send_pending
|
545
592
|
when :active
|
546
593
|
return unless @state == :inactive
|
@@ -554,40 +601,163 @@ module HTTPX
|
|
554
601
|
def purge_after_closed
|
555
602
|
@io.close if @io
|
556
603
|
@read_buffer.clear
|
557
|
-
|
604
|
+
@timeout = nil
|
558
605
|
end
|
559
606
|
|
560
|
-
def
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
error = ex
|
607
|
+
def initialize_type(uri, options)
|
608
|
+
options.transport || begin
|
609
|
+
case uri.scheme
|
610
|
+
when "http"
|
611
|
+
"tcp"
|
612
|
+
when "https"
|
613
|
+
"ssl"
|
568
614
|
else
|
569
|
-
|
570
|
-
|
571
|
-
|
615
|
+
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
572
619
|
|
573
|
-
|
574
|
-
|
575
|
-
|
620
|
+
def build_socket(addrs = nil)
|
621
|
+
case @type
|
622
|
+
when "tcp"
|
623
|
+
TCP.new(@origin, addrs, @options)
|
624
|
+
when "ssl"
|
625
|
+
SSL.new(@origin, addrs, @options) do |sock|
|
626
|
+
sock.ssl_session = @ssl_session
|
627
|
+
sock.session_new_cb do |sess|
|
628
|
+
@ssl_session = sess
|
629
|
+
|
630
|
+
sock.ssl_session = sess
|
576
631
|
end
|
632
|
+
end
|
633
|
+
when "unix"
|
634
|
+
path = Array(addrs).first
|
635
|
+
|
636
|
+
path = String(path) if path
|
637
|
+
|
638
|
+
UNIX.new(@origin, path, @options)
|
639
|
+
else
|
640
|
+
raise Error, "unsupported transport (#{@type})"
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
def on_error(error, request = nil)
|
645
|
+
if error.instance_of?(TimeoutError)
|
646
|
+
|
647
|
+
# inactive connections do not contribute to the select loop, therefore
|
648
|
+
# they should not fail due to such errors.
|
649
|
+
return if @state == :inactive
|
577
650
|
|
578
|
-
|
651
|
+
if @timeout
|
652
|
+
@timeout -= error.timeout
|
653
|
+
return unless @timeout <= 0
|
579
654
|
end
|
655
|
+
|
656
|
+
error = error.to_connection_error if connecting?
|
580
657
|
end
|
581
|
-
handle_error(error)
|
658
|
+
handle_error(error, request)
|
582
659
|
reset
|
583
660
|
end
|
584
661
|
|
585
|
-
def handle_error(error)
|
586
|
-
parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
|
587
|
-
while (
|
588
|
-
|
589
|
-
|
590
|
-
|
662
|
+
def handle_error(error, request = nil)
|
663
|
+
parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
|
664
|
+
while (req = @pending.shift)
|
665
|
+
next if request && req == request
|
666
|
+
|
667
|
+
response = ErrorResponse.new(req, error)
|
668
|
+
req.response = response
|
669
|
+
req.emit(:response, response)
|
670
|
+
end
|
671
|
+
|
672
|
+
return unless request
|
673
|
+
|
674
|
+
response = ErrorResponse.new(request, error)
|
675
|
+
request.response = response
|
676
|
+
request.emit(:response, response)
|
677
|
+
end
|
678
|
+
|
679
|
+
def set_request_timeouts(request)
|
680
|
+
set_request_write_timeout(request)
|
681
|
+
set_request_read_timeout(request)
|
682
|
+
set_request_request_timeout(request)
|
683
|
+
end
|
684
|
+
|
685
|
+
def set_request_read_timeout(request)
|
686
|
+
read_timeout = request.read_timeout
|
687
|
+
|
688
|
+
return if read_timeout.nil? || read_timeout.infinite?
|
689
|
+
|
690
|
+
set_request_timeout(request, read_timeout, :done, :response) do
|
691
|
+
read_timeout_callback(request, read_timeout)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
def set_request_write_timeout(request)
|
696
|
+
write_timeout = request.write_timeout
|
697
|
+
|
698
|
+
return if write_timeout.nil? || write_timeout.infinite?
|
699
|
+
|
700
|
+
set_request_timeout(request, write_timeout, :headers, %i[done response]) do
|
701
|
+
write_timeout_callback(request, write_timeout)
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
def set_request_request_timeout(request)
|
706
|
+
request_timeout = request.request_timeout
|
707
|
+
|
708
|
+
return if request_timeout.nil? || request_timeout.infinite?
|
709
|
+
|
710
|
+
set_request_timeout(request, request_timeout, :headers, :complete) do
|
711
|
+
read_timeout_callback(request, request_timeout, RequestTimeoutError)
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
def write_timeout_callback(request, write_timeout)
|
716
|
+
return if request.state == :done
|
717
|
+
|
718
|
+
@write_buffer.clear
|
719
|
+
error = WriteTimeoutError.new(request, nil, write_timeout)
|
720
|
+
|
721
|
+
on_error(error, request)
|
722
|
+
end
|
723
|
+
|
724
|
+
def read_timeout_callback(request, read_timeout, error_type = ReadTimeoutError)
|
725
|
+
response = request.response
|
726
|
+
|
727
|
+
return if response && response.finished?
|
728
|
+
|
729
|
+
@write_buffer.clear
|
730
|
+
error = error_type.new(request, request.response, read_timeout)
|
731
|
+
|
732
|
+
on_error(error, request)
|
733
|
+
end
|
734
|
+
|
735
|
+
def set_request_timeout(request, timeout, start_event, finish_events, &callback)
|
736
|
+
request.once(start_event) do
|
737
|
+
interval = @timers.after(timeout, callback)
|
738
|
+
|
739
|
+
Array(finish_events).each do |event|
|
740
|
+
# clean up request timeouts if the connection errors out
|
741
|
+
request.once(event) do
|
742
|
+
if @intervals.include?(interval)
|
743
|
+
interval.delete(callback)
|
744
|
+
@intervals.delete(interval) if interval.no_callbacks?
|
745
|
+
end
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
@intervals << interval
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
753
|
+
class << self
|
754
|
+
def parser_type(protocol)
|
755
|
+
case protocol
|
756
|
+
when "h2" then HTTP2
|
757
|
+
when "http/1.1" then HTTP1
|
758
|
+
else
|
759
|
+
raise Error, "unsupported protocol (##{protocol})"
|
760
|
+
end
|
591
761
|
end
|
592
762
|
end
|
593
763
|
end
|