httpx 0.21.0 → 1.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 +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 +4 -4
- 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_20_0.md +1 -1
- data/doc/release_notes/0_21_0.md +7 -5
- 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/lib/httpx/adapters/datadog.rb +100 -106
- data/lib/httpx/adapters/faraday.rb +143 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +33 -17
- data/lib/httpx/altsvc.rb +61 -24
- 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 +62 -37
- data/lib/httpx/connection/http2.rb +16 -27
- data/lib/httpx/connection.rb +213 -120
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +34 -2
- data/lib/httpx/extensions.rb +4 -134
- data/lib/httpx/io/ssl.rb +77 -71
- data/lib/httpx/io/tcp.rb +46 -70
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +6 -13
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +168 -110
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -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 +1 -3
- data/lib/httpx/plugins/aws_sigv4.rb +5 -6
- 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 +40 -16
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
- data/lib/httpx/plugins/circuit_breaker.rb +30 -7
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +20 -10
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
- data/lib/httpx/plugins/expect.rb +15 -13
- data/lib/httpx/plugins/follow_redirects.rb +71 -29
- 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 +35 -29
- data/lib/httpx/plugins/h2c.rb +25 -18
- 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 +170 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -10
- data/lib/httpx/plugins/proxy/socks4.rb +8 -6
- data/lib/httpx/plugins/proxy/socks5.rb +10 -8
- data/lib/httpx/plugins/proxy.rb +69 -67
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +3 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +34 -17
- data/lib/httpx/plugins/response_cache.rb +6 -6
- data/lib/httpx/plugins/retries.rb +61 -12
- data/lib/httpx/plugins/ssrf_filter.rb +142 -0
- data/lib/httpx/plugins/stream.rb +27 -32
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +10 -8
- data/lib/httpx/pool.rb +85 -23
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +158 -0
- data/lib/httpx/request.rb +86 -121
- data/lib/httpx/resolver/https.rb +54 -17
- data/lib/httpx/resolver/multi.rb +8 -12
- data/lib/httpx/resolver/native.rb +163 -70
- data/lib/httpx/resolver/resolver.rb +28 -13
- data/lib/httpx/resolver/system.rb +15 -10
- 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 +113 -211
- data/lib/httpx/selector.rb +2 -4
- data/lib/httpx/session.rb +91 -64
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +28 -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 +2 -5
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
- 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 +0 -5
- data/lib/httpx/transcoder.rb +4 -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 +1 -0
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +10 -9
- data/sig/connection/http1.rbs +5 -4
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +46 -24
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +26 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +10 -0
- data/sig/options.rbs +28 -12
- 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 +13 -3
- data/sig/plugins/compression.rbs +6 -4
- 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 +11 -2
- 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 +2 -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/socks4.rbs +4 -4
- data/sig/plugins/proxy/socks5.rbs +2 -2
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +10 -4
- data/sig/plugins/response_cache.rbs +12 -3
- data/sig/plugins/retries.rbs +28 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +5 -4
- data/sig/request/body.rbs +40 -0
- data/sig/request.rbs +12 -28
- data/sig/resolver/https.rbs +7 -2
- data/sig/resolver/native.rbs +10 -4
- data/sig/resolver/resolver.rbs +6 -4
- 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 +17 -38
- data/sig/session.rbs +24 -18
- data/sig/timers.rbs +17 -7
- 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 +4 -2
- data/sig/{plugins → transcoder}/multipart.rbs +3 -12
- 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 +1 -1
- data/sig/transcoder.rbs +22 -7
- data/sig/utils.rbs +2 -0
- metadata +127 -40
- 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 -13
- /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,41 +29,39 @@ 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
|
|
|
36
35
|
using URIExtensions
|
|
37
|
-
using NumericExtensions
|
|
38
36
|
|
|
39
37
|
require "httpx/connection/http2"
|
|
40
38
|
require "httpx/connection/http1"
|
|
41
39
|
|
|
42
|
-
BUFFER_SIZE = 1 << 14
|
|
43
|
-
|
|
44
40
|
def_delegator :@io, :closed?
|
|
45
41
|
|
|
46
42
|
def_delegator :@write_buffer, :empty?
|
|
47
43
|
|
|
48
|
-
attr_reader :io, :origin, :origins, :state, :pending, :options
|
|
44
|
+
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
|
|
49
45
|
|
|
50
46
|
attr_writer :timers
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
attr_accessor :family
|
|
49
|
+
|
|
50
|
+
def initialize(uri, options)
|
|
54
51
|
@origins = [uri.origin]
|
|
55
52
|
@origin = Utils.to_uri(uri.origin)
|
|
56
53
|
@options = Options.new(options)
|
|
54
|
+
@type = initialize_type(uri, @options)
|
|
57
55
|
@window_size = @options.window_size
|
|
58
|
-
@read_buffer = Buffer.new(
|
|
59
|
-
@write_buffer = Buffer.new(
|
|
56
|
+
@read_buffer = Buffer.new(@options.buffer_size)
|
|
57
|
+
@write_buffer = Buffer.new(@options.buffer_size)
|
|
60
58
|
@pending = []
|
|
61
59
|
on(:error, &method(:on_error))
|
|
62
60
|
if @options.io
|
|
63
61
|
# if there's an already open IO, get its
|
|
64
62
|
# peer address, and force-initiate the parser
|
|
65
63
|
transition(:already_open)
|
|
66
|
-
@io =
|
|
64
|
+
@io = build_socket
|
|
67
65
|
parser
|
|
68
66
|
else
|
|
69
67
|
transition(:idle)
|
|
@@ -71,7 +69,8 @@ module HTTPX
|
|
|
71
69
|
|
|
72
70
|
@inflight = 0
|
|
73
71
|
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
|
74
|
-
|
|
72
|
+
|
|
73
|
+
@intervals = []
|
|
75
74
|
|
|
76
75
|
self.addresses = @options.addresses if @options.addresses
|
|
77
76
|
end
|
|
@@ -82,7 +81,7 @@ module HTTPX
|
|
|
82
81
|
if @io
|
|
83
82
|
@io.add_addresses(addrs)
|
|
84
83
|
else
|
|
85
|
-
@io =
|
|
84
|
+
@io = build_socket(addrs)
|
|
86
85
|
end
|
|
87
86
|
end
|
|
88
87
|
|
|
@@ -91,30 +90,33 @@ module HTTPX
|
|
|
91
90
|
end
|
|
92
91
|
|
|
93
92
|
def match?(uri, options)
|
|
94
|
-
return false if @state == :closing || @state == :closed
|
|
95
|
-
|
|
96
|
-
return false if exhausted?
|
|
93
|
+
return false if !used? && (@state == :closing || @state == :closed)
|
|
97
94
|
|
|
98
95
|
(
|
|
99
|
-
(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
@origins.include?(uri.origin) &&
|
|
97
|
+
# if there is more than one origin to match, it means that this connection
|
|
98
|
+
# was the result of coalescing. To prevent blind trust in the case where the
|
|
99
|
+
# origin came from an ORIGIN frame, we're going to verify the hostname with the
|
|
100
|
+
# SSL certificate
|
|
101
|
+
(@origins.size == 1 || @origin == uri.origin || (@io.is_a?(SSL) && @io.verify_hostname(uri.host)))
|
|
102
|
+
) && @options == options
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def expired?
|
|
106
|
+
return false unless @io
|
|
107
|
+
|
|
108
|
+
@io.expired?
|
|
108
109
|
end
|
|
109
110
|
|
|
110
111
|
def mergeable?(connection)
|
|
111
112
|
return false if @state == :closing || @state == :closed || !@io
|
|
112
113
|
|
|
113
|
-
return false if exhausted?
|
|
114
|
-
|
|
115
114
|
return false unless connection.addresses
|
|
116
115
|
|
|
117
|
-
|
|
116
|
+
(
|
|
117
|
+
(open? && @origin == connection.origin) ||
|
|
118
|
+
!(@io.addresses & (connection.addresses || [])).empty?
|
|
119
|
+
) && @options == connection.options
|
|
118
120
|
end
|
|
119
121
|
|
|
120
122
|
# coalescable connections need to be mergeable!
|
|
@@ -131,11 +133,17 @@ module HTTPX
|
|
|
131
133
|
end
|
|
132
134
|
|
|
133
135
|
def create_idle(options = {})
|
|
134
|
-
self.class.new(@
|
|
136
|
+
self.class.new(@origin, @options.merge(options))
|
|
135
137
|
end
|
|
136
138
|
|
|
137
139
|
def merge(connection)
|
|
138
140
|
@origins |= connection.instance_variable_get(:@origins)
|
|
141
|
+
if connection.ssl_session
|
|
142
|
+
@ssl_session = connection.ssl_session
|
|
143
|
+
@io.session_new_cb do |sess|
|
|
144
|
+
@ssl_session = sess
|
|
145
|
+
end if @io
|
|
146
|
+
end
|
|
139
147
|
connection.purge_pending do |req|
|
|
140
148
|
send(req)
|
|
141
149
|
end
|
|
@@ -153,24 +161,6 @@ module HTTPX
|
|
|
153
161
|
end
|
|
154
162
|
end
|
|
155
163
|
|
|
156
|
-
# checks if this is connection is an alternative service of
|
|
157
|
-
# +uri+
|
|
158
|
-
def match_altsvcs?(uri)
|
|
159
|
-
@origins.any? { |origin| uri.altsvc_match?(origin) } ||
|
|
160
|
-
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
|
161
|
-
origin = altsvc["origin"]
|
|
162
|
-
origin.altsvc_match?(uri.origin)
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def match_altsvc_options?(uri, options)
|
|
167
|
-
return @options == options unless @options.ssl[:hostname] == uri.host
|
|
168
|
-
|
|
169
|
-
dup_options = @options.merge(ssl: { hostname: nil })
|
|
170
|
-
dup_options.ssl.delete(:hostname)
|
|
171
|
-
dup_options == options
|
|
172
|
-
end
|
|
173
|
-
|
|
174
164
|
def connecting?
|
|
175
165
|
@state == :idle
|
|
176
166
|
end
|
|
@@ -201,12 +191,14 @@ module HTTPX
|
|
|
201
191
|
|
|
202
192
|
def call
|
|
203
193
|
case @state
|
|
194
|
+
when :idle
|
|
195
|
+
connect
|
|
196
|
+
consume
|
|
204
197
|
when :closed
|
|
205
198
|
return
|
|
206
199
|
when :closing
|
|
207
200
|
consume
|
|
208
201
|
transition(:closed)
|
|
209
|
-
emit(:close)
|
|
210
202
|
when :open
|
|
211
203
|
consume
|
|
212
204
|
end
|
|
@@ -219,16 +211,29 @@ module HTTPX
|
|
|
219
211
|
@parser.close if @parser
|
|
220
212
|
end
|
|
221
213
|
|
|
214
|
+
def terminate
|
|
215
|
+
@connected_at = nil if @state == :closed
|
|
216
|
+
|
|
217
|
+
close
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# bypasses the state machine to force closing of connections still connecting.
|
|
221
|
+
# **only** used for Happy Eyeballs v2.
|
|
222
|
+
def force_reset
|
|
223
|
+
@state = :closing
|
|
224
|
+
transition(:closed)
|
|
225
|
+
end
|
|
226
|
+
|
|
222
227
|
def reset
|
|
228
|
+
return if @state == :closing || @state == :closed
|
|
229
|
+
|
|
223
230
|
transition(:closing)
|
|
231
|
+
|
|
224
232
|
transition(:closed)
|
|
225
|
-
emit(:close)
|
|
226
233
|
end
|
|
227
234
|
|
|
228
235
|
def send(request)
|
|
229
236
|
if @parser && !@write_buffer.full?
|
|
230
|
-
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
|
231
|
-
|
|
232
237
|
if @response_received_at && @keep_alive_timeout &&
|
|
233
238
|
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
|
234
239
|
# when pushing a request into an existing connection, we have to check whether there
|
|
@@ -248,28 +253,24 @@ module HTTPX
|
|
|
248
253
|
end
|
|
249
254
|
|
|
250
255
|
def timeout
|
|
251
|
-
if @
|
|
252
|
-
return @total_timeout unless @connected_at
|
|
253
|
-
|
|
254
|
-
elapsed_time = @total_timeout - Utils.elapsed_time(@connected_at)
|
|
255
|
-
|
|
256
|
-
if elapsed_time.negative?
|
|
257
|
-
ex = TotalTimeoutError.new(@total_timeout, "Timed out after #{@total_timeout} seconds")
|
|
258
|
-
ex.set_backtrace(caller)
|
|
259
|
-
on_error(ex)
|
|
260
|
-
return
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
return elapsed_time
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
return @timeout if defined?(@timeout)
|
|
256
|
+
return @timeout if @timeout
|
|
267
257
|
|
|
268
258
|
return @options.timeout[:connect_timeout] if @state == :idle
|
|
269
259
|
|
|
270
260
|
@options.timeout[:operation_timeout]
|
|
271
261
|
end
|
|
272
262
|
|
|
263
|
+
def idling
|
|
264
|
+
purge_after_closed
|
|
265
|
+
@write_buffer.clear
|
|
266
|
+
transition(:idle)
|
|
267
|
+
@parser = nil if @parser
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def used?
|
|
271
|
+
@connected_at
|
|
272
|
+
end
|
|
273
|
+
|
|
273
274
|
def deactivate
|
|
274
275
|
transition(:inactive)
|
|
275
276
|
end
|
|
@@ -278,7 +279,15 @@ module HTTPX
|
|
|
278
279
|
@state == :open || @state == :inactive
|
|
279
280
|
end
|
|
280
281
|
|
|
281
|
-
def
|
|
282
|
+
def handle_socket_timeout(interval)
|
|
283
|
+
@intervals.delete_if(&:elapsed?)
|
|
284
|
+
|
|
285
|
+
unless @intervals.empty?
|
|
286
|
+
# remove the intervals which will elapse
|
|
287
|
+
|
|
288
|
+
return
|
|
289
|
+
end
|
|
290
|
+
|
|
282
291
|
error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
|
|
283
292
|
error.set_backtrace(caller)
|
|
284
293
|
on_error(error)
|
|
@@ -290,16 +299,15 @@ module HTTPX
|
|
|
290
299
|
transition(:open)
|
|
291
300
|
end
|
|
292
301
|
|
|
293
|
-
def exhausted?
|
|
294
|
-
@parser && parser.exhausted?
|
|
295
|
-
end
|
|
296
|
-
|
|
297
302
|
def consume
|
|
298
303
|
return unless @io
|
|
299
304
|
|
|
300
305
|
catch(:called) do
|
|
301
306
|
epiped = false
|
|
302
307
|
loop do
|
|
308
|
+
# connection may have
|
|
309
|
+
return if @state == :idle
|
|
310
|
+
|
|
303
311
|
parser.consume
|
|
304
312
|
|
|
305
313
|
# we exit if there's no more requests to process
|
|
@@ -309,7 +317,7 @@ module HTTPX
|
|
|
309
317
|
# * the number of inflight requests
|
|
310
318
|
# * the number of pending requests
|
|
311
319
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
312
|
-
if @pending.
|
|
320
|
+
if @pending.empty? && @inflight.zero? && @write_buffer.empty?
|
|
313
321
|
log(level: 3) { "NO MORE REQUESTS..." }
|
|
314
322
|
return
|
|
315
323
|
end
|
|
@@ -353,7 +361,7 @@ module HTTPX
|
|
|
353
361
|
break if @state == :closing || @state == :closed
|
|
354
362
|
|
|
355
363
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
356
|
-
return if @pending.
|
|
364
|
+
return if @pending.empty? && @inflight.zero?
|
|
357
365
|
end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
|
|
358
366
|
|
|
359
367
|
#
|
|
@@ -430,6 +438,7 @@ module HTTPX
|
|
|
430
438
|
|
|
431
439
|
def send_request_to_parser(request)
|
|
432
440
|
@inflight += 1
|
|
441
|
+
request.peer_address = @io.ip
|
|
433
442
|
parser.send(request)
|
|
434
443
|
|
|
435
444
|
set_request_timeouts(request)
|
|
@@ -440,7 +449,7 @@ module HTTPX
|
|
|
440
449
|
end
|
|
441
450
|
|
|
442
451
|
def build_parser(protocol = @io.protocol)
|
|
443
|
-
parser =
|
|
452
|
+
parser = self.class.parser_type(protocol).new(@write_buffer, @options)
|
|
444
453
|
set_parser_callbacks(parser)
|
|
445
454
|
parser
|
|
446
455
|
end
|
|
@@ -464,33 +473,25 @@ module HTTPX
|
|
|
464
473
|
request.emit(:promise, parser, stream)
|
|
465
474
|
end
|
|
466
475
|
parser.on(:exhausted) do
|
|
476
|
+
@pending.concat(parser.pending)
|
|
467
477
|
emit(:exhausted)
|
|
468
478
|
end
|
|
469
479
|
parser.on(:origin) do |origin|
|
|
470
480
|
@origins |= [origin]
|
|
471
481
|
end
|
|
472
482
|
parser.on(:close) do |force|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
emit(:close)
|
|
483
|
+
if force
|
|
484
|
+
reset
|
|
485
|
+
emit(:terminate)
|
|
477
486
|
end
|
|
478
487
|
end
|
|
479
488
|
parser.on(:close_handshake) do
|
|
480
489
|
consume
|
|
481
490
|
end
|
|
482
491
|
parser.on(:reset) do
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
transition(:closing)
|
|
487
|
-
transition(:closed)
|
|
488
|
-
emit(:reset)
|
|
489
|
-
|
|
490
|
-
@parser.reset if @parser
|
|
491
|
-
transition(:idle)
|
|
492
|
-
transition(:open)
|
|
493
|
-
end
|
|
492
|
+
@pending.concat(parser.pending) unless parser.empty?
|
|
493
|
+
reset
|
|
494
|
+
idling unless @pending.empty?
|
|
494
495
|
end
|
|
495
496
|
parser.on(:current_timeout) do
|
|
496
497
|
@current_timeout = @timeout = parser.timeout
|
|
@@ -512,12 +513,26 @@ module HTTPX
|
|
|
512
513
|
|
|
513
514
|
def transition(nextstate)
|
|
514
515
|
handle_transition(nextstate)
|
|
515
|
-
rescue Errno::
|
|
516
|
+
rescue Errno::ECONNABORTED,
|
|
517
|
+
Errno::ECONNREFUSED,
|
|
518
|
+
Errno::ECONNRESET,
|
|
516
519
|
Errno::EADDRNOTAVAIL,
|
|
517
520
|
Errno::EHOSTUNREACH,
|
|
518
|
-
|
|
521
|
+
Errno::EINVAL,
|
|
522
|
+
Errno::ENETUNREACH,
|
|
523
|
+
Errno::EPIPE,
|
|
524
|
+
Errno::ENOENT,
|
|
525
|
+
SocketError => e
|
|
526
|
+
# connect errors, exit gracefully
|
|
527
|
+
error = ConnectionError.new(e.message)
|
|
528
|
+
error.set_backtrace(e.backtrace)
|
|
529
|
+
connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, error) : handle_error(error)
|
|
530
|
+
@state = :closed
|
|
531
|
+
emit(:close)
|
|
532
|
+
rescue TLSError => e
|
|
519
533
|
# connect errors, exit gracefully
|
|
520
534
|
handle_error(e)
|
|
535
|
+
connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, e) : handle_error(e)
|
|
521
536
|
@state = :closed
|
|
522
537
|
emit(:close)
|
|
523
538
|
end
|
|
@@ -527,10 +542,13 @@ module HTTPX
|
|
|
527
542
|
when :idle
|
|
528
543
|
@timeout = @current_timeout = @options.timeout[:connect_timeout]
|
|
529
544
|
|
|
545
|
+
@connected_at = nil
|
|
530
546
|
when :open
|
|
531
547
|
return if @state == :closed
|
|
532
548
|
|
|
533
549
|
@io.connect
|
|
550
|
+
emit(:tcp_open, self) if @io.state == :connected
|
|
551
|
+
|
|
534
552
|
return unless @io.connected?
|
|
535
553
|
|
|
536
554
|
@connected_at = Utils.now
|
|
@@ -542,15 +560,28 @@ module HTTPX
|
|
|
542
560
|
when :inactive
|
|
543
561
|
return unless @state == :open
|
|
544
562
|
when :closing
|
|
545
|
-
return unless @state == :open
|
|
546
|
-
|
|
563
|
+
return unless @state == :idle || @state == :open
|
|
564
|
+
|
|
565
|
+
unless @write_buffer.empty?
|
|
566
|
+
# preset state before handshake, as error callbacks
|
|
567
|
+
# may take it back here.
|
|
568
|
+
@state = nextstate
|
|
569
|
+
# handshakes, try sending
|
|
570
|
+
consume
|
|
571
|
+
@write_buffer.clear
|
|
572
|
+
return
|
|
573
|
+
end
|
|
547
574
|
when :closed
|
|
548
575
|
return unless @state == :closing
|
|
549
576
|
return unless @write_buffer.empty?
|
|
550
577
|
|
|
551
578
|
purge_after_closed
|
|
579
|
+
emit(:close) if @pending.empty?
|
|
552
580
|
when :already_open
|
|
553
581
|
nextstate = :open
|
|
582
|
+
# the first check for given io readiness must still use a timeout.
|
|
583
|
+
# connect is the reasonable choice in such a case.
|
|
584
|
+
@timeout = @options.timeout[:connect_timeout]
|
|
554
585
|
send_pending
|
|
555
586
|
when :active
|
|
556
587
|
return unless @state == :inactive
|
|
@@ -564,29 +595,55 @@ module HTTPX
|
|
|
564
595
|
def purge_after_closed
|
|
565
596
|
@io.close if @io
|
|
566
597
|
@read_buffer.clear
|
|
567
|
-
|
|
598
|
+
@timeout = nil
|
|
568
599
|
end
|
|
569
600
|
|
|
570
|
-
def
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
error = ex
|
|
601
|
+
def initialize_type(uri, options)
|
|
602
|
+
options.transport || begin
|
|
603
|
+
case uri.scheme
|
|
604
|
+
when "http"
|
|
605
|
+
"tcp"
|
|
606
|
+
when "https"
|
|
607
|
+
"ssl"
|
|
578
608
|
else
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
609
|
+
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
end
|
|
582
613
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
614
|
+
def build_socket(addrs = nil)
|
|
615
|
+
case @type
|
|
616
|
+
when "tcp"
|
|
617
|
+
TCP.new(@origin, addrs, @options)
|
|
618
|
+
when "ssl"
|
|
619
|
+
SSL.new(@origin, addrs, @options) do |sock|
|
|
620
|
+
sock.ssl_session = @ssl_session
|
|
621
|
+
sock.session_new_cb do |sess|
|
|
622
|
+
@ssl_session = sess
|
|
623
|
+
|
|
624
|
+
sock.ssl_session = sess
|
|
586
625
|
end
|
|
626
|
+
end
|
|
627
|
+
when "unix"
|
|
628
|
+
UNIX.new(@origin, addrs, @options)
|
|
629
|
+
else
|
|
630
|
+
raise Error, "unsupported transport (#{@type})"
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def on_error(error)
|
|
635
|
+
if error.instance_of?(TimeoutError)
|
|
636
|
+
|
|
637
|
+
# inactive connections do not contribute to the select loop, therefore
|
|
638
|
+
# they should not fail due to such errors.
|
|
639
|
+
return if @state == :inactive
|
|
587
640
|
|
|
588
|
-
|
|
641
|
+
if @timeout
|
|
642
|
+
@timeout -= error.timeout
|
|
643
|
+
return unless @timeout <= 0
|
|
589
644
|
end
|
|
645
|
+
|
|
646
|
+
error = error.to_connection_error if connecting?
|
|
590
647
|
end
|
|
591
648
|
handle_error(error)
|
|
592
649
|
reset
|
|
@@ -603,19 +660,26 @@ module HTTPX
|
|
|
603
660
|
|
|
604
661
|
def set_request_timeouts(request)
|
|
605
662
|
write_timeout = request.write_timeout
|
|
606
|
-
request.once(:headers) do
|
|
607
|
-
@timers.after(write_timeout) { write_timeout_callback(request, write_timeout) }
|
|
608
|
-
end unless write_timeout.nil? || write_timeout.infinite?
|
|
609
|
-
|
|
610
663
|
read_timeout = request.read_timeout
|
|
611
|
-
request.once(:done) do
|
|
612
|
-
@timers.after(read_timeout) { read_timeout_callback(request, read_timeout) }
|
|
613
|
-
end unless read_timeout.nil? || read_timeout.infinite?
|
|
614
|
-
|
|
615
664
|
request_timeout = request.request_timeout
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
665
|
+
|
|
666
|
+
unless write_timeout.nil? || write_timeout.infinite?
|
|
667
|
+
set_request_timeout(request, write_timeout, :headers, %i[done response]) do
|
|
668
|
+
write_timeout_callback(request, write_timeout)
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
unless read_timeout.nil? || read_timeout.infinite?
|
|
673
|
+
set_request_timeout(request, read_timeout, :done, :response) do
|
|
674
|
+
read_timeout_callback(request, read_timeout)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
return if request_timeout.nil? || request_timeout.infinite?
|
|
679
|
+
|
|
680
|
+
set_request_timeout(request, request_timeout, :headers, :response) do
|
|
681
|
+
read_timeout_callback(request, request_timeout, RequestTimeoutError)
|
|
682
|
+
end
|
|
619
683
|
end
|
|
620
684
|
|
|
621
685
|
def write_timeout_callback(request, write_timeout)
|
|
@@ -635,5 +699,34 @@ module HTTPX
|
|
|
635
699
|
error = error_type.new(request, request.response, read_timeout)
|
|
636
700
|
on_error(error)
|
|
637
701
|
end
|
|
702
|
+
|
|
703
|
+
def set_request_timeout(request, timeout, start_event, finish_events, &callback)
|
|
704
|
+
request.once(start_event) do
|
|
705
|
+
interval = @timers.after(timeout, callback)
|
|
706
|
+
|
|
707
|
+
Array(finish_events).each do |event|
|
|
708
|
+
# clean up request timeouts if the connection errors out
|
|
709
|
+
request.once(event) do
|
|
710
|
+
if @intervals.include?(interval)
|
|
711
|
+
interval.delete(callback)
|
|
712
|
+
@intervals.delete(interval) if interval.no_callbacks?
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
@intervals << interval
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
class << self
|
|
722
|
+
def parser_type(protocol)
|
|
723
|
+
case protocol
|
|
724
|
+
when "h2" then HTTP2
|
|
725
|
+
when "http/1.1" then HTTP1
|
|
726
|
+
else
|
|
727
|
+
raise Error, "unsupported protocol (##{protocol})"
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
end
|
|
638
731
|
end
|
|
639
732
|
end
|
data/lib/httpx/domain_name.rb
CHANGED
|
@@ -51,8 +51,6 @@ module HTTPX
|
|
|
51
51
|
# non-canonical domain.
|
|
52
52
|
attr_reader :domain
|
|
53
53
|
|
|
54
|
-
DOT = "." # :nodoc:
|
|
55
|
-
|
|
56
54
|
class << self
|
|
57
55
|
def new(domain)
|
|
58
56
|
return domain if domain.is_a?(self)
|
|
@@ -63,8 +61,12 @@ module HTTPX
|
|
|
63
61
|
# Normalizes a _domain_ using the Punycode algorithm as necessary.
|
|
64
62
|
# The result will be a downcased, ASCII-only string.
|
|
65
63
|
def normalize(domain)
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
unless domain.ascii_only?
|
|
65
|
+
domain = domain.chomp(".").unicode_normalize(:nfc)
|
|
66
|
+
domain = Punycode.encode_hostname(domain)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
domain.downcase
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
|
@@ -73,7 +75,7 @@ module HTTPX
|
|
|
73
75
|
def initialize(hostname)
|
|
74
76
|
hostname = String(hostname)
|
|
75
77
|
|
|
76
|
-
raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(
|
|
78
|
+
raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(".")
|
|
77
79
|
|
|
78
80
|
begin
|
|
79
81
|
@ipaddr = IPAddr.new(hostname)
|
|
@@ -84,7 +86,7 @@ module HTTPX
|
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
@hostname = DomainName.normalize(hostname)
|
|
87
|
-
tld = if (last_dot = @hostname.rindex(
|
|
89
|
+
tld = if (last_dot = @hostname.rindex("."))
|
|
88
90
|
@hostname[(last_dot + 1)..-1]
|
|
89
91
|
else
|
|
90
92
|
@hostname
|
|
@@ -94,7 +96,7 @@ module HTTPX
|
|
|
94
96
|
@domain = if last_dot
|
|
95
97
|
# fallback - accept cookies down to second level
|
|
96
98
|
# cf. http://www.dkim-reputation.org/regdom-libs/
|
|
97
|
-
if (penultimate_dot = @hostname.rindex(
|
|
99
|
+
if (penultimate_dot = @hostname.rindex(".", last_dot - 1))
|
|
98
100
|
@hostname[(penultimate_dot + 1)..-1]
|
|
99
101
|
else
|
|
100
102
|
@hostname
|
|
@@ -126,17 +128,12 @@ module HTTPX
|
|
|
126
128
|
@domain && self <= domain && domain <= @domain
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
# def ==(other)
|
|
130
|
-
# other = DomainName.new(other)
|
|
131
|
-
# other.hostname == @hostname
|
|
132
|
-
# end
|
|
133
|
-
|
|
134
131
|
def <=>(other)
|
|
135
132
|
other = DomainName.new(other)
|
|
136
133
|
othername = other.hostname
|
|
137
134
|
if othername == @hostname
|
|
138
135
|
0
|
|
139
|
-
elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] ==
|
|
136
|
+
elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] == "."
|
|
140
137
|
# The other is higher
|
|
141
138
|
-1
|
|
142
139
|
else
|