httpx 0.7.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|