httpx 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/doc/release_notes/1_1_4.md +1 -1
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/lib/httpx/adapters/webmock.rb +29 -8
- data/lib/httpx/altsvc.rb +57 -2
- data/lib/httpx/chainable.rb +40 -29
- data/lib/httpx/connection/http1.rb +27 -22
- data/lib/httpx/connection/http2.rb +7 -3
- data/lib/httpx/connection.rb +45 -60
- data/lib/httpx/extensions.rb +0 -15
- data/lib/httpx/options.rb +84 -27
- data/lib/httpx/plugins/aws_sigv4.rb +2 -2
- data/lib/httpx/plugins/basic_auth.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker.rb +2 -0
- data/lib/httpx/plugins/cookies.rb +19 -9
- data/lib/httpx/plugins/digest_auth.rb +1 -1
- data/lib/httpx/plugins/follow_redirects.rb +11 -0
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -0
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/h2c.rb +20 -8
- data/lib/httpx/plugins/proxy/socks4.rb +2 -2
- data/lib/httpx/plugins/proxy/socks5.rb +2 -2
- data/lib/httpx/plugins/proxy.rb +14 -32
- data/lib/httpx/plugins/rate_limiter.rb +1 -1
- data/lib/httpx/plugins/retries.rb +4 -0
- data/lib/httpx/plugins/ssrf_filter.rb +142 -0
- data/lib/httpx/plugins/stream.rb +9 -4
- data/lib/httpx/plugins/upgrade/h2.rb +1 -1
- data/lib/httpx/plugins/upgrade.rb +1 -1
- data/lib/httpx/plugins/webdav.rb +1 -1
- data/lib/httpx/pool.rb +32 -28
- data/lib/httpx/request/body.rb +3 -3
- data/lib/httpx/request.rb +12 -3
- data/lib/httpx/resolver/https.rb +3 -2
- data/lib/httpx/resolver/native.rb +1 -0
- data/lib/httpx/resolver/resolver.rb +17 -6
- data/lib/httpx/response.rb +1 -1
- data/lib/httpx/session.rb +13 -82
- data/lib/httpx/timers.rb +3 -10
- data/lib/httpx/transcoder.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +33 -0
- data/sig/chainable.rbs +1 -0
- data/sig/connection/http1.rbs +2 -1
- data/sig/connection.rbs +16 -16
- data/sig/options.rbs +10 -2
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/follow_redirects.rbs +2 -0
- data/sig/plugins/proxy/socks4.rbs +2 -1
- data/sig/plugins/proxy/socks5.rbs +2 -1
- data/sig/plugins/proxy.rbs +11 -1
- data/sig/plugins/stream.rbs +24 -22
- data/sig/pool.rbs +1 -3
- data/sig/resolver/resolver.rbs +3 -1
- data/sig/session.rbs +4 -4
- metadata +12 -4
data/lib/httpx/plugins/h2c.rb
CHANGED
@@ -4,9 +4,9 @@ module HTTPX
|
|
4
4
|
module Plugins
|
5
5
|
#
|
6
6
|
# This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
|
7
|
-
# (https://
|
7
|
+
# (https://datatracker.ietf.org/doc/html/rfc7540#section-3.2)
|
8
8
|
#
|
9
|
-
# https://gitlab.com/os85/httpx/wikis/Upgrade#h2c
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2c
|
10
10
|
#
|
11
11
|
module H2C
|
12
12
|
VALID_H2C_VERBS = %w[GET OPTIONS HEAD].freeze
|
@@ -73,22 +73,34 @@ module HTTPX
|
|
73
73
|
@inflight -= prev_parser.requests.size
|
74
74
|
end
|
75
75
|
|
76
|
-
|
77
|
-
@parser = H2CParser.new(@write_buffer, parser_options)
|
76
|
+
@parser = H2CParser.new(@write_buffer, @options)
|
78
77
|
set_parser_callbacks(@parser)
|
79
78
|
@inflight += 1
|
80
79
|
@parser.upgrade(request, response)
|
81
80
|
@upgrade_protocol = "h2c"
|
82
81
|
|
83
|
-
if request.options.max_concurrent_requests != @options.max_concurrent_requests
|
84
|
-
@options = @options.merge(max_concurrent_requests: nil)
|
85
|
-
end
|
86
|
-
|
87
82
|
prev_parser.requests.each do |req|
|
88
83
|
req.transition(:idle)
|
89
84
|
send(req)
|
90
85
|
end
|
91
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def send_request_to_parser(request)
|
91
|
+
super
|
92
|
+
|
93
|
+
return unless request.headers["upgrade"] == "h2c" && parser.is_a?(Connection::HTTP1)
|
94
|
+
|
95
|
+
max_concurrent_requests = parser.max_concurrent_requests
|
96
|
+
|
97
|
+
return if max_concurrent_requests == 1
|
98
|
+
|
99
|
+
parser.max_concurrent_requests = 1
|
100
|
+
request.once(:response) do
|
101
|
+
parser.max_concurrent_requests = max_concurrent_requests
|
102
|
+
end
|
103
|
+
end
|
92
104
|
end
|
93
105
|
end
|
94
106
|
register_plugin(:h2c, H2C)
|
@@ -4,7 +4,7 @@ require "resolv"
|
|
4
4
|
require "ipaddr"
|
5
5
|
|
6
6
|
module HTTPX
|
7
|
-
class Socks4Error <
|
7
|
+
class Socks4Error < HTTPProxyError; end
|
8
8
|
|
9
9
|
module Plugins
|
10
10
|
module Proxy
|
@@ -85,7 +85,7 @@ module HTTPX
|
|
85
85
|
end
|
86
86
|
|
87
87
|
class SocksParser
|
88
|
-
include Callbacks
|
88
|
+
include HTTPX::Callbacks
|
89
89
|
|
90
90
|
def initialize(buffer, options)
|
91
91
|
@buffer = buffer
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
-
class Socks5Error <
|
4
|
+
class Socks5Error < HTTPProxyError; end
|
5
5
|
|
6
6
|
module Plugins
|
7
7
|
module Proxy
|
@@ -137,7 +137,7 @@ module HTTPX
|
|
137
137
|
end
|
138
138
|
|
139
139
|
class SocksParser
|
140
|
-
include Callbacks
|
140
|
+
include HTTPX::Callbacks
|
141
141
|
|
142
142
|
def initialize(buffer, options)
|
143
143
|
@buffer = buffer
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
-
class HTTPProxyError <
|
4
|
+
class HTTPProxyError < ConnectionError; end
|
5
5
|
|
6
6
|
module Plugins
|
7
7
|
#
|
@@ -143,7 +143,7 @@ module HTTPX
|
|
143
143
|
proxy = Parameters.new(**proxy_opts)
|
144
144
|
|
145
145
|
proxy_options = options.merge(proxy: proxy)
|
146
|
-
connection = pool.find_connection(uri, proxy_options) ||
|
146
|
+
connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
|
147
147
|
unless connections.nil? || connections.include?(connection)
|
148
148
|
connections << connection
|
149
149
|
set_connection_callbacks(connection, connections, options)
|
@@ -151,19 +151,15 @@ module HTTPX
|
|
151
151
|
connection
|
152
152
|
end
|
153
153
|
|
154
|
-
def build_connection(uri, options)
|
155
|
-
proxy = options.proxy
|
156
|
-
return super unless proxy
|
157
|
-
|
158
|
-
init_connection("tcp", uri, options)
|
159
|
-
end
|
160
|
-
|
161
154
|
def fetch_response(request, connections, options)
|
162
155
|
response = super
|
163
156
|
|
164
|
-
if response.is_a?(ErrorResponse) &&
|
165
|
-
__proxy_error?(response) && !@_proxy_uris.empty?
|
157
|
+
if response.is_a?(ErrorResponse) && proxy_error?(request, response)
|
166
158
|
@_proxy_uris.shift
|
159
|
+
|
160
|
+
# return last error response if no more proxies to try
|
161
|
+
return response if @_proxy_uris.empty?
|
162
|
+
|
167
163
|
log { "failed connecting to proxy, trying next..." }
|
168
164
|
request.transition(:idle)
|
169
165
|
send_request(request, connections, options)
|
@@ -172,13 +168,7 @@ module HTTPX
|
|
172
168
|
response
|
173
169
|
end
|
174
170
|
|
175
|
-
def
|
176
|
-
return if options.proxy
|
177
|
-
|
178
|
-
super
|
179
|
-
end
|
180
|
-
|
181
|
-
def __proxy_error?(response)
|
171
|
+
def proxy_error?(_request, response)
|
182
172
|
error = response.error
|
183
173
|
case error
|
184
174
|
when NativeResolveError
|
@@ -235,14 +225,6 @@ module HTTPX
|
|
235
225
|
end
|
236
226
|
end
|
237
227
|
|
238
|
-
def send(request)
|
239
|
-
return super unless (
|
240
|
-
@options.proxy && @state != :idle && connecting?
|
241
|
-
)
|
242
|
-
|
243
|
-
(@proxy_pending ||= []) << request
|
244
|
-
end
|
245
|
-
|
246
228
|
def connecting?
|
247
229
|
return super unless @options.proxy
|
248
230
|
|
@@ -271,6 +253,12 @@ module HTTPX
|
|
271
253
|
|
272
254
|
private
|
273
255
|
|
256
|
+
def initialize_type(uri, options)
|
257
|
+
return super unless options.proxy
|
258
|
+
|
259
|
+
"tcp"
|
260
|
+
end
|
261
|
+
|
274
262
|
def connect
|
275
263
|
return super unless @options.proxy
|
276
264
|
|
@@ -278,12 +266,6 @@ module HTTPX
|
|
278
266
|
when :idle
|
279
267
|
transition(:connecting)
|
280
268
|
when :connected
|
281
|
-
if @proxy_pending
|
282
|
-
while (req = @proxy_pendind.shift)
|
283
|
-
send(req)
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
269
|
transition(:open)
|
288
270
|
end
|
289
271
|
end
|
@@ -9,7 +9,7 @@ module HTTPX
|
|
9
9
|
# * when the server is unavailable (503);
|
10
10
|
# * when a 3xx request comes with a "retry-after" value
|
11
11
|
#
|
12
|
-
# https://gitlab.com/os85/httpx/wikis/
|
12
|
+
# https://gitlab.com/os85/httpx/wikis/Rate-Limiter
|
13
13
|
#
|
14
14
|
module RateLimiter
|
15
15
|
class << self
|
@@ -132,6 +132,10 @@ module HTTPX
|
|
132
132
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
|
133
133
|
end
|
134
134
|
|
135
|
+
def proxy_error?(request, response)
|
136
|
+
super && !request.retries.positive?
|
137
|
+
end
|
138
|
+
|
135
139
|
#
|
136
140
|
# Atttempt to set the request to perform a partial range request.
|
137
141
|
# This happens if the peer server accepts byte-range requests, and
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
class ServerSideRequestForgeryError < Error; end
|
5
|
+
|
6
|
+
module Plugins
|
7
|
+
#
|
8
|
+
# This plugin adds support for preventing Server-Side Request Forgery attacks.
|
9
|
+
#
|
10
|
+
# https://gitlab.com/os85/httpx/wikis/Server-Side-Request-Forgery-Filter
|
11
|
+
#
|
12
|
+
module SsrfFilter
|
13
|
+
module IPAddrExtensions
|
14
|
+
refine IPAddr do
|
15
|
+
def prefixlen
|
16
|
+
mask_addr = @mask_addr
|
17
|
+
raise "Invalid mask" if mask_addr.zero?
|
18
|
+
|
19
|
+
mask_addr >>= 1 while (mask_addr & 0x1).zero?
|
20
|
+
|
21
|
+
length = 0
|
22
|
+
while mask_addr & 0x1 == 0x1
|
23
|
+
length += 1
|
24
|
+
mask_addr >>= 1
|
25
|
+
end
|
26
|
+
|
27
|
+
length
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
using IPAddrExtensions
|
33
|
+
|
34
|
+
# https://en.wikipedia.org/wiki/Reserved_IP_addresses
|
35
|
+
IPV4_BLACKLIST = [
|
36
|
+
IPAddr.new("0.0.0.0/8"), # Current network (only valid as source address)
|
37
|
+
IPAddr.new("10.0.0.0/8"), # Private network
|
38
|
+
IPAddr.new("100.64.0.0/10"), # Shared Address Space
|
39
|
+
IPAddr.new("127.0.0.0/8"), # Loopback
|
40
|
+
IPAddr.new("169.254.0.0/16"), # Link-local
|
41
|
+
IPAddr.new("172.16.0.0/12"), # Private network
|
42
|
+
IPAddr.new("192.0.0.0/24"), # IETF Protocol Assignments
|
43
|
+
IPAddr.new("192.0.2.0/24"), # TEST-NET-1, documentation and examples
|
44
|
+
IPAddr.new("192.88.99.0/24"), # IPv6 to IPv4 relay (includes 2002::/16)
|
45
|
+
IPAddr.new("192.168.0.0/16"), # Private network
|
46
|
+
IPAddr.new("198.18.0.0/15"), # Network benchmark tests
|
47
|
+
IPAddr.new("198.51.100.0/24"), # TEST-NET-2, documentation and examples
|
48
|
+
IPAddr.new("203.0.113.0/24"), # TEST-NET-3, documentation and examples
|
49
|
+
IPAddr.new("224.0.0.0/4"), # IP multicast (former Class D network)
|
50
|
+
IPAddr.new("240.0.0.0/4"), # Reserved (former Class E network)
|
51
|
+
IPAddr.new("255.255.255.255"), # Broadcast
|
52
|
+
].freeze
|
53
|
+
|
54
|
+
IPV6_BLACKLIST = ([
|
55
|
+
IPAddr.new("::1/128"), # Loopback
|
56
|
+
IPAddr.new("64:ff9b::/96"), # IPv4/IPv6 translation (RFC 6052)
|
57
|
+
IPAddr.new("100::/64"), # Discard prefix (RFC 6666)
|
58
|
+
IPAddr.new("2001::/32"), # Teredo tunneling
|
59
|
+
IPAddr.new("2001:10::/28"), # Deprecated (previously ORCHID)
|
60
|
+
IPAddr.new("2001:20::/28"), # ORCHIDv2
|
61
|
+
IPAddr.new("2001:db8::/32"), # Addresses used in documentation and example source code
|
62
|
+
IPAddr.new("2002::/16"), # 6to4
|
63
|
+
IPAddr.new("fc00::/7"), # Unique local address
|
64
|
+
IPAddr.new("fe80::/10"), # Link-local address
|
65
|
+
IPAddr.new("ff00::/8"), # Multicast
|
66
|
+
] + IPV4_BLACKLIST.flat_map do |ipaddr|
|
67
|
+
prefixlen = ipaddr.prefixlen
|
68
|
+
|
69
|
+
ipv4_compatible = ipaddr.ipv4_compat.mask(96 + prefixlen)
|
70
|
+
ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
|
71
|
+
|
72
|
+
[ipv4_compatible, ipv4_mapped]
|
73
|
+
end).freeze
|
74
|
+
|
75
|
+
class << self
|
76
|
+
def extra_options(options)
|
77
|
+
options.merge(allowed_schemes: %w[https http])
|
78
|
+
end
|
79
|
+
|
80
|
+
def unsafe_ip_address?(ipaddr)
|
81
|
+
range = ipaddr.to_range
|
82
|
+
return true if range.first != range.last
|
83
|
+
|
84
|
+
return IPV6_BLACKLIST.any? { |r| r.include?(ipaddr) } if ipaddr.ipv6?
|
85
|
+
|
86
|
+
IPV4_BLACKLIST.any? { |r| r.include?(ipaddr) } # then it's IPv4
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module OptionsMethods
|
91
|
+
def option_allowed_schemes(value)
|
92
|
+
Array(value)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
module InstanceMethods
|
97
|
+
def send_requests(*requests)
|
98
|
+
responses = requests.map do |request|
|
99
|
+
next if @options.allowed_schemes.include?(request.uri.scheme)
|
100
|
+
|
101
|
+
error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
|
102
|
+
error.set_backtrace(caller)
|
103
|
+
response = ErrorResponse.new(request, error, request.options)
|
104
|
+
request.emit(:response, response)
|
105
|
+
response
|
106
|
+
end
|
107
|
+
allowed_requests = requests.select { |req| responses[requests.index(req)].nil? }
|
108
|
+
allowed_responses = super(*allowed_requests)
|
109
|
+
allowed_responses.each_with_index do |res, idx|
|
110
|
+
req = allowed_requests[idx]
|
111
|
+
responses[requests.index(req)] = res
|
112
|
+
end
|
113
|
+
|
114
|
+
responses
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module ConnectionMethods
|
119
|
+
def initialize(*)
|
120
|
+
begin
|
121
|
+
super
|
122
|
+
rescue ServerSideRequestForgeryError => e
|
123
|
+
# may raise when IPs are passed as options via :addresses
|
124
|
+
throw(:resolve_error, e)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def addresses=(addrs)
|
129
|
+
addrs = addrs.map { |addr| addr.is_a?(IPAddr) ? addr : IPAddr.new(addr) }
|
130
|
+
|
131
|
+
addrs.reject!(&SsrfFilter.method(:unsafe_ip_address?))
|
132
|
+
|
133
|
+
raise ServerSideRequestForgeryError, "#{@origin.host} has no public IP addresses" if addrs.empty?
|
134
|
+
|
135
|
+
super
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
register_plugin :ssrf_filter, SsrfFilter
|
141
|
+
end
|
142
|
+
end
|
data/lib/httpx/plugins/stream.rb
CHANGED
@@ -5,6 +5,7 @@ module HTTPX
|
|
5
5
|
def initialize(request, session)
|
6
6
|
@request = request
|
7
7
|
@session = session
|
8
|
+
@response = nil
|
8
9
|
end
|
9
10
|
|
10
11
|
def each(&block)
|
@@ -25,7 +26,7 @@ module HTTPX
|
|
25
26
|
def each_line
|
26
27
|
return enum_for(__method__) unless block_given?
|
27
28
|
|
28
|
-
line =
|
29
|
+
line = "".b
|
29
30
|
|
30
31
|
each do |chunk|
|
31
32
|
line << chunk
|
@@ -36,6 +37,8 @@ module HTTPX
|
|
36
37
|
line = line.byteslice(idx + 1..-1)
|
37
38
|
end
|
38
39
|
end
|
40
|
+
|
41
|
+
yield line unless line.empty?
|
39
42
|
end
|
40
43
|
|
41
44
|
# This is a ghost method. It's to be used ONLY internally, when processing streams
|
@@ -58,8 +61,10 @@ module HTTPX
|
|
58
61
|
private
|
59
62
|
|
60
63
|
def response
|
61
|
-
@response
|
62
|
-
|
64
|
+
return @response if @response
|
65
|
+
|
66
|
+
@request.response || begin
|
67
|
+
@response = @session.request(@request)
|
63
68
|
end
|
64
69
|
end
|
65
70
|
|
@@ -78,7 +83,7 @@ module HTTPX
|
|
78
83
|
#
|
79
84
|
# This plugin adds support for stream response (text/event-stream).
|
80
85
|
#
|
81
|
-
# https://gitlab.com/
|
86
|
+
# https://gitlab.com/os85/httpx/wikis/Stream
|
82
87
|
#
|
83
88
|
module Stream
|
84
89
|
def self.extra_options(options)
|
@@ -6,7 +6,7 @@ module HTTPX
|
|
6
6
|
# This plugin adds support for upgrading an HTTP/1.1 connection to HTTP/2
|
7
7
|
# via an Upgrade: h2 response declaration
|
8
8
|
#
|
9
|
-
# https://gitlab.com/
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2
|
10
10
|
#
|
11
11
|
module H2
|
12
12
|
class << self
|
@@ -6,7 +6,7 @@ module HTTPX
|
|
6
6
|
# This plugin helps negotiating a new protocol from an HTTP/1.1 connection, via the
|
7
7
|
# Upgrade header.
|
8
8
|
#
|
9
|
-
# https://gitlab.com/
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Upgrade
|
10
10
|
#
|
11
11
|
module Upgrade
|
12
12
|
class << self
|
data/lib/httpx/plugins/webdav.rb
CHANGED
data/lib/httpx/pool.rb
CHANGED
@@ -17,8 +17,6 @@ module HTTPX
|
|
17
17
|
@timers = Timers.new
|
18
18
|
@selector = Selector.new
|
19
19
|
@connections = []
|
20
|
-
@eden_connections = []
|
21
|
-
@connected_connections = 0
|
22
20
|
end
|
23
21
|
|
24
22
|
def empty?
|
@@ -45,16 +43,15 @@ module HTTPX
|
|
45
43
|
connection.emit(:error, e)
|
46
44
|
end
|
47
45
|
rescue Exception # rubocop:disable Lint/RescueException
|
48
|
-
@connections.each(&:
|
46
|
+
@connections.each(&:force_reset)
|
49
47
|
raise
|
50
48
|
end
|
51
49
|
|
52
50
|
def close(connections = @connections)
|
53
51
|
return if connections.empty?
|
54
52
|
|
55
|
-
@eden_connections.clear
|
56
53
|
connections = connections.reject(&:inflight?)
|
57
|
-
connections.each(&:
|
54
|
+
connections.each(&:terminate)
|
58
55
|
next_tick until connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
59
56
|
|
60
57
|
# close resolvers
|
@@ -68,22 +65,36 @@ module HTTPX
|
|
68
65
|
resolver.close unless resolver.closed?
|
69
66
|
end
|
70
67
|
# for https resolver
|
71
|
-
resolver_connections.each(&:
|
68
|
+
resolver_connections.each(&:terminate)
|
72
69
|
next_tick until resolver_connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
73
70
|
end
|
74
71
|
|
75
72
|
def init_connection(connection, _options)
|
76
|
-
resolve_connection(connection) unless connection.family
|
77
73
|
connection.timers = @timers
|
78
|
-
connection.on(:open) do
|
79
|
-
@connected_connections += 1
|
80
|
-
end
|
81
74
|
connection.on(:activate) do
|
82
75
|
select_connection(connection)
|
83
76
|
end
|
77
|
+
connection.on(:exhausted) do
|
78
|
+
case connection.state
|
79
|
+
when :closed
|
80
|
+
connection.idling
|
81
|
+
@connections << connection
|
82
|
+
select_connection(connection)
|
83
|
+
when :closing
|
84
|
+
connection.once(:close) do
|
85
|
+
connection.idling
|
86
|
+
@connections << connection
|
87
|
+
select_connection(connection)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
84
91
|
connection.on(:close) do
|
85
92
|
unregister_connection(connection)
|
86
93
|
end
|
94
|
+
connection.on(:terminate) do
|
95
|
+
unregister_connection(connection, true)
|
96
|
+
end
|
97
|
+
resolve_connection(connection) unless connection.family
|
87
98
|
end
|
88
99
|
|
89
100
|
def deactivate(connections)
|
@@ -102,16 +113,15 @@ module HTTPX
|
|
102
113
|
connection.match?(uri, options)
|
103
114
|
end
|
104
115
|
|
105
|
-
unless conn
|
106
|
-
@eden_connections.delete_if do |connection|
|
107
|
-
is_expired = connection.expired?
|
108
|
-
conn = connection if conn.nil? && !is_expired && connection.match?(uri, options)
|
109
|
-
is_expired
|
110
|
-
end
|
116
|
+
return unless conn
|
111
117
|
|
112
|
-
|
118
|
+
case conn.state
|
119
|
+
when :closed
|
120
|
+
conn.idling
|
121
|
+
select_connection(conn)
|
122
|
+
when :closing
|
123
|
+
conn.once(:close) do
|
113
124
|
conn.idling
|
114
|
-
@connections << conn
|
115
125
|
select_connection(conn)
|
116
126
|
end
|
117
127
|
end
|
@@ -151,7 +161,7 @@ module HTTPX
|
|
151
161
|
|
152
162
|
return connection if connection.family == family
|
153
163
|
|
154
|
-
new_connection = connection.class.new(connection.
|
164
|
+
new_connection = connection.class.new(connection.origin, connection.options)
|
155
165
|
new_connection.family = family
|
156
166
|
|
157
167
|
connection.once(:tcp_open) { new_connection.force_reset }
|
@@ -218,18 +228,12 @@ module HTTPX
|
|
218
228
|
end
|
219
229
|
|
220
230
|
def register_connection(connection)
|
221
|
-
if connection.state == :open
|
222
|
-
# if open, an IO was passed upstream, therefore
|
223
|
-
# consider it connected already.
|
224
|
-
@connected_connections += 1
|
225
|
-
end
|
226
231
|
select_connection(connection)
|
227
232
|
end
|
228
233
|
|
229
|
-
def unregister_connection(connection)
|
230
|
-
@connections.delete(connection)
|
231
|
-
|
232
|
-
@connected_connections -= 1 if deselect_connection(connection)
|
234
|
+
def unregister_connection(connection, cleanup = !connection.used?)
|
235
|
+
@connections.delete(connection) if cleanup
|
236
|
+
deselect_connection(connection)
|
233
237
|
end
|
234
238
|
|
235
239
|
def select_connection(connection)
|
data/lib/httpx/request/body.rb
CHANGED
@@ -70,9 +70,9 @@ module HTTPX
|
|
70
70
|
|
71
71
|
# sets the body to yield using chunked trannsfer encoding format.
|
72
72
|
def stream(body)
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
return body unless chunked?
|
74
|
+
|
75
|
+
Transcoder::Chunker.encode(body.enum_for(:each))
|
76
76
|
end
|
77
77
|
|
78
78
|
# returns whether the body yields infinitely.
|
data/lib/httpx/request.rb
CHANGED
@@ -119,10 +119,19 @@ module HTTPX
|
|
119
119
|
def response=(response)
|
120
120
|
return unless response
|
121
121
|
|
122
|
-
if response.is_a?(Response) && response.status
|
123
|
-
|
124
|
-
|
122
|
+
if response.is_a?(Response) && response.status < 200
|
123
|
+
# deal with informational responses
|
124
|
+
|
125
|
+
if response.status == 100 && @headers.key?("expect")
|
126
|
+
@informational_status = response.status
|
127
|
+
return
|
128
|
+
end
|
129
|
+
|
130
|
+
# 103 Early Hints advertises resources in document to browsers.
|
131
|
+
# not very relevant for an HTTP client, discard.
|
132
|
+
return if response.status >= 103
|
125
133
|
end
|
134
|
+
|
126
135
|
@response = response
|
127
136
|
|
128
137
|
emit(:response_started, response)
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -27,7 +27,7 @@ module HTTPX
|
|
27
27
|
use_get: false,
|
28
28
|
}.freeze
|
29
29
|
|
30
|
-
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close
|
30
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate
|
31
31
|
|
32
32
|
def initialize(_, options)
|
33
33
|
super
|
@@ -50,6 +50,7 @@ module HTTPX
|
|
50
50
|
if @uri_addresses.empty?
|
51
51
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
52
52
|
ex.set_backtrace(caller)
|
53
|
+
connection.force_reset
|
53
54
|
throw(:resolve_error, ex)
|
54
55
|
end
|
55
56
|
|
@@ -67,7 +68,7 @@ module HTTPX
|
|
67
68
|
def resolver_connection
|
68
69
|
@resolver_connection ||= @pool.find_connection(@uri, @options) || begin
|
69
70
|
@building_connection = true
|
70
|
-
connection = @options.connection_class.new(
|
71
|
+
connection = @options.connection_class.new(@uri, @options.merge(ssl: { alpn_protocols: %w[h2] }))
|
71
72
|
@pool.init_connection(connection, @options)
|
72
73
|
# only explicity emit addresses if connection didn't pre-resolve, i.e. it's not an IP.
|
73
74
|
emit_addresses(connection, @family, @uri_addresses) unless connection.addresses
|
@@ -38,6 +38,8 @@ module HTTPX
|
|
38
38
|
|
39
39
|
def close; end
|
40
40
|
|
41
|
+
alias_method :terminate, :close
|
42
|
+
|
41
43
|
def closed?
|
42
44
|
true
|
43
45
|
end
|
@@ -65,20 +67,29 @@ module HTTPX
|
|
65
67
|
unless connection.state == :closed ||
|
66
68
|
# double emission check
|
67
69
|
(connection.addresses && addresses.intersect?(connection.addresses))
|
68
|
-
emit_resolved_connection(connection, addresses)
|
70
|
+
emit_resolved_connection(connection, addresses, early_resolve)
|
69
71
|
end
|
70
72
|
end
|
71
73
|
else
|
72
|
-
emit_resolved_connection(connection, addresses)
|
74
|
+
emit_resolved_connection(connection, addresses, early_resolve)
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
78
|
private
|
77
79
|
|
78
|
-
def emit_resolved_connection(connection, addresses)
|
79
|
-
|
80
|
-
|
81
|
-
|
80
|
+
def emit_resolved_connection(connection, addresses, early_resolve)
|
81
|
+
begin
|
82
|
+
connection.addresses = addresses
|
83
|
+
|
84
|
+
emit(:resolve, connection)
|
85
|
+
rescue StandardError => e
|
86
|
+
if early_resolve
|
87
|
+
connection.force_reset
|
88
|
+
throw(:resolve_error, e)
|
89
|
+
else
|
90
|
+
emit(:error, connection, e)
|
91
|
+
end
|
92
|
+
end
|
82
93
|
end
|
83
94
|
|
84
95
|
def early_resolve(connection, hostname: connection.origin.host)
|
data/lib/httpx/response.rb
CHANGED