httpx 1.1.5 → 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_2_0.md +49 -0
- data/lib/httpx/adapters/webmock.rb +25 -3
- 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.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 +1 -1
- 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 +3 -5
- 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/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/pool.rbs +1 -3
- data/sig/resolver/resolver.rbs +3 -1
- data/sig/session.rbs +4 -4
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3df88a59256b85aacf9d98578c965626b3bfa4611695ae21a707252df71cb0a
|
4
|
+
data.tar.gz: f1616869724c217272d0dc165130548edc8ca35eadd97ab42d5a17012a0019d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4ca8efc0a1ffe3dec2d2d6ee345bcb58bc2eec9e4c5b4fe951a889041be8eeaeb4b005d9771f0e712f161dad69f8de25d07d520c3e0cd17ae1574f53fadace1
|
7
|
+
data.tar.gz: 509c48d74aafb520e3142c5f9b2356e2f36bf9d649eb723ca07a2863077057ab12ae2e17dbf61f13149eac4f80201252dad9b46cb904f5664b8910b62a868c1d
|
data/README.md
CHANGED
@@ -61,7 +61,7 @@ puts body #=> #<HTTPX::Response ...
|
|
61
61
|
You can also send as many requests as you want simultaneously:
|
62
62
|
|
63
63
|
```ruby
|
64
|
-
page1, page2, page3
|
64
|
+
page1, page2, page3 = HTTPX.get("https://news.ycombinator.com/news", "https://news.ycombinator.com/news?p=2", "https://news.ycombinator.com/news?p=3")
|
65
65
|
```
|
66
66
|
|
67
67
|
## Installation
|
@@ -107,22 +107,22 @@ HTTPX.get(
|
|
107
107
|
|
108
108
|
```ruby
|
109
109
|
response = HTTPX.get("https://www.google.com", params: { q: "me" })
|
110
|
-
response = HTTPX.post("https://www.nghttp2.org/httpbin/post", form: {name: "John", age: "22"})
|
110
|
+
response = HTTPX.post("https://www.nghttp2.org/httpbin/post", form: { name: "John", age: "22" })
|
111
111
|
response = HTTPX.plugin(:basic_auth)
|
112
112
|
.basic_auth("user", "pass")
|
113
113
|
.get("https://www.google.com")
|
114
114
|
|
115
115
|
# more complex client objects can be cached, and are thread-safe
|
116
|
-
http = HTTPX.plugin(:expect).with(headers: { "x-pvt-token" => "TOKEN"})
|
116
|
+
http = HTTPX.plugin(:expect).with(headers: { "x-pvt-token" => "TOKEN" })
|
117
117
|
http.get("https://example.com") # the above options will apply
|
118
|
-
http.post("https://example2.com",
|
118
|
+
http.post("https://example2.com", form: { name: "John", age: "22" }) # same, plus the form POST body
|
119
119
|
```
|
120
120
|
|
121
121
|
### Lightweight
|
122
122
|
|
123
123
|
It ships with most features published as a plugin, making vanilla `httpx` lightweight and dependency-free, while allowing you to "pay for what you use"
|
124
124
|
|
125
|
-
The plugin system is similar to the ones used by [sequel](https://github.com/jeremyevans/sequel), [roda](https://github.com/jeremyevans/roda) or [shrine](https://github.com/
|
125
|
+
The plugin system is similar to the ones used by [sequel](https://github.com/jeremyevans/sequel), [roda](https://github.com/jeremyevans/roda) or [shrine](https://github.com/shrinerb/shrine).
|
126
126
|
|
127
127
|
### Advanced DNS features
|
128
128
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# 1.2.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### `:ssrf_filter` plugin
|
6
|
+
|
7
|
+
The `:ssrf_filter` p plugin prevents server-side request forgery attacks, by blocking requests to the internal network. This is useful when the URLs used to perform requests aren’t under the developer control (such as when they are inserted via a web application form).
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
http = HTTPX.plugin(:ssrf_filter)
|
11
|
+
|
12
|
+
# this works
|
13
|
+
response = http.get("https://example.com")
|
14
|
+
|
15
|
+
# this doesn't
|
16
|
+
response = http.get("http://localhost:3002")
|
17
|
+
response = http.get("http://[::1]:3002")
|
18
|
+
response = http.get("http://169.254.169.254/latest/meta-data/")
|
19
|
+
```
|
20
|
+
|
21
|
+
More info under https://honeyryderchuck.gitlab.io/httpx/wiki/SSRF-Filter
|
22
|
+
|
23
|
+
### `:callbacks` plugin
|
24
|
+
|
25
|
+
The session callbacks introduced in v0.24.0 are in its own plugin. Older code will still work and emit a deprecation warning.
|
26
|
+
|
27
|
+
More info under https://honeyryderchuck.gitlab.io/httpx/wiki/Callbacks
|
28
|
+
|
29
|
+
### `:redirect_on` option for `:follow_redirects` plugin
|
30
|
+
|
31
|
+
This option allows passing a callback which, when returning `false`, can interrupt the redirect loop.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
http = HTTPX.plugin(:follow_redirects).with(redirect_on: ->(location_uri) { BLACKLIST_HOSTS.include?(location_uri.host) ]
|
35
|
+
```
|
36
|
+
|
37
|
+
### `:close_on_handshake_timeout` timeout
|
38
|
+
|
39
|
+
A new `:timeout` option, `:close_handshake_timeout`, is added, which monitors connection readiness when performing HTTP/2 connection termination handshake.
|
40
|
+
|
41
|
+
## Improvements
|
42
|
+
|
43
|
+
* Internal "eden connections" concept was removed, and connection objects are now kept-and-reused during the lifetime of a session, even when closed. This simplified connectio pool implementation and improved performance.
|
44
|
+
* request using `:proxy` and `:retries` plugin enabled sessions will now retry on proxy connection establishment related errors.
|
45
|
+
|
46
|
+
## Bugfixes
|
47
|
+
|
48
|
+
* webmock adapter: mocked responses storing decoded payloads won't try to decode them again (fixes vcr/webmock integrations).
|
49
|
+
* webmock adapter: fix issue related with making real requests over webmock-enabled connection.
|
@@ -41,7 +41,9 @@ module WebMock
|
|
41
41
|
request.options.response_class.new(request,
|
42
42
|
webmock_response.status[0],
|
43
43
|
"2.0",
|
44
|
-
webmock_response.headers)
|
44
|
+
webmock_response.headers).tap do |res|
|
45
|
+
res.mocked = true
|
46
|
+
end
|
45
47
|
end
|
46
48
|
|
47
49
|
def build_error_response(request, exception)
|
@@ -50,16 +52,36 @@ module WebMock
|
|
50
52
|
end
|
51
53
|
|
52
54
|
module InstanceMethods
|
53
|
-
def
|
55
|
+
def init_connection(*)
|
54
56
|
connection = super
|
55
57
|
connection.once(:unmock_connection) do
|
58
|
+
unless connection.addresses
|
59
|
+
connection.__send__(:callbacks)[:connect_error].clear
|
60
|
+
pool.__send__(:unregister_connection, connection)
|
61
|
+
end
|
56
62
|
pool.__send__(:resolve_connection, connection)
|
57
|
-
pool.__send__(:unregister_connection, connection) unless connection.addresses
|
58
63
|
end
|
59
64
|
connection
|
60
65
|
end
|
61
66
|
end
|
62
67
|
|
68
|
+
module ResponseMethods
|
69
|
+
attr_accessor :mocked
|
70
|
+
|
71
|
+
def initialize(*)
|
72
|
+
super
|
73
|
+
@mocked = false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module ResponseBodyMethods
|
78
|
+
def decode_chunk(chunk)
|
79
|
+
return chunk if @response.mocked
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
63
85
|
module ConnectionMethods
|
64
86
|
def initialize(*)
|
65
87
|
super
|
data/lib/httpx/altsvc.rb
CHANGED
@@ -4,6 +4,58 @@ require "strscan"
|
|
4
4
|
|
5
5
|
module HTTPX
|
6
6
|
module AltSvc
|
7
|
+
# makes connections able to accept requests destined to primary service.
|
8
|
+
module ConnectionMixin
|
9
|
+
using URIExtensions
|
10
|
+
|
11
|
+
def send(request)
|
12
|
+
request.headers["alt-used"] = @origin.authority if @parser && !@write_buffer.full? && match_altsvcs?(request.uri)
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def match?(uri, options)
|
18
|
+
return false if !used? && (@state == :closing || @state == :closed)
|
19
|
+
|
20
|
+
match_altsvcs?(uri) && match_altsvc_options?(uri, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# checks if this is connection is an alternative service of
|
26
|
+
# +uri+
|
27
|
+
def match_altsvcs?(uri)
|
28
|
+
@origins.any? { |origin| altsvc_match?(uri, origin) } ||
|
29
|
+
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
30
|
+
origin = altsvc["origin"]
|
31
|
+
altsvc_match?(origin, uri.origin)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def match_altsvc_options?(uri, options)
|
36
|
+
return @options == options unless @options.ssl.all? do |k, v|
|
37
|
+
v == (k == :hostname ? uri.host : options.ssl[k])
|
38
|
+
end
|
39
|
+
|
40
|
+
@options.options_equals?(options, Options::REQUEST_BODY_IVARS + %i[@ssl])
|
41
|
+
end
|
42
|
+
|
43
|
+
def altsvc_match?(uri, other_uri)
|
44
|
+
other_uri = URI(other_uri)
|
45
|
+
|
46
|
+
uri.origin == other_uri.origin || begin
|
47
|
+
case uri.scheme
|
48
|
+
when "h2"
|
49
|
+
(other_uri.scheme == "https" || other_uri.scheme == "h2") &&
|
50
|
+
uri.host == other_uri.host &&
|
51
|
+
uri.port == other_uri.port
|
52
|
+
else
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
7
59
|
@altsvc_mutex = Thread::Mutex.new
|
8
60
|
@altsvcs = Hash.new { |h, k| h[k] = [] }
|
9
61
|
|
@@ -46,7 +98,7 @@ module HTTPX
|
|
46
98
|
|
47
99
|
altsvc = response.headers["alt-svc"]
|
48
100
|
|
49
|
-
# https://
|
101
|
+
# https://datatracker.ietf.org/doc/html/rfc7838#section-3
|
50
102
|
# A field value containing the special value "clear" indicates that the
|
51
103
|
# origin requests all alternatives for that origin to be invalidated
|
52
104
|
# (including those specified in the same response, in case of an
|
@@ -99,7 +151,10 @@ module HTTPX
|
|
99
151
|
end
|
100
152
|
|
101
153
|
def parse_altsvc_origin(alt_proto, alt_origin)
|
102
|
-
alt_scheme = parse_altsvc_scheme(alt_proto)
|
154
|
+
alt_scheme = parse_altsvc_scheme(alt_proto)
|
155
|
+
|
156
|
+
return unless alt_scheme
|
157
|
+
|
103
158
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
104
159
|
|
105
160
|
URI.parse("#{alt_scheme}://#{alt_origin}")
|
data/lib/httpx/chainable.rb
CHANGED
@@ -10,19 +10,6 @@ module HTTPX
|
|
10
10
|
MOD
|
11
11
|
end
|
12
12
|
|
13
|
-
%i[
|
14
|
-
connection_opened connection_closed
|
15
|
-
request_error
|
16
|
-
request_started request_body_chunk request_completed
|
17
|
-
response_started response_body_chunk response_completed
|
18
|
-
].each do |meth|
|
19
|
-
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
20
|
-
def on_#{meth}(&blk) # def on_connection_opened(&blk)
|
21
|
-
on(:#{meth}, &blk) # on(:connection_opened, &blk)
|
22
|
-
end # end
|
23
|
-
MOD
|
24
|
-
end
|
25
|
-
|
26
13
|
def request(*args, **options)
|
27
14
|
branch(default_options).request(*args, **options)
|
28
15
|
end
|
@@ -46,12 +33,6 @@ module HTTPX
|
|
46
33
|
branch(default_options.merge(options), &blk)
|
47
34
|
end
|
48
35
|
|
49
|
-
protected
|
50
|
-
|
51
|
-
def on(*args, &blk)
|
52
|
-
branch(default_options).on(*args, &blk)
|
53
|
-
end
|
54
|
-
|
55
36
|
private
|
56
37
|
|
57
38
|
def default_options
|
@@ -64,22 +45,52 @@ module HTTPX
|
|
64
45
|
Session.new(options, &blk)
|
65
46
|
end
|
66
47
|
|
67
|
-
def method_missing(meth, *args, **options)
|
68
|
-
|
48
|
+
def method_missing(meth, *args, **options, &blk)
|
49
|
+
case meth
|
50
|
+
when /\Awith_(.+)/
|
69
51
|
|
70
|
-
|
52
|
+
option = Regexp.last_match(1)
|
71
53
|
|
72
|
-
|
54
|
+
return super unless option
|
73
55
|
|
74
|
-
|
75
|
-
|
56
|
+
with(option.to_sym => args.first || options)
|
57
|
+
when /\Aon_(.+)/
|
58
|
+
callback = Regexp.last_match(1)
|
76
59
|
|
77
|
-
|
78
|
-
|
60
|
+
return super unless %w[
|
61
|
+
connection_opened connection_closed
|
62
|
+
request_error
|
63
|
+
request_started request_body_chunk request_completed
|
64
|
+
response_started response_body_chunk response_completed
|
65
|
+
].include?(callback)
|
79
66
|
|
80
|
-
|
67
|
+
warn "DEPRECATION WARNING: calling `.#{meth}` on plain HTTPX sessions is deprecated. " \
|
68
|
+
"Use HTTPX.plugin(:callbacks).#{meth} instead."
|
81
69
|
|
82
|
-
|
70
|
+
plugin(:callbacks).__send__(meth, *args, **options, &blk)
|
71
|
+
else
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def respond_to_missing?(meth, *)
|
77
|
+
case meth
|
78
|
+
when /\Awith_(.+)/
|
79
|
+
option = Regexp.last_match(1)
|
80
|
+
|
81
|
+
default_options.respond_to?(option) || super
|
82
|
+
when /\Aon_(.+)/
|
83
|
+
callback = Regexp.last_match(1)
|
84
|
+
|
85
|
+
%w[
|
86
|
+
connection_opened connection_closed
|
87
|
+
request_error
|
88
|
+
request_started request_body_chunk request_completed
|
89
|
+
response_started response_body_chunk response_completed
|
90
|
+
].include?(callback) || super
|
91
|
+
else
|
92
|
+
super
|
93
|
+
end
|
83
94
|
end
|
84
95
|
end
|
85
96
|
end
|
@@ -12,6 +12,8 @@ module HTTPX
|
|
12
12
|
|
13
13
|
attr_reader :pending, :requests
|
14
14
|
|
15
|
+
attr_accessor :max_concurrent_requests
|
16
|
+
|
15
17
|
def initialize(buffer, options)
|
16
18
|
@options = Options.new(options)
|
17
19
|
@max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
|
@@ -47,6 +49,7 @@ module HTTPX
|
|
47
49
|
@max_requests = @options.max_requests || MAX_REQUESTS
|
48
50
|
@parser.reset!
|
49
51
|
@handshake_completed = false
|
52
|
+
@pending.concat(@requests) unless @requests.empty?
|
50
53
|
end
|
51
54
|
|
52
55
|
def close
|
@@ -218,6 +221,7 @@ module HTTPX
|
|
218
221
|
end
|
219
222
|
|
220
223
|
def ping
|
224
|
+
reset
|
221
225
|
emit(:reset)
|
222
226
|
emit(:exhausted)
|
223
227
|
end
|
@@ -262,6 +266,7 @@ module HTTPX
|
|
262
266
|
|
263
267
|
def disable
|
264
268
|
disable_pipelining
|
269
|
+
reset
|
265
270
|
emit(:reset)
|
266
271
|
throw(:called)
|
267
272
|
end
|
@@ -292,29 +297,31 @@ module HTTPX
|
|
292
297
|
request.body.chunk!
|
293
298
|
end
|
294
299
|
|
295
|
-
|
300
|
+
extra_headers = {}
|
296
301
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
# when it's not a persistent connection, it sets "Connection: close" always
|
307
|
-
# on the last request of the possible batch (either allowed max requests,
|
308
|
-
# or if smaller, the size of the batch itself)
|
309
|
-
requests_limit = [@max_requests, @requests.size].min
|
310
|
-
if request == @requests[requests_limit - 1]
|
311
|
-
"close"
|
302
|
+
unless request.headers.key?("connection")
|
303
|
+
connection_value = if request.persistent?
|
304
|
+
# when in a persistent connection, the request can't be at
|
305
|
+
# the edge of a renegotiation
|
306
|
+
if @requests.index(request) + 1 < @max_requests
|
307
|
+
"keep-alive"
|
308
|
+
else
|
309
|
+
"close"
|
310
|
+
end
|
312
311
|
else
|
313
|
-
"
|
312
|
+
# when it's not a persistent connection, it sets "Connection: close" always
|
313
|
+
# on the last request of the possible batch (either allowed max requests,
|
314
|
+
# or if smaller, the size of the batch itself)
|
315
|
+
requests_limit = [@max_requests, @requests.size].min
|
316
|
+
if request == @requests[requests_limit - 1]
|
317
|
+
"close"
|
318
|
+
else
|
319
|
+
"keep-alive"
|
320
|
+
end
|
314
321
|
end
|
315
|
-
end
|
316
322
|
|
317
|
-
|
323
|
+
extra_headers["connection"] = connection_value
|
324
|
+
end
|
318
325
|
extra_headers["host"] = request.authority unless request.headers.key?("host")
|
319
326
|
extra_headers
|
320
327
|
end
|
@@ -370,12 +377,10 @@ module HTTPX
|
|
370
377
|
end
|
371
378
|
|
372
379
|
def join_headers2(headers)
|
373
|
-
buffer = "".b
|
374
380
|
headers.each do |field, value|
|
375
|
-
buffer
|
381
|
+
buffer = "#{capitalized(field)}: #{value}#{CRLF}"
|
376
382
|
log(color: :yellow) { "<- HEADER: #{buffer.chomp}" }
|
377
383
|
@buffer << buffer
|
378
|
-
buffer.clear
|
379
384
|
end
|
380
385
|
end
|
381
386
|
|
@@ -55,7 +55,7 @@ module HTTPX
|
|
55
55
|
return :w
|
56
56
|
end
|
57
57
|
|
58
|
-
unless
|
58
|
+
unless @connection.state == :connected && @handshake_completed
|
59
59
|
return @buffer.empty? ? :r : :rw
|
60
60
|
end
|
61
61
|
|
@@ -73,8 +73,11 @@ module HTTPX
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def close
|
76
|
-
|
77
|
-
|
76
|
+
unless @connection.state == :closed
|
77
|
+
@connection.goaway
|
78
|
+
emit(:timeout, @options.timeout[:close_handshake_timeout])
|
79
|
+
end
|
80
|
+
emit(:close, true)
|
78
81
|
end
|
79
82
|
|
80
83
|
def empty?
|
@@ -147,6 +150,7 @@ module HTTPX
|
|
147
150
|
|
148
151
|
def send_pending
|
149
152
|
while (request = @pending.shift)
|
153
|
+
# TODO: this request should go back to top of stack
|
150
154
|
break unless send(request)
|
151
155
|
end
|
152
156
|
end
|
data/lib/httpx/connection.rb
CHANGED
@@ -47,11 +47,11 @@ module HTTPX
|
|
47
47
|
|
48
48
|
attr_accessor :family
|
49
49
|
|
50
|
-
def initialize(
|
51
|
-
@type = type
|
50
|
+
def initialize(uri, options)
|
52
51
|
@origins = [uri.origin]
|
53
52
|
@origin = Utils.to_uri(uri.origin)
|
54
53
|
@options = Options.new(options)
|
54
|
+
@type = initialize_type(uri, @options)
|
55
55
|
@window_size = @options.window_size
|
56
56
|
@read_buffer = Buffer.new(@options.buffer_size)
|
57
57
|
@write_buffer = Buffer.new(@options.buffer_size)
|
@@ -92,18 +92,14 @@ module HTTPX
|
|
92
92
|
def match?(uri, options)
|
93
93
|
return false if !used? && (@state == :closing || @state == :closed)
|
94
94
|
|
95
|
-
return false if exhausted?
|
96
|
-
|
97
95
|
(
|
98
|
-
(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
) && @options == options
|
106
|
-
) || (match_altsvcs?(uri) && match_altsvc_options?(uri, options))
|
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
|
107
103
|
end
|
108
104
|
|
109
105
|
def expired?
|
@@ -115,8 +111,6 @@ module HTTPX
|
|
115
111
|
def mergeable?(connection)
|
116
112
|
return false if @state == :closing || @state == :closed || !@io
|
117
113
|
|
118
|
-
return false if exhausted?
|
119
|
-
|
120
114
|
return false unless connection.addresses
|
121
115
|
|
122
116
|
(
|
@@ -139,7 +133,7 @@ module HTTPX
|
|
139
133
|
end
|
140
134
|
|
141
135
|
def create_idle(options = {})
|
142
|
-
self.class.new(@
|
136
|
+
self.class.new(@origin, @options.merge(options))
|
143
137
|
end
|
144
138
|
|
145
139
|
def merge(connection)
|
@@ -167,24 +161,6 @@ module HTTPX
|
|
167
161
|
end
|
168
162
|
end
|
169
163
|
|
170
|
-
# checks if this is connection is an alternative service of
|
171
|
-
# +uri+
|
172
|
-
def match_altsvcs?(uri)
|
173
|
-
@origins.any? { |origin| uri.altsvc_match?(origin) } ||
|
174
|
-
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
175
|
-
origin = altsvc["origin"]
|
176
|
-
origin.altsvc_match?(uri.origin)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def match_altsvc_options?(uri, options)
|
181
|
-
return @options == options unless @options.ssl[:hostname] == uri.host
|
182
|
-
|
183
|
-
dup_options = @options.merge(ssl: { hostname: nil })
|
184
|
-
dup_options.ssl.delete(:hostname)
|
185
|
-
dup_options == options
|
186
|
-
end
|
187
|
-
|
188
164
|
def connecting?
|
189
165
|
@state == :idle
|
190
166
|
end
|
@@ -223,7 +199,6 @@ module HTTPX
|
|
223
199
|
when :closing
|
224
200
|
consume
|
225
201
|
transition(:closed)
|
226
|
-
emit(:close)
|
227
202
|
when :open
|
228
203
|
consume
|
229
204
|
end
|
@@ -236,24 +211,33 @@ module HTTPX
|
|
236
211
|
@parser.close if @parser
|
237
212
|
end
|
238
213
|
|
214
|
+
def terminate
|
215
|
+
@connected_at = nil if @state == :closed
|
216
|
+
|
217
|
+
close
|
218
|
+
end
|
219
|
+
|
239
220
|
# bypasses the state machine to force closing of connections still connecting.
|
240
221
|
# **only** used for Happy Eyeballs v2.
|
241
222
|
def force_reset
|
242
223
|
@state = :closing
|
243
224
|
transition(:closed)
|
244
|
-
emit(:close)
|
245
225
|
end
|
246
226
|
|
247
227
|
def reset
|
228
|
+
return if @state == :closing || @state == :closed
|
229
|
+
|
248
230
|
transition(:closing)
|
231
|
+
unless @write_buffer.empty?
|
232
|
+
# handshakes, try sending
|
233
|
+
consume
|
234
|
+
@write_buffer.clear
|
235
|
+
end
|
249
236
|
transition(:closed)
|
250
|
-
emit(:close)
|
251
237
|
end
|
252
238
|
|
253
239
|
def send(request)
|
254
240
|
if @parser && !@write_buffer.full?
|
255
|
-
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
256
|
-
|
257
241
|
if @response_received_at && @keep_alive_timeout &&
|
258
242
|
Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
|
259
243
|
# when pushing a request into an existing connection, we have to check whether there
|
@@ -319,10 +303,6 @@ module HTTPX
|
|
319
303
|
transition(:open)
|
320
304
|
end
|
321
305
|
|
322
|
-
def exhausted?
|
323
|
-
@parser && parser.exhausted?
|
324
|
-
end
|
325
|
-
|
326
306
|
def consume
|
327
307
|
return unless @io
|
328
308
|
|
@@ -497,34 +477,25 @@ module HTTPX
|
|
497
477
|
request.emit(:promise, parser, stream)
|
498
478
|
end
|
499
479
|
parser.on(:exhausted) do
|
480
|
+
@pending.concat(parser.pending)
|
500
481
|
emit(:exhausted)
|
501
482
|
end
|
502
483
|
parser.on(:origin) do |origin|
|
503
484
|
@origins |= [origin]
|
504
485
|
end
|
505
486
|
parser.on(:close) do |force|
|
506
|
-
if
|
507
|
-
|
508
|
-
|
509
|
-
transition(:closed)
|
510
|
-
emit(:close)
|
511
|
-
end
|
487
|
+
if force
|
488
|
+
reset
|
489
|
+
emit(:terminate)
|
512
490
|
end
|
513
491
|
end
|
514
492
|
parser.on(:close_handshake) do
|
515
493
|
consume
|
516
494
|
end
|
517
495
|
parser.on(:reset) do
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
transition(:closing)
|
522
|
-
transition(:closed)
|
523
|
-
|
524
|
-
@parser.reset if @parser
|
525
|
-
transition(:idle)
|
526
|
-
transition(:open)
|
527
|
-
end
|
496
|
+
@pending.concat(parser.pending) unless parser.empty?
|
497
|
+
reset
|
498
|
+
idling unless @pending.empty?
|
528
499
|
end
|
529
500
|
parser.on(:current_timeout) do
|
530
501
|
@current_timeout = @timeout = parser.timeout
|
@@ -593,13 +564,14 @@ module HTTPX
|
|
593
564
|
when :inactive
|
594
565
|
return unless @state == :open
|
595
566
|
when :closing
|
596
|
-
return unless @state == :open
|
567
|
+
return unless @state == :idle || @state == :open
|
597
568
|
|
598
569
|
when :closed
|
599
570
|
return unless @state == :closing
|
600
571
|
return unless @write_buffer.empty?
|
601
572
|
|
602
573
|
purge_after_closed
|
574
|
+
emit(:close) if @pending.empty?
|
603
575
|
when :already_open
|
604
576
|
nextstate = :open
|
605
577
|
# the first check for given io readiness must still use a timeout.
|
@@ -621,6 +593,19 @@ module HTTPX
|
|
621
593
|
@timeout = nil
|
622
594
|
end
|
623
595
|
|
596
|
+
def initialize_type(uri, options)
|
597
|
+
options.transport || begin
|
598
|
+
case uri.scheme
|
599
|
+
when "http"
|
600
|
+
"tcp"
|
601
|
+
when "https"
|
602
|
+
"ssl"
|
603
|
+
else
|
604
|
+
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
624
609
|
def build_socket(addrs = nil)
|
625
610
|
case @type
|
626
611
|
when "tcp"
|
data/lib/httpx/extensions.rb
CHANGED
@@ -54,21 +54,6 @@ module HTTPX
|
|
54
54
|
def origin
|
55
55
|
"#{scheme}://#{authority}"
|
56
56
|
end unless URI::HTTP.method_defined?(:origin)
|
57
|
-
|
58
|
-
def altsvc_match?(uri)
|
59
|
-
uri = URI.parse(uri)
|
60
|
-
|
61
|
-
origin == uri.origin || begin
|
62
|
-
case scheme
|
63
|
-
when "h2"
|
64
|
-
(uri.scheme == "https" || uri.scheme == "h2") &&
|
65
|
-
host == uri.host &&
|
66
|
-
(port || default_port) == (uri.port || uri.default_port)
|
67
|
-
else
|
68
|
-
false
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
57
|
end
|
73
58
|
end
|
74
59
|
end
|