httpx 1.7.2 → 1.7.4
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/README.md +3 -1
- data/doc/release_notes/1_7_3.md +29 -0
- data/doc/release_notes/1_7_4.md +42 -0
- data/lib/httpx/adapters/datadog.rb +24 -60
- data/lib/httpx/adapters/webmock.rb +3 -4
- data/lib/httpx/connection/http1.rb +6 -1
- data/lib/httpx/connection/http2.rb +43 -30
- data/lib/httpx/connection.rb +74 -22
- data/lib/httpx/plugins/auth/digest.rb +2 -1
- data/lib/httpx/plugins/brotli.rb +33 -5
- data/lib/httpx/plugins/cookies/cookie.rb +34 -11
- data/lib/httpx/plugins/cookies/jar.rb +93 -18
- data/lib/httpx/plugins/cookies.rb +7 -3
- data/lib/httpx/plugins/expect.rb +30 -3
- data/lib/httpx/plugins/fiber_concurrency.rb +2 -4
- data/lib/httpx/plugins/follow_redirects.rb +7 -1
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -8
- data/lib/httpx/plugins/proxy.rb +10 -2
- data/lib/httpx/plugins/rate_limiter.rb +19 -19
- data/lib/httpx/plugins/retries.rb +17 -9
- data/lib/httpx/plugins/ssrf_filter.rb +1 -0
- data/lib/httpx/plugins/stream_bidi.rb +6 -0
- data/lib/httpx/plugins/tracing.rb +137 -0
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/multi.rb +1 -8
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/resolver/resolver.rb +21 -2
- data/lib/httpx/resolver/system.rb +3 -1
- data/lib/httpx/selector.rb +4 -4
- data/lib/httpx/session.rb +11 -6
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +2 -0
- data/sig/connection/http2.rbs +11 -4
- data/sig/connection.rbs +7 -0
- data/sig/plugins/brotli.rbs +11 -6
- data/sig/plugins/cookies/cookie.rbs +3 -2
- data/sig/plugins/cookies/jar.rbs +11 -0
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/expect.rbs +17 -2
- data/sig/plugins/proxy/socks4.rbs +4 -0
- data/sig/plugins/rate_limiter.rbs +2 -2
- data/sig/plugins/response_cache.rbs +3 -3
- data/sig/plugins/retries.rbs +17 -13
- data/sig/plugins/tracing.rbs +41 -0
- data/sig/request.rbs +1 -0
- data/sig/resolver/native.rbs +2 -0
- data/sig/resolver/resolver.rbs +4 -2
- data/sig/resolver/system.rbs +0 -2
- data/sig/response/body.rbs +1 -1
- data/sig/selector.rbs +4 -0
- data/sig/session.rbs +2 -0
- data/sig/transcoder/gzip.rbs +1 -1
- data/sig/transcoder.rbs +0 -2
- metadata +9 -3
|
@@ -14,12 +14,14 @@ module HTTPX
|
|
|
14
14
|
|
|
15
15
|
attr_reader :domain, :path, :name, :value, :created_at
|
|
16
16
|
|
|
17
|
+
# assigns a new +path+ to this cookie.
|
|
17
18
|
def path=(path)
|
|
18
19
|
path = String(path)
|
|
20
|
+
@for_domain = false
|
|
19
21
|
@path = path.start_with?("/") ? path : "/"
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
#
|
|
24
|
+
# assigns a new +domain+ to this cookie.
|
|
23
25
|
def domain=(domain)
|
|
24
26
|
domain = String(domain)
|
|
25
27
|
|
|
@@ -37,6 +39,13 @@ module HTTPX
|
|
|
37
39
|
@domain = @domain_name.hostname
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
# checks whether +other+ is the same cookie, i.e. name, value, domain and path are
|
|
43
|
+
# the same.
|
|
44
|
+
def ==(other)
|
|
45
|
+
@name == other.name && @value == other.value &&
|
|
46
|
+
@path == other.path && @domain == other.domain
|
|
47
|
+
end
|
|
48
|
+
|
|
40
49
|
# Compares the cookie with another. When there are many cookies with
|
|
41
50
|
# the same name for a URL, the value of the smallest must be used.
|
|
42
51
|
def <=>(other)
|
|
@@ -47,11 +56,29 @@ module HTTPX
|
|
|
47
56
|
(@created_at <=> other.created_at).nonzero? || 0
|
|
48
57
|
end
|
|
49
58
|
|
|
59
|
+
def match?(name_or_options)
|
|
60
|
+
case name_or_options
|
|
61
|
+
when String
|
|
62
|
+
@name == name_or_options
|
|
63
|
+
when Hash, Array
|
|
64
|
+
name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
|
|
65
|
+
else
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
50
70
|
class << self
|
|
51
71
|
def new(cookie, *args)
|
|
52
|
-
|
|
72
|
+
case cookie
|
|
73
|
+
when self
|
|
74
|
+
cookie
|
|
75
|
+
when Array, Hash
|
|
76
|
+
options = Hash[cookie] #: cookie_attributes
|
|
77
|
+
super(options[:name], options[:value], options)
|
|
78
|
+
else
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
super
|
|
81
|
+
end
|
|
55
82
|
end
|
|
56
83
|
|
|
57
84
|
# Tests if +target_path+ is under +base_path+ as described in RFC
|
|
@@ -84,16 +111,12 @@ module HTTPX
|
|
|
84
111
|
end
|
|
85
112
|
end
|
|
86
113
|
|
|
87
|
-
def initialize(arg,
|
|
114
|
+
def initialize(arg, value, attrs = nil)
|
|
88
115
|
@created_at = Time.now
|
|
89
116
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@name = arg
|
|
94
|
-
@value, attr_hash = attrs
|
|
95
|
-
attr_hash = Hash.try_convert(attr_hash)
|
|
96
|
-
end
|
|
117
|
+
@name = arg
|
|
118
|
+
@value = value
|
|
119
|
+
attr_hash = Hash.try_convert(attrs)
|
|
97
120
|
|
|
98
121
|
attr_hash.each do |key, val|
|
|
99
122
|
key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)
|
|
@@ -4,7 +4,12 @@ module HTTPX
|
|
|
4
4
|
module Plugins::Cookies
|
|
5
5
|
# The Cookie Jar
|
|
6
6
|
#
|
|
7
|
-
# It
|
|
7
|
+
# It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
|
|
8
|
+
# initialization from parsing `Set-Cookie` HTTP header values.
|
|
9
|
+
#
|
|
10
|
+
# It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
|
|
11
|
+
# by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
|
|
12
|
+
#
|
|
8
13
|
class Jar
|
|
9
14
|
using URIExtensions
|
|
10
15
|
|
|
@@ -12,10 +17,14 @@ module HTTPX
|
|
|
12
17
|
|
|
13
18
|
def initialize_dup(orig)
|
|
14
19
|
super
|
|
20
|
+
@mtx = orig.instance_variable_get(:@mtx).dup
|
|
15
21
|
@cookies = orig.instance_variable_get(:@cookies).dup
|
|
16
22
|
end
|
|
17
23
|
|
|
24
|
+
# initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
|
|
25
|
+
# can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
|
|
18
26
|
def initialize(cookies = nil)
|
|
27
|
+
@mtx = Thread::Mutex.new
|
|
19
28
|
@cookies = []
|
|
20
29
|
|
|
21
30
|
cookies.each do |elem|
|
|
@@ -32,48 +41,106 @@ module HTTPX
|
|
|
32
41
|
end if cookies
|
|
33
42
|
end
|
|
34
43
|
|
|
44
|
+
# parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
|
|
35
45
|
def parse(set_cookie)
|
|
36
46
|
SetCookieParser.call(set_cookie) do |name, value, attrs|
|
|
37
|
-
|
|
47
|
+
set(Cookie.new(name, value, attrs))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
|
|
52
|
+
# (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
|
|
53
|
+
def get(name_or_options)
|
|
54
|
+
each.find { |ck| ck.match?(name_or_options) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
|
|
58
|
+
# (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
|
|
59
|
+
def get_all(name_or_options)
|
|
60
|
+
each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
|
|
64
|
+
# it creates a cookie with it and the value-or-attributes passed to +value_or_options+.
|
|
65
|
+
|
|
66
|
+
# optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
|
|
67
|
+
def set(name, value_or_options = nil)
|
|
68
|
+
cookie = case name
|
|
69
|
+
when Cookie
|
|
70
|
+
raise ArgumentError, "there should not be a second argument" if value_or_options
|
|
71
|
+
|
|
72
|
+
name
|
|
73
|
+
when Array, Hash
|
|
74
|
+
raise ArgumentError, "there should not be a second argument" if value_or_options
|
|
75
|
+
|
|
76
|
+
Cookie.new(name)
|
|
77
|
+
else
|
|
78
|
+
raise ArgumentError, "the second argument is required" unless value_or_options
|
|
79
|
+
|
|
80
|
+
Cookie.new(name, value_or_options)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
synchronize do
|
|
84
|
+
# If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
|
|
85
|
+
# as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
|
|
86
|
+
@cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }
|
|
87
|
+
|
|
88
|
+
@cookies << cookie
|
|
38
89
|
end
|
|
39
90
|
end
|
|
40
91
|
|
|
92
|
+
# @deprecated
|
|
41
93
|
def add(cookie, path = nil)
|
|
94
|
+
warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
|
|
42
95
|
c = cookie.dup
|
|
43
|
-
|
|
44
96
|
c.path = path if path && c.path == "/"
|
|
97
|
+
set(c)
|
|
98
|
+
end
|
|
45
99
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
100
|
+
# deletes all cookies in the store which match either the name (when String) or all attributes (when a Hash
|
|
101
|
+
# or array of tuples) passed to +name_or_options+.
|
|
102
|
+
#
|
|
103
|
+
# alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
|
|
104
|
+
def delete(name_or_options)
|
|
105
|
+
synchronize do
|
|
106
|
+
case name_or_options
|
|
107
|
+
when Cookie
|
|
108
|
+
@cookies.delete(name_or_options)
|
|
109
|
+
else
|
|
110
|
+
@cookies.delete_if { |ck| ck.match?(name_or_options) }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
51
113
|
end
|
|
52
114
|
|
|
115
|
+
# returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
|
|
53
116
|
def [](uri)
|
|
54
117
|
each(uri).sort
|
|
55
118
|
end
|
|
56
119
|
|
|
120
|
+
# enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
|
|
121
|
+
# only yield cookies which match its domain and path.
|
|
57
122
|
def each(uri = nil, &blk)
|
|
58
123
|
return enum_for(__method__, uri) unless blk
|
|
59
124
|
|
|
60
|
-
return @cookies.each(&blk) unless uri
|
|
125
|
+
return synchronize { @cookies.each(&blk) } unless uri
|
|
61
126
|
|
|
62
127
|
now = Time.now
|
|
63
128
|
tpath = uri.path
|
|
64
129
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
130
|
+
synchronize do
|
|
131
|
+
@cookies.delete_if do |cookie|
|
|
132
|
+
if cookie.expired?(now)
|
|
133
|
+
true
|
|
134
|
+
else
|
|
135
|
+
yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
|
|
136
|
+
false
|
|
137
|
+
end
|
|
71
138
|
end
|
|
72
139
|
end
|
|
73
140
|
end
|
|
74
141
|
|
|
75
142
|
def merge(other)
|
|
76
|
-
|
|
143
|
+
jar_dup = dup
|
|
77
144
|
|
|
78
145
|
other.each do |elem|
|
|
79
146
|
cookie = case elem
|
|
@@ -85,10 +152,18 @@ module HTTPX
|
|
|
85
152
|
Cookie.new(elem)
|
|
86
153
|
end
|
|
87
154
|
|
|
88
|
-
|
|
155
|
+
jar_dup.set(cookie)
|
|
89
156
|
end
|
|
90
157
|
|
|
91
|
-
|
|
158
|
+
jar_dup
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
def synchronize(&block)
|
|
164
|
+
return yield if @mtx.owned?
|
|
165
|
+
|
|
166
|
+
@mtx.synchronize(&block)
|
|
92
167
|
end
|
|
93
168
|
end
|
|
94
169
|
end
|
|
@@ -7,8 +7,6 @@ module HTTPX
|
|
|
7
7
|
#
|
|
8
8
|
# This plugin implements a persistent cookie jar for the duration of a session.
|
|
9
9
|
#
|
|
10
|
-
# It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
|
|
11
|
-
#
|
|
12
10
|
# https://gitlab.com/os85/httpx/wikis/Cookies
|
|
13
11
|
#
|
|
14
12
|
module Cookies
|
|
@@ -46,6 +44,12 @@ module HTTPX
|
|
|
46
44
|
request
|
|
47
45
|
end
|
|
48
46
|
|
|
47
|
+
# factory method to return a Jar to the user, which can then manipulate
|
|
48
|
+
# externally to the session.
|
|
49
|
+
def make_jar(*args)
|
|
50
|
+
Jar.new(*args)
|
|
51
|
+
end
|
|
52
|
+
|
|
49
53
|
private
|
|
50
54
|
|
|
51
55
|
def set_request_callbacks(request)
|
|
@@ -96,7 +100,7 @@ module HTTPX
|
|
|
96
100
|
cookies.each do |ck|
|
|
97
101
|
ck.split(/ *; */).each do |cookie|
|
|
98
102
|
name, value = cookie.split("=", 2)
|
|
99
|
-
jar.
|
|
103
|
+
jar.set(name, value)
|
|
100
104
|
end
|
|
101
105
|
end
|
|
102
106
|
end
|
data/lib/httpx/plugins/expect.rb
CHANGED
|
@@ -9,10 +9,34 @@ module HTTPX
|
|
|
9
9
|
#
|
|
10
10
|
module Expect
|
|
11
11
|
EXPECT_TIMEOUT = 2
|
|
12
|
+
NOEXPECT_STORE_MUTEX = Thread::Mutex.new
|
|
13
|
+
|
|
14
|
+
class Store
|
|
15
|
+
def initialize
|
|
16
|
+
@store = []
|
|
17
|
+
@mutex = Thread::Mutex.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def include?(host)
|
|
21
|
+
@mutex.synchronize { @store.include?(host) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add(host)
|
|
25
|
+
@mutex.synchronize { @store << host }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete(host)
|
|
29
|
+
@mutex.synchronize { @store.delete(host) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
12
32
|
|
|
13
33
|
class << self
|
|
14
34
|
def no_expect_store
|
|
15
|
-
|
|
35
|
+
return Ractor.store_if_absent(:httpx_no_expect_store) { Store.new } if Utils.in_ractor?
|
|
36
|
+
|
|
37
|
+
@no_expect_store ||= NOEXPECT_STORE_MUTEX.synchronize do
|
|
38
|
+
@no_expect_store || Store.new
|
|
39
|
+
end
|
|
16
40
|
end
|
|
17
41
|
|
|
18
42
|
def extra_options(options)
|
|
@@ -89,7 +113,7 @@ module HTTPX
|
|
|
89
113
|
set_request_timeout(:expect_timeout, request, expect_timeout, :expect, %i[body response]) do
|
|
90
114
|
# expect timeout expired
|
|
91
115
|
if request.state == :expect && !request.expects?
|
|
92
|
-
Expect.no_expect_store
|
|
116
|
+
Expect.no_expect_store.add(request.origin)
|
|
93
117
|
request.headers.delete("expect")
|
|
94
118
|
consume
|
|
95
119
|
end
|
|
@@ -108,7 +132,10 @@ module HTTPX
|
|
|
108
132
|
request.headers.delete("expect")
|
|
109
133
|
request.transition(:idle)
|
|
110
134
|
send_request(request, selector, options)
|
|
111
|
-
|
|
135
|
+
|
|
136
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
137
|
+
# verify retriability again.
|
|
138
|
+
return fetch_response(request, selector, options)
|
|
112
139
|
end
|
|
113
140
|
|
|
114
141
|
response
|
|
@@ -160,9 +160,7 @@ module HTTPX
|
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
-
module
|
|
164
|
-
private
|
|
165
|
-
|
|
163
|
+
module ResolverNativeMethods
|
|
166
164
|
def calculate_interests
|
|
167
165
|
return if @queries.empty?
|
|
168
166
|
|
|
@@ -172,7 +170,7 @@ module HTTPX
|
|
|
172
170
|
end
|
|
173
171
|
end
|
|
174
172
|
|
|
175
|
-
module
|
|
173
|
+
module ResolverSystemMethods
|
|
176
174
|
def interests
|
|
177
175
|
return unless @queries.any? { |_, conn| conn.current_context? }
|
|
178
176
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
|
-
InsecureRedirectError
|
|
4
|
+
class InsecureRedirectError < Error
|
|
5
|
+
end
|
|
6
|
+
|
|
5
7
|
module Plugins
|
|
6
8
|
#
|
|
7
9
|
# This plugin adds support for automatically following redirect (status 30X) responses.
|
|
@@ -163,6 +165,10 @@ module HTTPX
|
|
|
163
165
|
end
|
|
164
166
|
else
|
|
165
167
|
send_request(retry_request, selector, options)
|
|
168
|
+
|
|
169
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
170
|
+
# verify retriability again.
|
|
171
|
+
return fetch_response(request, selector, options)
|
|
166
172
|
end
|
|
167
173
|
nil
|
|
168
174
|
end
|
data/lib/httpx/plugins/h2c.rb
CHANGED
|
@@ -35,7 +35,10 @@ module HTTPX
|
|
|
35
35
|
request.headers["proxy-authorization"] =
|
|
36
36
|
options.proxy.authenticate(request, response.headers["proxy-authenticate"])
|
|
37
37
|
send_request(request, selector, options)
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
40
|
+
# verify retriability again.
|
|
41
|
+
return fetch_response(request, selector, options)
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
response
|
|
@@ -46,7 +49,7 @@ module HTTPX
|
|
|
46
49
|
def force_close(*)
|
|
47
50
|
if @state == :connecting
|
|
48
51
|
# proxy connect related requests should not be reenqueed
|
|
49
|
-
@parser.reset
|
|
52
|
+
@parser.reset
|
|
50
53
|
@inflight -= @parser.pending.size
|
|
51
54
|
@parser.pending.clear
|
|
52
55
|
end
|
|
@@ -67,18 +70,16 @@ module HTTPX
|
|
|
67
70
|
return unless @io.connected?
|
|
68
71
|
|
|
69
72
|
@parser || begin
|
|
70
|
-
@parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
|
71
|
-
parser = @parser
|
|
73
|
+
@parser = parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
|
72
74
|
parser.extend(ProxyParser)
|
|
73
75
|
parser.on(:response, &method(:__http_on_connect))
|
|
74
76
|
parser.on(:close) do
|
|
75
77
|
next unless @parser
|
|
76
78
|
|
|
77
79
|
reset
|
|
78
|
-
disconnect
|
|
79
80
|
end
|
|
80
81
|
parser.on(:reset) do
|
|
81
|
-
if parser.empty?
|
|
82
|
+
if parser.pending.empty? && parser.empty?
|
|
82
83
|
reset
|
|
83
84
|
else
|
|
84
85
|
enqueue_pending_requests_from_parser(parser)
|
|
@@ -94,17 +95,23 @@ module HTTPX
|
|
|
94
95
|
# keep parser state around due to proxy auth protocol;
|
|
95
96
|
# intermediate authenticated request is already inside
|
|
96
97
|
# the parser
|
|
97
|
-
parser = nil
|
|
98
|
+
connect_request = parser = nil
|
|
98
99
|
|
|
99
100
|
if initial_state == :connecting
|
|
100
101
|
parser = @parser
|
|
101
102
|
@parser.reset
|
|
103
|
+
if @pending.first.is_a?(ConnectRequest)
|
|
104
|
+
connect_request = @pending.shift # this happened when reenqueing
|
|
105
|
+
end
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
idling
|
|
105
109
|
|
|
106
110
|
@parser = parser
|
|
107
|
-
|
|
111
|
+
if connect_request
|
|
112
|
+
@inflight += 1
|
|
113
|
+
parser.send(connect_request)
|
|
114
|
+
end
|
|
108
115
|
transition(:connecting)
|
|
109
116
|
end
|
|
110
117
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -202,7 +202,10 @@ module HTTPX
|
|
|
202
202
|
log { "failed connecting to proxy, trying next..." }
|
|
203
203
|
request.transition(:idle)
|
|
204
204
|
send_request(request, selector, options)
|
|
205
|
-
|
|
205
|
+
|
|
206
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
207
|
+
# verify retriability again.
|
|
208
|
+
return fetch_response(request, selector, options)
|
|
206
209
|
end
|
|
207
210
|
response
|
|
208
211
|
rescue ProxyError
|
|
@@ -320,7 +323,12 @@ module HTTPX
|
|
|
320
323
|
|
|
321
324
|
def purge_after_closed
|
|
322
325
|
super
|
|
323
|
-
|
|
326
|
+
|
|
327
|
+
while @io.respond_to?(:proxy_io)
|
|
328
|
+
@io = @io.proxy_io
|
|
329
|
+
|
|
330
|
+
super
|
|
331
|
+
end
|
|
324
332
|
end
|
|
325
333
|
end
|
|
326
334
|
|
|
@@ -16,25 +16,7 @@ module HTTPX
|
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
18
|
def load_dependencies(klass)
|
|
19
|
-
klass.plugin(:retries
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Servers send the "Retry-After" header field to indicate how long the
|
|
23
|
-
# user agent ought to wait before making a follow-up request. When
|
|
24
|
-
# sent with a 503 (Service Unavailable) response, Retry-After indicates
|
|
25
|
-
# how long the service is expected to be unavailable to the client.
|
|
26
|
-
# When sent with any 3xx (Redirection) response, Retry-After indicates
|
|
27
|
-
# the minimum time that the user agent is asked to wait before issuing
|
|
28
|
-
# the redirected request.
|
|
29
|
-
#
|
|
30
|
-
def retry_after_rate_limit(_, response)
|
|
31
|
-
return unless response.is_a?(Response)
|
|
32
|
-
|
|
33
|
-
retry_after = response.headers["retry-after"]
|
|
34
|
-
|
|
35
|
-
return unless retry_after
|
|
36
|
-
|
|
37
|
-
Utils.parse_retry_after(retry_after)
|
|
19
|
+
klass.plugin(:retries)
|
|
38
20
|
end
|
|
39
21
|
end
|
|
40
22
|
|
|
@@ -52,6 +34,24 @@ module HTTPX
|
|
|
52
34
|
def rate_limit_error?(response)
|
|
53
35
|
response.is_a?(Response) && RATE_LIMIT_CODES.include?(response.status)
|
|
54
36
|
end
|
|
37
|
+
|
|
38
|
+
# Servers send the "Retry-After" header field to indicate how long the
|
|
39
|
+
# user agent ought to wait before making a follow-up request. When
|
|
40
|
+
# sent with a 503 (Service Unavailable) response, Retry-After indicates
|
|
41
|
+
# how long the service is expected to be unavailable to the client.
|
|
42
|
+
# When sent with any 3xx (Redirection) response, Retry-After indicates
|
|
43
|
+
# the minimum time that the user agent is asked to wait before issuing
|
|
44
|
+
# the redirected request.
|
|
45
|
+
#
|
|
46
|
+
def when_to_retry(_, response, options)
|
|
47
|
+
return super unless response.is_a?(Response)
|
|
48
|
+
|
|
49
|
+
retry_after = response.headers["retry-after"]
|
|
50
|
+
|
|
51
|
+
return super unless retry_after
|
|
52
|
+
|
|
53
|
+
Utils.parse_retry_after(retry_after)
|
|
54
|
+
end
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -148,10 +148,7 @@ module HTTPX
|
|
|
148
148
|
log { "failed to get response, #{request.retries} tries to go..." }
|
|
149
149
|
prepare_to_retry(request, response)
|
|
150
150
|
|
|
151
|
-
retry_after = options
|
|
152
|
-
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
153
|
-
|
|
154
|
-
if retry_after
|
|
151
|
+
if (retry_after = when_to_retry(request, response, options))
|
|
155
152
|
# apply jitter
|
|
156
153
|
if (jitter = request.options.retry_jitter)
|
|
157
154
|
retry_after = jitter.call(retry_after)
|
|
@@ -169,11 +166,15 @@ module HTTPX
|
|
|
169
166
|
send_request(request, selector, options)
|
|
170
167
|
end
|
|
171
168
|
end
|
|
169
|
+
|
|
170
|
+
return
|
|
172
171
|
else
|
|
173
172
|
send_request(request, selector, options)
|
|
174
|
-
end
|
|
175
173
|
|
|
176
|
-
|
|
174
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
175
|
+
# verify retriability again.
|
|
176
|
+
return fetch_response(request, selector, options)
|
|
177
|
+
end
|
|
177
178
|
end
|
|
178
179
|
response
|
|
179
180
|
end
|
|
@@ -201,6 +202,12 @@ module HTTPX
|
|
|
201
202
|
request.transition(:idle)
|
|
202
203
|
end
|
|
203
204
|
|
|
205
|
+
def when_to_retry(request, response, options)
|
|
206
|
+
retry_after = options.retry_after
|
|
207
|
+
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
208
|
+
retry_after
|
|
209
|
+
end
|
|
210
|
+
|
|
204
211
|
#
|
|
205
212
|
# Attempt to set the request to perform a partial range request.
|
|
206
213
|
# This happens if the peer server accepts byte-range requests, and
|
|
@@ -237,14 +244,15 @@ module HTTPX
|
|
|
237
244
|
def initialize(*args)
|
|
238
245
|
super
|
|
239
246
|
@retries = @options.max_retries
|
|
247
|
+
@partial_response = nil
|
|
240
248
|
end
|
|
241
249
|
|
|
242
250
|
def response=(response)
|
|
243
|
-
if @partial_response
|
|
251
|
+
if (partial_response = @partial_response)
|
|
244
252
|
if response.is_a?(Response) && response.status == 206
|
|
245
|
-
response.from_partial_response(
|
|
253
|
+
response.from_partial_response(partial_response)
|
|
246
254
|
else
|
|
247
|
-
|
|
255
|
+
partial_response.close
|
|
248
256
|
end
|
|
249
257
|
@partial_response = nil
|
|
250
258
|
end
|
|
@@ -106,6 +106,7 @@ module HTTPX
|
|
|
106
106
|
error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
|
|
107
107
|
error.set_backtrace(caller)
|
|
108
108
|
response = ErrorResponse.new(request, error)
|
|
109
|
+
request.response = response
|
|
109
110
|
request.emit(:response, response)
|
|
110
111
|
response
|
|
111
112
|
end
|
|
@@ -189,6 +189,10 @@ module HTTPX
|
|
|
189
189
|
!@closed
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
def force_close(*)
|
|
193
|
+
terminate
|
|
194
|
+
end
|
|
195
|
+
|
|
192
196
|
def terminate
|
|
193
197
|
return if @closed
|
|
194
198
|
|
|
@@ -202,6 +206,8 @@ module HTTPX
|
|
|
202
206
|
terminate
|
|
203
207
|
end
|
|
204
208
|
|
|
209
|
+
alias_method :on_io_error, :on_error
|
|
210
|
+
|
|
205
211
|
# noop (the owner connection will take of it)
|
|
206
212
|
def handle_socket_timeout(interval); end
|
|
207
213
|
end
|