httpx 0.7.0 → 0.10.0
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 +48 -0
- data/README.md +9 -5
- data/doc/release_notes/0_0_1.md +7 -0
- data/doc/release_notes/0_0_2.md +9 -0
- data/doc/release_notes/0_0_3.md +9 -0
- data/doc/release_notes/0_0_4.md +7 -0
- data/doc/release_notes/0_0_5.md +5 -0
- data/doc/release_notes/0_10_0.md +66 -0
- data/doc/release_notes/0_1_0.md +9 -0
- data/doc/release_notes/0_2_0.md +5 -0
- data/doc/release_notes/0_2_1.md +16 -0
- data/doc/release_notes/0_3_0.md +12 -0
- data/doc/release_notes/0_3_1.md +6 -0
- data/doc/release_notes/0_4_0.md +51 -0
- data/doc/release_notes/0_4_1.md +3 -0
- data/doc/release_notes/0_5_0.md +15 -0
- data/doc/release_notes/0_5_1.md +14 -0
- data/doc/release_notes/0_6_0.md +5 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_6_2.md +6 -0
- data/doc/release_notes/0_6_3.md +13 -0
- data/doc/release_notes/0_6_4.md +21 -0
- data/doc/release_notes/0_6_5.md +22 -0
- data/doc/release_notes/0_6_6.md +19 -0
- data/doc/release_notes/0_6_7.md +5 -0
- data/doc/release_notes/0_7_0.md +46 -0
- data/doc/release_notes/0_8_0.md +27 -0
- data/doc/release_notes/0_8_1.md +8 -0
- data/doc/release_notes/0_8_2.md +7 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/lib/httpx.rb +2 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/altsvc.rb +18 -2
- data/lib/httpx/chainable.rb +9 -8
- data/lib/httpx/connection.rb +177 -72
- data/lib/httpx/connection/http1.rb +44 -13
- data/lib/httpx/connection/http2.rb +77 -34
- data/lib/httpx/domain_name.rb +440 -0
- data/lib/httpx/errors.rb +1 -0
- data/lib/httpx/extensions.rb +23 -3
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +11 -4
- data/lib/httpx/io/tcp.rb +16 -5
- data/lib/httpx/io/udp.rb +4 -1
- data/lib/httpx/loggable.rb +6 -6
- data/lib/httpx/options.rb +22 -15
- data/lib/httpx/parser/http1.rb +14 -17
- data/lib/httpx/plugins/compression.rb +49 -64
- data/lib/httpx/plugins/compression/brotli.rb +10 -14
- data/lib/httpx/plugins/compression/deflate.rb +7 -6
- data/lib/httpx/plugins/compression/gzip.rb +45 -17
- data/lib/httpx/plugins/cookies.rb +21 -60
- data/lib/httpx/plugins/cookies/cookie.rb +173 -0
- data/lib/httpx/plugins/cookies/jar.rb +74 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
- data/lib/httpx/plugins/digest_authentication.rb +2 -0
- data/lib/httpx/plugins/expect.rb +12 -1
- data/lib/httpx/plugins/follow_redirects.rb +20 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/multipart.rb +0 -8
- data/lib/httpx/plugins/persistent.rb +6 -1
- data/lib/httpx/plugins/proxy.rb +16 -12
- data/lib/httpx/plugins/proxy/http.rb +7 -2
- data/lib/httpx/plugins/proxy/socks4.rb +4 -2
- data/lib/httpx/plugins/proxy/socks5.rb +5 -1
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/rate_limiter.rb +51 -0
- data/lib/httpx/plugins/retries.rb +13 -6
- data/lib/httpx/plugins/stream.rb +109 -13
- data/lib/httpx/pool.rb +13 -15
- data/lib/httpx/registry.rb +2 -1
- data/lib/httpx/request.rb +14 -19
- data/lib/httpx/resolver.rb +7 -8
- data/lib/httpx/resolver/https.rb +22 -5
- data/lib/httpx/resolver/native.rb +27 -33
- data/lib/httpx/resolver/options.rb +2 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/response.rb +22 -17
- data/lib/httpx/selector.rb +96 -97
- data/lib/httpx/session.rb +32 -24
- data/lib/httpx/timeout.rb +7 -1
- data/lib/httpx/transcoder/chunker.rb +0 -2
- data/lib/httpx/transcoder/form.rb +0 -6
- data/lib/httpx/transcoder/json.rb +0 -4
- data/lib/httpx/utils.rb +45 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/buffer.rbs +24 -0
- data/sig/callbacks.rbs +14 -0
- data/sig/chainable.rbs +37 -0
- data/sig/connection.rbs +2 -0
- data/sig/connection/http2.rbs +4 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +3 -0
- data/sig/headers.rbs +42 -0
- data/sig/httpx.rbs +14 -0
- data/sig/loggable.rbs +11 -0
- data/sig/missing.rbs +12 -0
- data/sig/options.rbs +118 -0
- data/sig/parser/http1.rbs +50 -0
- data/sig/plugins/authentication.rbs +11 -0
- data/sig/plugins/basic_authentication.rbs +13 -0
- data/sig/plugins/compression.rbs +55 -0
- data/sig/plugins/compression/brotli.rbs +21 -0
- data/sig/plugins/compression/deflate.rbs +17 -0
- data/sig/plugins/compression/gzip.rbs +29 -0
- data/sig/plugins/cookies.rbs +26 -0
- data/sig/plugins/cookies/cookie.rbs +50 -0
- data/sig/plugins/cookies/jar.rbs +27 -0
- data/sig/plugins/digest_authentication.rbs +33 -0
- data/sig/plugins/expect.rbs +19 -0
- data/sig/plugins/follow_redirects.rbs +37 -0
- data/sig/plugins/h2c.rbs +26 -0
- data/sig/plugins/multipart.rbs +19 -0
- data/sig/plugins/persistent.rbs +17 -0
- data/sig/plugins/proxy.rbs +47 -0
- data/sig/plugins/proxy/http.rbs +14 -0
- data/sig/plugins/proxy/socks4.rbs +33 -0
- data/sig/plugins/proxy/socks5.rbs +36 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/push_promise.rbs +22 -0
- data/sig/plugins/rate_limiter.rbs +11 -0
- data/sig/plugins/retries.rbs +48 -0
- data/sig/plugins/stream.rbs +39 -0
- data/sig/pool.rbs +2 -0
- data/sig/registry.rbs +9 -0
- data/sig/request.rbs +61 -0
- data/sig/response.rbs +87 -0
- data/sig/session.rbs +49 -0
- data/sig/test.rbs +9 -0
- data/sig/timeout.rbs +29 -0
- data/sig/transcoder.rbs +16 -0
- data/sig/transcoder/body.rbs +18 -0
- data/sig/transcoder/chunker.rbs +32 -0
- data/sig/transcoder/form.rbs +16 -0
- data/sig/transcoder/json.rbs +14 -0
- metadata +120 -21
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 0.6.6
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
* The `retries` plugin receives two new options:
|
|
6
|
+
* `retry_on`: a callable that receives the failed response as an argument; the return value will determine whether there'll be a retried request.
|
|
7
|
+
* `retry_after`: time (in seconds) after which there request will be retried. Can be an integer or a callable that receives the request and returns an integer (one can do exponential back-off like that, for example).
|
|
8
|
+
* Added support for DNS-over-HTTPS GET requests as per the latest spec.
|
|
9
|
+
|
|
10
|
+
## Improvements
|
|
11
|
+
|
|
12
|
+
* `HTTPX.plugins` got deprecated; basically, it's great until you have to pass options to a plugin, and then it just works (not). The recommended way to load multiple plugins is `HTTPX.plugin(...).plugin(...)`.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Bugfixes
|
|
16
|
+
|
|
17
|
+
* fixed a proxy bug where an `Alt-Svc` response header would make the client try to connect. Just like connection coalescing and the ORIGIN frame, it ignores it when going through a proxy.
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# 0.6.7
|
|
2
|
+
|
|
3
|
+
## Bugfixes
|
|
4
|
+
|
|
5
|
+
* An error was reported when using the follow plugin allowing insecure redirects: if the insecure redirect would be for the same host, and the original request was performed with HTTP/2, the library would try to coalesce the request, and blocks the reactor. A check was made to ensure that connection only coalesces if both are https connections.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 0.7.0
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
|
|
6
|
+
New option: `:max_requests`. This is a connection-level option signalizing how many requests can be performed on a connection. Although the HTTP/1 parser defined this well, in HTTP/2 this wasn't very clear, so: by definition, the remote MAX_CONCURRENT_STREAMS setting will be used to define it, unless the user explicitly passed the option. You can also pass `:max_requests => Float::INFINITY` if you know that the server allows more requests than that on a connection.
|
|
7
|
+
|
|
8
|
+
New plugin: `:expect`.
|
|
9
|
+
|
|
10
|
+
Although there was support for `expect: 100-continue` header already when passed, this plugin can:
|
|
11
|
+
|
|
12
|
+
* automatically set the header on requests with body;
|
|
13
|
+
* execute the flow;
|
|
14
|
+
* recover from 417 status errors (i.e. try again without it);
|
|
15
|
+
* send body after X seconds if no 100 response came;
|
|
16
|
+
|
|
17
|
+
Suport for `with_` methods for the session. As long as the suffix is a valid attribute, it's just like that:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
HTTPX.with_timeout(...).with_ssl(...)
|
|
21
|
+
# same as:
|
|
22
|
+
# HTTPX.with(timeout: ..., ssl: ...)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Improvements
|
|
26
|
+
|
|
27
|
+
### Connections
|
|
28
|
+
|
|
29
|
+
The following improvements make the `persistent` plugin way more resilient:
|
|
30
|
+
|
|
31
|
+
* Better balancing of HTTP/2 connections by distributing requests among X connections depending of how many requests they can process.
|
|
32
|
+
* Exhausted connections can off-load to a new same-origin connection (such as, when the server negotiates less `MAX_CONCURRENT_STREAMS` than what's expected).
|
|
33
|
+
|
|
34
|
+
### Timeouts
|
|
35
|
+
|
|
36
|
+
(Timeouts will be one of the main improvements from the 0.7.x series)
|
|
37
|
+
|
|
38
|
+
`:total_timeout` is now a connection-level directive, which means that this feature will actually make more sense and account for all requests in a block at the same time, instead of one-by-one.
|
|
39
|
+
|
|
40
|
+
### Options
|
|
41
|
+
|
|
42
|
+
Option setters were being bypassed, therefore a lot of the type-checks defined there weren't effectively being picked upon, which could have led to weird user errors.
|
|
43
|
+
|
|
44
|
+
## Bugfixes
|
|
45
|
+
|
|
46
|
+
* fixed the `push_promise` plugin integration (wasn't working well since `http-2-next` was adopted);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 0.8.0
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
|
|
6
|
+
* `keep_alive_timeout`: for persistent connections, the keep alive timeout will set the connection to be closed if not reused for a request **after** the last received response;
|
|
7
|
+
|
|
8
|
+
## Improvements
|
|
9
|
+
|
|
10
|
+
* using `max_requests` for HTTP/1 pipelining as well;
|
|
11
|
+
* `retries` plugin now works with plain HTTP responses (not just error responses);
|
|
12
|
+
* reduced the number of string allocations from log labels;
|
|
13
|
+
* performance: a lot of improvements were made to optimize the "waiting for IO events" phase, which dramatically reduced the CPU usage and make the performance of the library more on-par with other ruby HTTP gems for the 1-shot request scenario.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Bugfixes
|
|
17
|
+
|
|
18
|
+
* fixed `HTTPX::Response#copy_to`;
|
|
19
|
+
* fixed `compression` plugin not properly compressing request bodies using `gzip`;
|
|
20
|
+
* fixed `compression` plugin not handling `content-encoding: identity` payloads;
|
|
21
|
+
* do not overwrite user-defined `max_requests`on HTTP2 connection handshake;
|
|
22
|
+
* `retries` plugin: connection was blocking when a request with body was retried;
|
|
23
|
+
* `alt-svc: clear` response header was causing the process to hang;
|
|
24
|
+
|
|
25
|
+
## Tests
|
|
26
|
+
|
|
27
|
+
* Code coverage improved to 91%;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# 0.8.2
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
* `:expect` plugin now supports a new option, `:expect_threshold_size`, meaning: the byte size threshold below which no `expect` header will be sent with requests with payload.
|
|
6
|
+
* `:compression` plugin now supports a new option, `:compression_threshold_size`, meaning: the bytesize threshold below which request payload won't be compressed before being sent.
|
|
7
|
+
* for HTTP/2 connections, when `keep_alive_timeout` expires, a `PING` frame is used to check connection availability; if successful, the connection will be reused.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 0.9.0
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
### Multiple requests with specific options
|
|
6
|
+
|
|
7
|
+
You can now pass a third element to the "request element" of an array to `.request`.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
requests = [
|
|
11
|
+
[:post, "https://url/post", { form: { foo: "bar" } }],
|
|
12
|
+
[:post, "https://url/post", { form: { foo: "bar2" } }]
|
|
13
|
+
]
|
|
14
|
+
HTTPX.request(requests)
|
|
15
|
+
# or, if you want to pass options common to all requests
|
|
16
|
+
HTTPX.request(requests, max_concurrent_requests: 1)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### HTTPX::Session#build_request
|
|
21
|
+
|
|
22
|
+
`HTTPX::Session::build_request` is now public API from a session. You can now build requests before you send them. These request objects are still considered somewhat "internal", so consider them immutable and **do not rely on its API**. Just pass them forward.
|
|
23
|
+
|
|
24
|
+
Note: this API is only available for instantiated session, so there is no `HTTPX.build_request`.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
|
|
29
|
+
HTTPX.wrap do |http|
|
|
30
|
+
requests = [
|
|
31
|
+
http.build_request(:post, "https://url/post", { form: { foo: "bar" } }),
|
|
32
|
+
http.build_request(:post, "https://url/post", { form: { foo: "bar2" } })
|
|
33
|
+
]
|
|
34
|
+
http.request(requests)
|
|
35
|
+
# or, if you want to pass options common to all requests
|
|
36
|
+
http.request(requests, max_concurrent_requests: 1)
|
|
37
|
+
end
|
|
38
|
+
```
|
data/lib/httpx.rb
CHANGED
data/lib/httpx/altsvc.rb
CHANGED
|
@@ -42,7 +42,23 @@ module HTTPX
|
|
|
42
42
|
|
|
43
43
|
origin = request.origin
|
|
44
44
|
host = request.uri.host
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
altsvc = response.headers["alt-svc"]
|
|
47
|
+
|
|
48
|
+
# https://tools.ietf.org/html/rfc7838#section-3
|
|
49
|
+
# A field value containing the special value "clear" indicates that the
|
|
50
|
+
# origin requests all alternatives for that origin to be invalidated
|
|
51
|
+
# (including those specified in the same response, in case of an
|
|
52
|
+
# invalid reply containing both "clear" and alternative services).
|
|
53
|
+
if altsvc == "clear"
|
|
54
|
+
@altsvc_mutex.synchronize do
|
|
55
|
+
@altsvcs[origin].clear
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
parse(altsvc) do |alt_origin, alt_params|
|
|
46
62
|
alt_origin.host ||= host
|
|
47
63
|
yield(alt_origin, origin, alt_params)
|
|
48
64
|
end
|
|
@@ -73,7 +89,7 @@ module HTTPX
|
|
|
73
89
|
alt_proto, alt_origin = alt_origin.split("=")
|
|
74
90
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
|
75
91
|
if alt_origin.start_with?(":")
|
|
76
|
-
alt_origin = "dummy#{alt_origin}"
|
|
92
|
+
alt_origin = "#{alt_proto}://dummy#{alt_origin}"
|
|
77
93
|
uri = URI.parse(alt_origin)
|
|
78
94
|
uri.host = nil
|
|
79
95
|
uri
|
data/lib/httpx/chainable.rb
CHANGED
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
module HTTPX
|
|
4
4
|
module Chainable
|
|
5
5
|
%i[head get post put delete trace options connect patch].each do |meth|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
|
7
|
+
def #{meth}(*uri, **options)
|
|
8
|
+
request(:#{meth}, uri, **options)
|
|
9
|
+
end
|
|
10
|
+
MOD
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def request(
|
|
12
|
-
branch(default_options).request(
|
|
13
|
+
def request(*args, **options)
|
|
14
|
+
branch(default_options).request(*args, **options)
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
# :nocov:
|
|
@@ -32,11 +34,11 @@ module HTTPX
|
|
|
32
34
|
branch(default_options).wrap(&blk)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
def plugin(*args, **opts)
|
|
37
|
+
def plugin(*args, **opts, &blk)
|
|
36
38
|
klass = is_a?(Session) ? self.class : Session
|
|
37
39
|
klass = Class.new(klass)
|
|
38
40
|
klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
|
|
39
|
-
klass.plugin(*args, **opts).new
|
|
41
|
+
klass.plugin(*args, **opts, &blk).new
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
# deprecated
|
|
@@ -57,7 +59,6 @@ module HTTPX
|
|
|
57
59
|
@options || Options.new
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
# :nodoc:
|
|
61
62
|
def branch(options, &blk)
|
|
62
63
|
return self.class.new(options, &blk) if is_a?(Session)
|
|
63
64
|
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -51,7 +51,7 @@ module HTTPX
|
|
|
51
51
|
def initialize(type, uri, options)
|
|
52
52
|
@type = type
|
|
53
53
|
@origins = [uri.origin]
|
|
54
|
-
@origin =
|
|
54
|
+
@origin = Utils.uri(uri.origin)
|
|
55
55
|
@options = Options.new(options)
|
|
56
56
|
@window_size = @options.window_size
|
|
57
57
|
@read_buffer = Buffer.new(BUFFER_SIZE)
|
|
@@ -67,6 +67,10 @@ module HTTPX
|
|
|
67
67
|
else
|
|
68
68
|
transition(:idle)
|
|
69
69
|
end
|
|
70
|
+
|
|
71
|
+
@inflight = 0
|
|
72
|
+
@keep_alive_timeout = options.timeout.keep_alive_timeout
|
|
73
|
+
@keep_alive_timer = nil
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
# this is a semi-private method, to be used by the resolver
|
|
@@ -138,14 +142,15 @@ module HTTPX
|
|
|
138
142
|
end
|
|
139
143
|
end
|
|
140
144
|
|
|
141
|
-
def purge_pending
|
|
145
|
+
def purge_pending(&block)
|
|
142
146
|
pendings = []
|
|
143
|
-
|
|
147
|
+
if @parser
|
|
148
|
+
@inflight -= @parser.pending.size
|
|
149
|
+
pendings << @parser.pending
|
|
150
|
+
end
|
|
144
151
|
pendings << @pending
|
|
145
152
|
pendings.each do |pending|
|
|
146
|
-
pending.reject!
|
|
147
|
-
yield request
|
|
148
|
-
end
|
|
153
|
+
pending.reject!(&block)
|
|
149
154
|
end
|
|
150
155
|
end
|
|
151
156
|
|
|
@@ -168,22 +173,45 @@ module HTTPX
|
|
|
168
173
|
end
|
|
169
174
|
|
|
170
175
|
def interests
|
|
171
|
-
|
|
176
|
+
# connecting
|
|
177
|
+
if connecting?
|
|
178
|
+
connect
|
|
179
|
+
|
|
180
|
+
return @io.interests if connecting?
|
|
181
|
+
end
|
|
172
182
|
|
|
173
|
-
|
|
183
|
+
# if the write buffer is full, we drain it
|
|
184
|
+
return :w if @write_buffer.full?
|
|
185
|
+
|
|
186
|
+
return @parser.interests if @parser
|
|
187
|
+
|
|
188
|
+
nil
|
|
174
189
|
end
|
|
175
190
|
|
|
176
191
|
def to_io
|
|
192
|
+
@io.to_io
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def call
|
|
177
196
|
case @state
|
|
178
|
-
when :
|
|
179
|
-
|
|
197
|
+
when :closed
|
|
198
|
+
return
|
|
199
|
+
when :closing
|
|
200
|
+
consume
|
|
201
|
+
transition(:closed)
|
|
202
|
+
emit(:close)
|
|
203
|
+
when :open
|
|
204
|
+
consume
|
|
180
205
|
end
|
|
181
|
-
|
|
206
|
+
nil
|
|
182
207
|
end
|
|
183
208
|
|
|
184
209
|
def close
|
|
185
210
|
@parser.close if @parser
|
|
186
|
-
|
|
211
|
+
return unless @keep_alive_timer
|
|
212
|
+
|
|
213
|
+
@keep_alive_timer.cancel
|
|
214
|
+
remove_instance_variable(:@keep_alive_timer)
|
|
187
215
|
end
|
|
188
216
|
|
|
189
217
|
def reset
|
|
@@ -195,26 +223,25 @@ module HTTPX
|
|
|
195
223
|
def send(request)
|
|
196
224
|
if @parser && !@write_buffer.full?
|
|
197
225
|
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
|
226
|
+
if @keep_alive_timer
|
|
227
|
+
# when pushing a request into an existing connection, we have to check whether there
|
|
228
|
+
# is the possibility that the connection might have extended the keep alive timeout.
|
|
229
|
+
# for such cases, we want to ping for availability before deciding to shovel requests.
|
|
230
|
+
if @keep_alive_timer.fires_in.negative?
|
|
231
|
+
@pending << request
|
|
232
|
+
parser.ping
|
|
233
|
+
return
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
@keep_alive_timer.pause
|
|
237
|
+
end
|
|
238
|
+
@inflight += 1
|
|
198
239
|
parser.send(request)
|
|
199
240
|
else
|
|
200
241
|
@pending << request
|
|
201
242
|
end
|
|
202
243
|
end
|
|
203
244
|
|
|
204
|
-
def call
|
|
205
|
-
case @state
|
|
206
|
-
when :closed
|
|
207
|
-
return
|
|
208
|
-
when :closing
|
|
209
|
-
dwrite
|
|
210
|
-
transition(:closed)
|
|
211
|
-
emit(:close)
|
|
212
|
-
when :open
|
|
213
|
-
consume
|
|
214
|
-
end
|
|
215
|
-
nil
|
|
216
|
-
end
|
|
217
|
-
|
|
218
245
|
def timeout
|
|
219
246
|
return @timeout if defined?(@timeout)
|
|
220
247
|
|
|
@@ -225,54 +252,91 @@ module HTTPX
|
|
|
225
252
|
|
|
226
253
|
private
|
|
227
254
|
|
|
255
|
+
def connect
|
|
256
|
+
transition(:open)
|
|
257
|
+
end
|
|
258
|
+
|
|
228
259
|
def exhausted?
|
|
229
260
|
@parser && parser.exhausted?
|
|
230
261
|
end
|
|
231
262
|
|
|
232
263
|
def consume
|
|
233
264
|
catch(:called) do
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
loop do
|
|
266
|
+
parser.consume
|
|
267
|
+
|
|
268
|
+
# we exit if there's no more data to process
|
|
269
|
+
if @pending.size.zero? && @inflight.zero?
|
|
270
|
+
log(level: 3) { "NO MORE REQUESTS..." }
|
|
271
|
+
return
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
@timeout = @current_timeout
|
|
275
|
+
|
|
276
|
+
read_drained = false
|
|
277
|
+
write_drained = nil
|
|
278
|
+
|
|
279
|
+
# dread
|
|
280
|
+
loop do
|
|
281
|
+
siz = @io.read(@window_size, @read_buffer)
|
|
282
|
+
unless siz
|
|
283
|
+
ex = EOFError.new("descriptor closed")
|
|
284
|
+
ex.set_backtrace(caller)
|
|
285
|
+
on_error(ex)
|
|
286
|
+
return
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
if siz.zero?
|
|
290
|
+
read_drained = @read_buffer.empty?
|
|
291
|
+
break
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
parser << @read_buffer.to_s
|
|
295
|
+
|
|
296
|
+
break if @state == :closing || @state == :closed
|
|
297
|
+
|
|
298
|
+
# for HTTP/2, we just want to write goaway frame
|
|
299
|
+
end unless @state == :closing
|
|
300
|
+
|
|
301
|
+
# dwrite
|
|
302
|
+
loop do
|
|
303
|
+
if @write_buffer.empty?
|
|
304
|
+
# we only mark as drained on the first loop
|
|
305
|
+
write_drained = write_drained.nil? && @inflight.positive?
|
|
306
|
+
break
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
siz = @io.write(@write_buffer)
|
|
310
|
+
unless siz
|
|
311
|
+
ex = EOFError.new("descriptor closed")
|
|
312
|
+
ex.set_backtrace(caller)
|
|
313
|
+
on_error(ex)
|
|
314
|
+
return
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
if siz.zero?
|
|
318
|
+
write_drained = !@write_buffer.empty?
|
|
319
|
+
break
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
break if @state == :closing || @state == :closed
|
|
323
|
+
|
|
324
|
+
write_drained = false
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# return if socket is drained
|
|
328
|
+
if read_drained && write_drained
|
|
329
|
+
log(level: 3) { "WAITING FOR EVENTS..." }
|
|
330
|
+
return
|
|
331
|
+
end
|
|
267
332
|
end
|
|
268
|
-
log { "WRITE: #{siz} bytes..." }
|
|
269
|
-
return if siz.zero?
|
|
270
|
-
return if @state == :closing || @state == :closed
|
|
271
333
|
end
|
|
272
334
|
end
|
|
273
335
|
|
|
274
336
|
def send_pending
|
|
275
337
|
while !@write_buffer.full? && (request = @pending.shift)
|
|
338
|
+
@inflight += 1
|
|
339
|
+
@keep_alive_timer.pause if @keep_alive_timer
|
|
276
340
|
parser.send(request)
|
|
277
341
|
end
|
|
278
342
|
end
|
|
@@ -292,12 +356,15 @@ module HTTPX
|
|
|
292
356
|
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
|
293
357
|
emit(:altsvc, alt_origin, origin, alt_params)
|
|
294
358
|
end
|
|
359
|
+
handle_response
|
|
295
360
|
request.emit(:response, response)
|
|
296
361
|
end
|
|
297
362
|
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
|
298
363
|
emit(:altsvc, alt_origin, origin, alt_params)
|
|
299
364
|
end
|
|
300
365
|
|
|
366
|
+
parser.on(:pong, &method(:send_pending))
|
|
367
|
+
|
|
301
368
|
parser.on(:promise) do |request, stream|
|
|
302
369
|
request.emit(:promise, parser, stream)
|
|
303
370
|
end
|
|
@@ -307,14 +374,21 @@ module HTTPX
|
|
|
307
374
|
parser.on(:origin) do |origin|
|
|
308
375
|
@origins << origin
|
|
309
376
|
end
|
|
310
|
-
parser.on(:close) do
|
|
377
|
+
parser.on(:close) do |force|
|
|
311
378
|
transition(:closing)
|
|
379
|
+
if force
|
|
380
|
+
transition(:closed)
|
|
381
|
+
emit(:close)
|
|
382
|
+
end
|
|
312
383
|
end
|
|
313
384
|
parser.on(:reset) do
|
|
314
|
-
|
|
315
|
-
|
|
385
|
+
if parser.empty?
|
|
386
|
+
reset
|
|
387
|
+
else
|
|
388
|
+
transition(:closing)
|
|
316
389
|
transition(:closed)
|
|
317
390
|
emit(:reset)
|
|
391
|
+
@parser.reset if @parser
|
|
318
392
|
transition(:idle)
|
|
319
393
|
transition(:open)
|
|
320
394
|
end
|
|
@@ -335,6 +409,9 @@ module HTTPX
|
|
|
335
409
|
|
|
336
410
|
def transition(nextstate)
|
|
337
411
|
case nextstate
|
|
412
|
+
when :idle
|
|
413
|
+
@timeout = @current_timeout = @options.timeout.connect_timeout
|
|
414
|
+
|
|
338
415
|
when :open
|
|
339
416
|
return if @state == :closed
|
|
340
417
|
|
|
@@ -344,6 +421,8 @@ module HTTPX
|
|
|
344
421
|
return unless @io.connected?
|
|
345
422
|
|
|
346
423
|
send_pending
|
|
424
|
+
|
|
425
|
+
@timeout = @current_timeout = @options.timeout.operation_timeout
|
|
347
426
|
emit(:open)
|
|
348
427
|
when :closing
|
|
349
428
|
return unless @state == :open
|
|
@@ -358,6 +437,11 @@ module HTTPX
|
|
|
358
437
|
|
|
359
438
|
@io.close
|
|
360
439
|
@read_buffer.clear
|
|
440
|
+
if @keep_alive_timer
|
|
441
|
+
@keep_alive_timer.cancel
|
|
442
|
+
remove_instance_variable(:@keep_alive_timer)
|
|
443
|
+
end
|
|
444
|
+
|
|
361
445
|
remove_instance_variable(:@timeout) if defined?(@timeout)
|
|
362
446
|
when :already_open
|
|
363
447
|
nextstate = :open
|
|
@@ -371,7 +455,6 @@ module HTTPX
|
|
|
371
455
|
throw(:jump_tick)
|
|
372
456
|
rescue Errno::ECONNREFUSED,
|
|
373
457
|
Errno::EADDRNOTAVAIL,
|
|
374
|
-
Errno::EHOSTUNREACH,
|
|
375
458
|
OpenSSL::SSL::SSLError => e
|
|
376
459
|
# connect errors, exit gracefully
|
|
377
460
|
handle_error(e)
|
|
@@ -379,21 +462,43 @@ module HTTPX
|
|
|
379
462
|
emit(:close)
|
|
380
463
|
end
|
|
381
464
|
|
|
382
|
-
def
|
|
383
|
-
|
|
384
|
-
|
|
465
|
+
def handle_response
|
|
466
|
+
@inflight -= 1
|
|
467
|
+
return unless @inflight.zero?
|
|
468
|
+
|
|
469
|
+
if @keep_alive_timer
|
|
470
|
+
@keep_alive_timer.resume
|
|
471
|
+
@keep_alive_timer.reset
|
|
472
|
+
else
|
|
473
|
+
@keep_alive_timer = @timers.after(@keep_alive_timeout) do
|
|
474
|
+
unless @inflight.zero?
|
|
475
|
+
log { "(#{@origin}): keep alive timeout expired" }
|
|
476
|
+
parser.ping
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
385
480
|
end
|
|
386
481
|
|
|
387
|
-
def
|
|
482
|
+
def on_error(error)
|
|
388
483
|
if error.instance_of?(TimeoutError)
|
|
389
484
|
if @timeout
|
|
390
485
|
@timeout -= error.timeout
|
|
391
486
|
return unless @timeout <= 0
|
|
392
487
|
end
|
|
393
488
|
|
|
394
|
-
|
|
489
|
+
if @total_timeout && @total_timeout.fires_in.negative?
|
|
490
|
+
ex = TotalTimeoutError.new(@total_timeout.interval, "Timed out after #{@total_timeout.interval} seconds")
|
|
491
|
+
ex.set_backtrace(error.backtrace)
|
|
492
|
+
error = ex
|
|
493
|
+
elsif connecting?
|
|
494
|
+
error = error.to_connection_error
|
|
495
|
+
end
|
|
395
496
|
end
|
|
497
|
+
handle_error(error)
|
|
498
|
+
reset
|
|
499
|
+
end
|
|
396
500
|
|
|
501
|
+
def handle_error(error)
|
|
397
502
|
parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
|
|
398
503
|
while (request = @pending.shift)
|
|
399
504
|
request.emit(:response, ErrorResponse.new(request, error, @options))
|
|
@@ -408,8 +513,8 @@ module HTTPX
|
|
|
408
513
|
@total_timeout ||= @timers.after(total) do
|
|
409
514
|
ex = TotalTimeoutError.new(total, "Timed out after #{total} seconds")
|
|
410
515
|
ex.set_backtrace(caller)
|
|
411
|
-
@parser.close if @parser
|
|
412
516
|
on_error(ex)
|
|
517
|
+
@parser.close if @parser
|
|
413
518
|
end
|
|
414
519
|
end
|
|
415
520
|
end
|