httpx 1.1.5 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -6
- data/doc/release_notes/1_1_1.md +2 -2
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/lib/httpx/adapters/webmock.rb +25 -3
- data/lib/httpx/altsvc.rb +57 -2
- data/lib/httpx/buffer.rb +8 -0
- data/lib/httpx/chainable.rb +48 -29
- data/lib/httpx/connection/http1.rb +27 -22
- data/lib/httpx/connection/http2.rb +7 -3
- data/lib/httpx/connection.rb +52 -62
- data/lib/httpx/extensions.rb +0 -15
- data/lib/httpx/options.rb +85 -28
- 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 +16 -34
- 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 +10 -4
- data/lib/httpx/resolver/native.rb +1 -0
- data/lib/httpx/resolver/resolver.rb +17 -6
- data/lib/httpx/response/body.rb +3 -0
- data/lib/httpx/response.rb +3 -2
- 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 +14 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e5ee54988be76a44ae512da359d83c502f8a43073f244a116a8fdc45fa7b87d
|
4
|
+
data.tar.gz: e3c08652a8d08eadbd1ef14b20005f93ff4131a712f8234c3eab94c7fce07167
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58f16e523d23215d89a8c873a5ce12491b52726073c334c4d9800e17e40dca8111ad43ab2467c37939a19bebbaa000c8db144fdbba87cc3d22cb699687df6699
|
7
|
+
data.tar.gz: 64dd9bb70af4173339019b62fa9f54fdf3f22c4bf593b51fb3de624844ac8a599a51b49cae10623aee8b8cff24af4dee69e39bedfec7da3d1ae229d733632e9c
|
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
|
|
@@ -136,7 +136,7 @@ The test suite runs against [httpbin proxied over nghttp2](https://nghttp2.org/h
|
|
136
136
|
|
137
137
|
All Rubies greater or equal to 2.7, and always latest JRuby and Truffleruby.
|
138
138
|
|
139
|
-
**Note**: This gem is tested against all latest patch versions, i.e. if you're using 3.
|
139
|
+
**Note**: This gem is tested against all latest patch versions, i.e. if you're using 3.3.0 and you experience some issue, please test it against 3.3.$latest before creating an issue.
|
140
140
|
|
141
141
|
## Resources
|
142
142
|
| | |
|
data/doc/release_notes/1_1_1.md
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
## improvements
|
4
4
|
|
5
|
-
* (Re-)enabling default retries in DNS name queries; this had been disabled as a result of revamping
|
5
|
+
* (Re-)enabling default retries in DNS name queries; this had been disabled as a result of revamping timeouts, and resulted in queries only being sent once, which is very little for UDP-related traffic, and breaks if using DNs rate-limiting software. Retries the query just once, for now.
|
6
6
|
|
7
7
|
## bugfixes
|
8
8
|
|
9
9
|
* reset timers when adding new intervals, as these may be added as a result on after-select connection handling, and must wait for the next tick cycle (before the patch, they were triggering too soon).
|
10
10
|
* fixed "on close" callback leak on connection reuse, which caused linear performance regression in benchmarks performing one request per connection.
|
11
|
-
* fixed hanging connection
|
11
|
+
* fixed hanging connection when an HTTP/1.1 emitted a "connection: close" header but the server would not emit one (it closes the connection now).
|
12
12
|
* fixed recursive dns cached lookups which may have already expired, and created nil entries in the returned address list.
|
13
13
|
* dns system resolver is now able to retry on failure.
|
14
14
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# 1.2.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### `:ssrf_filter` plugin
|
6
|
+
|
7
|
+
The `:ssrf_filter` 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/buffer.rb
CHANGED
@@ -3,6 +3,14 @@
|
|
3
3
|
require "forwardable"
|
4
4
|
|
5
5
|
module HTTPX
|
6
|
+
# Internal class to abstract a string buffer, by wrapping a string and providing the
|
7
|
+
# minimum possible API and functionality required.
|
8
|
+
#
|
9
|
+
# buffer = Buffer.new(640)
|
10
|
+
# buffer.full? #=> false
|
11
|
+
# buffer << "aa"
|
12
|
+
# buffer.capacity #=> 638
|
13
|
+
#
|
6
14
|
class Buffer
|
7
15
|
extend Forwardable
|
8
16
|
|
data/lib/httpx/chainable.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
+
# Session mixin, implements most of the APIs that the users call.
|
5
|
+
# delegates to a default session when extended.
|
4
6
|
module Chainable
|
5
7
|
%w[head get post put delete trace options connect patch].each do |meth|
|
6
8
|
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
@@ -10,19 +12,7 @@ module HTTPX
|
|
10
12
|
MOD
|
11
13
|
end
|
12
14
|
|
13
|
-
|
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
|
-
|
15
|
+
# delegates to the default session (see HTTPX::Session#request).
|
26
16
|
def request(*args, **options)
|
27
17
|
branch(default_options).request(*args, **options)
|
28
18
|
end
|
@@ -31,10 +21,12 @@ module HTTPX
|
|
31
21
|
with(headers: { "accept" => String(type) })
|
32
22
|
end
|
33
23
|
|
24
|
+
# delegates to the default session (see HTTPX::Session#wrap).
|
34
25
|
def wrap(&blk)
|
35
26
|
branch(default_options).wrap(&blk)
|
36
27
|
end
|
37
28
|
|
29
|
+
# returns a new instance loaded with the +pl+ plugin and +options+.
|
38
30
|
def plugin(pl, options = nil, &blk)
|
39
31
|
klass = is_a?(S) ? self.class : Session
|
40
32
|
klass = Class.new(klass)
|
@@ -42,44 +34,71 @@ module HTTPX
|
|
42
34
|
klass.plugin(pl, options, &blk).new
|
43
35
|
end
|
44
36
|
|
37
|
+
# returns a new instance loaded with +options+.
|
45
38
|
def with(options, &blk)
|
46
39
|
branch(default_options.merge(options), &blk)
|
47
40
|
end
|
48
41
|
|
49
|
-
protected
|
50
|
-
|
51
|
-
def on(*args, &blk)
|
52
|
-
branch(default_options).on(*args, &blk)
|
53
|
-
end
|
54
|
-
|
55
42
|
private
|
56
43
|
|
44
|
+
# returns default instance of HTTPX::Options.
|
57
45
|
def default_options
|
58
46
|
@options || Session.default_options
|
59
47
|
end
|
60
48
|
|
49
|
+
# returns a default instance of HTTPX::Session.
|
61
50
|
def branch(options, &blk)
|
62
51
|
return self.class.new(options, &blk) if is_a?(S)
|
63
52
|
|
64
53
|
Session.new(options, &blk)
|
65
54
|
end
|
66
55
|
|
67
|
-
def method_missing(meth, *args, **options)
|
68
|
-
|
56
|
+
def method_missing(meth, *args, **options, &blk)
|
57
|
+
case meth
|
58
|
+
when /\Awith_(.+)/
|
69
59
|
|
70
|
-
|
60
|
+
option = Regexp.last_match(1)
|
71
61
|
|
72
|
-
|
62
|
+
return super unless option
|
73
63
|
|
74
|
-
|
75
|
-
|
64
|
+
with(option.to_sym => args.first || options)
|
65
|
+
when /\Aon_(.+)/
|
66
|
+
callback = Regexp.last_match(1)
|
76
67
|
|
77
|
-
|
78
|
-
|
68
|
+
return super unless %w[
|
69
|
+
connection_opened connection_closed
|
70
|
+
request_error
|
71
|
+
request_started request_body_chunk request_completed
|
72
|
+
response_started response_body_chunk response_completed
|
73
|
+
].include?(callback)
|
79
74
|
|
80
|
-
|
75
|
+
warn "DEPRECATION WARNING: calling `.#{meth}` on plain HTTPX sessions is deprecated. " \
|
76
|
+
"Use HTTPX.plugin(:callbacks).#{meth} instead."
|
81
77
|
|
82
|
-
|
78
|
+
plugin(:callbacks).__send__(meth, *args, **options, &blk)
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def respond_to_missing?(meth, *)
|
85
|
+
case meth
|
86
|
+
when /\Awith_(.+)/
|
87
|
+
option = Regexp.last_match(1)
|
88
|
+
|
89
|
+
default_options.respond_to?(option) || super
|
90
|
+
when /\Aon_(.+)/
|
91
|
+
callback = Regexp.last_match(1)
|
92
|
+
|
93
|
+
%w[
|
94
|
+
connection_opened connection_closed
|
95
|
+
request_error
|
96
|
+
request_started request_body_chunk request_completed
|
97
|
+
response_started response_body_chunk response_completed
|
98
|
+
].include?(callback) || super
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
83
102
|
end
|
84
103
|
end
|
85
104
|
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
|