httpx 1.7.2 → 1.7.6
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/doc/release_notes/1_7_5.md +10 -0
- data/doc/release_notes/1_7_6.md +24 -0
- data/lib/httpx/adapters/datadog.rb +37 -64
- data/lib/httpx/adapters/webmock.rb +3 -4
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +26 -18
- data/lib/httpx/connection/http2.rb +53 -33
- data/lib/httpx/connection.rb +152 -63
- data/lib/httpx/io/ssl.rb +20 -8
- data/lib/httpx/io/tcp.rb +18 -12
- data/lib/httpx/io/unix.rb +13 -9
- data/lib/httpx/options.rb +23 -7
- data/lib/httpx/parser/http1.rb +14 -4
- data/lib/httpx/plugins/auth/digest.rb +2 -1
- data/lib/httpx/plugins/auth.rb +23 -9
- 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 +33 -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/pool.rb +7 -9
- data/lib/httpx/request.rb +15 -3
- data/lib/httpx/resolver/multi.rb +1 -8
- data/lib/httpx/resolver/native.rb +2 -2
- data/lib/httpx/resolver/resolver.rb +21 -2
- data/lib/httpx/resolver/system.rb +3 -1
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +19 -16
- data/lib/httpx/session.rb +34 -44
- data/lib/httpx/timers.rb +4 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +2 -0
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +3 -1
- data/sig/connection/http2.rbs +11 -4
- data/sig/connection.rbs +16 -2
- data/sig/io/ssl.rbs +1 -0
- data/sig/io/tcp.rbs +2 -2
- data/sig/options.rbs +8 -3
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth.rbs +5 -2
- 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 +21 -2
- data/sig/plugins/fiber_concurrency.rbs +2 -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/pool.rbs +1 -1
- data/sig/request.rbs +4 -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 +7 -2
- data/sig/session.rbs +2 -0
- data/sig/timers.rbs +2 -0
- data/sig/transcoder/gzip.rbs +1 -1
- data/sig/transcoder.rbs +0 -2
- metadata +13 -3
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -14,6 +14,8 @@ module HTTPX
|
|
|
14
14
|
@state = :idle
|
|
15
15
|
@buffer = "".b
|
|
16
16
|
@headers = {}
|
|
17
|
+
@content_length = nil
|
|
18
|
+
@_has_trailers = @upgrade = false
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def <<(chunk)
|
|
@@ -25,7 +27,8 @@ module HTTPX
|
|
|
25
27
|
@state = :idle
|
|
26
28
|
@headers = {}
|
|
27
29
|
@content_length = nil
|
|
28
|
-
@_has_trailers =
|
|
30
|
+
@_has_trailers = @upgrade = false
|
|
31
|
+
@buffer = @buffer.to_s
|
|
29
32
|
@buffer.clear
|
|
30
33
|
end
|
|
31
34
|
|
|
@@ -34,7 +37,7 @@ module HTTPX
|
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def upgrade_data
|
|
37
|
-
@buffer
|
|
40
|
+
@buffer.to_s
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
private
|
|
@@ -55,6 +58,7 @@ module HTTPX
|
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def parse_headline
|
|
61
|
+
#: @type ivar @buffer: String
|
|
58
62
|
idx = @buffer.index("\n")
|
|
59
63
|
return unless idx
|
|
60
64
|
|
|
@@ -75,6 +79,8 @@ module HTTPX
|
|
|
75
79
|
headers = @headers
|
|
76
80
|
buffer = @buffer
|
|
77
81
|
|
|
82
|
+
#: @type var buffer: String
|
|
83
|
+
|
|
78
84
|
while (idx = buffer.index("\n"))
|
|
79
85
|
# @type var line: String
|
|
80
86
|
line = buffer.byteslice(0..idx)
|
|
@@ -118,17 +124,20 @@ module HTTPX
|
|
|
118
124
|
|
|
119
125
|
def parse_data
|
|
120
126
|
if @buffer.respond_to?(:each)
|
|
127
|
+
# @type ivar @buffer: Transcoder::Chunker::Decoder
|
|
121
128
|
@buffer.each do |chunk|
|
|
122
129
|
@observer.on_data(chunk)
|
|
123
130
|
end
|
|
124
131
|
elsif @content_length
|
|
125
|
-
# @type
|
|
132
|
+
# @type ivar @buffer: String
|
|
126
133
|
data = @buffer.byteslice(0, @content_length)
|
|
134
|
+
# @type var data: String
|
|
127
135
|
@buffer = @buffer.byteslice(@content_length..-1) || "".b
|
|
128
136
|
@content_length -= data.bytesize
|
|
129
137
|
@observer.on_data(data)
|
|
130
138
|
data.clear
|
|
131
139
|
else
|
|
140
|
+
# @type ivar @buffer: String
|
|
132
141
|
@observer.on_data(@buffer)
|
|
133
142
|
@buffer.clear
|
|
134
143
|
end
|
|
@@ -152,7 +161,7 @@ module HTTPX
|
|
|
152
161
|
tr_encoding.split(/ *, */).each do |encoding|
|
|
153
162
|
case encoding
|
|
154
163
|
when "chunked"
|
|
155
|
-
@buffer = Transcoder::Chunker::Decoder.new(@buffer, @_has_trailers)
|
|
164
|
+
@buffer = Transcoder::Chunker::Decoder.new(@buffer.to_s, @_has_trailers)
|
|
156
165
|
end
|
|
157
166
|
end
|
|
158
167
|
end
|
|
@@ -165,6 +174,7 @@ module HTTPX
|
|
|
165
174
|
if @content_length
|
|
166
175
|
@content_length <= 0
|
|
167
176
|
elsif @buffer.respond_to?(:finished?)
|
|
177
|
+
# @type ivar @buffer: Transcoder::Chunker::Decoder
|
|
168
178
|
@buffer.finished?
|
|
169
179
|
else
|
|
170
180
|
false
|
data/lib/httpx/plugins/auth.rb
CHANGED
|
@@ -44,6 +44,7 @@ module HTTPX
|
|
|
44
44
|
super
|
|
45
45
|
|
|
46
46
|
@auth_header_value = nil
|
|
47
|
+
@auth_header_value_mtx = Thread::Mutex.new
|
|
47
48
|
@skip_auth_header_value = false
|
|
48
49
|
end
|
|
49
50
|
|
|
@@ -63,7 +64,9 @@ module HTTPX
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
def reset_auth_header_value!
|
|
66
|
-
@
|
|
67
|
+
@auth_header_value_mtx.synchronize do
|
|
68
|
+
@auth_header_value = nil
|
|
69
|
+
end
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
private
|
|
@@ -71,9 +74,11 @@ module HTTPX
|
|
|
71
74
|
def send_request(request, *)
|
|
72
75
|
return super if @skip_auth_header_value || request.authorized?
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
auth_header_value = @auth_header_value_mtx.synchronize do
|
|
78
|
+
@auth_header_value ||= generate_auth_token
|
|
79
|
+
end
|
|
75
80
|
|
|
76
|
-
request.authorize(
|
|
81
|
+
request.authorize(auth_header_value) if auth_header_value
|
|
77
82
|
|
|
78
83
|
super
|
|
79
84
|
end
|
|
@@ -92,9 +97,11 @@ module HTTPX
|
|
|
92
97
|
end
|
|
93
98
|
|
|
94
99
|
module RequestMethods
|
|
100
|
+
attr_reader :auth_token_value
|
|
101
|
+
|
|
95
102
|
def initialize(*)
|
|
96
103
|
super
|
|
97
|
-
@auth_token_value = nil
|
|
104
|
+
@auth_token_value = @auth_header_value = nil
|
|
98
105
|
end
|
|
99
106
|
|
|
100
107
|
def authorized?
|
|
@@ -102,19 +109,20 @@ module HTTPX
|
|
|
102
109
|
end
|
|
103
110
|
|
|
104
111
|
def unauthorize!
|
|
105
|
-
return unless (auth_value = @
|
|
112
|
+
return unless (auth_value = @auth_header_value)
|
|
106
113
|
|
|
107
114
|
@headers.get("authorization").delete(auth_value)
|
|
108
115
|
|
|
109
|
-
@auth_token_value = nil
|
|
116
|
+
@auth_token_value = @auth_header_value = nil
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
def authorize(auth_value)
|
|
120
|
+
@auth_header_value = auth_value
|
|
113
121
|
if (auth_type = @options.auth_header_type)
|
|
114
|
-
|
|
122
|
+
@auth_header_value = "#{auth_type} #{@auth_header_value}"
|
|
115
123
|
end
|
|
116
124
|
|
|
117
|
-
@headers.add("authorization",
|
|
125
|
+
@headers.add("authorization", @auth_header_value)
|
|
118
126
|
|
|
119
127
|
@auth_token_value = auth_value
|
|
120
128
|
end
|
|
@@ -138,8 +146,14 @@ module HTTPX
|
|
|
138
146
|
return unless auth_error?(response, request.options) ||
|
|
139
147
|
(@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))
|
|
140
148
|
|
|
149
|
+
# regenerate token before retry, but only if it's the first request from batch failing.
|
|
150
|
+
# otherwise, it means that the first request already passed here, so this request should
|
|
151
|
+
# use whatever was generated for it.
|
|
152
|
+
@auth_header_value_mtx.synchronize do
|
|
153
|
+
@auth_header_value = generate_auth_token if request.auth_token_value == @auth_header_value
|
|
154
|
+
end
|
|
155
|
+
|
|
141
156
|
request.unauthorize!
|
|
142
|
-
@auth_header_value = generate_auth_token
|
|
143
157
|
end
|
|
144
158
|
|
|
145
159
|
def auth_error?(response, options)
|
data/lib/httpx/plugins/brotli.rb
CHANGED
|
@@ -3,11 +3,33 @@
|
|
|
3
3
|
module HTTPX
|
|
4
4
|
module Plugins
|
|
5
5
|
module Brotli
|
|
6
|
+
class Error < HTTPX::Error; end
|
|
7
|
+
|
|
6
8
|
class Deflater < Transcoder::Deflater
|
|
9
|
+
def initialize(body)
|
|
10
|
+
@compressor = ::Brotli::Compressor.new
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
def deflate(chunk)
|
|
8
|
-
return
|
|
15
|
+
return @compressor.process(chunk) << @compressor.flush if chunk
|
|
16
|
+
|
|
17
|
+
@compressor.finish
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Inflater
|
|
22
|
+
def initialize(bytesize)
|
|
23
|
+
@inflater = ::Brotli::Decompressor.new
|
|
24
|
+
@bytesize = bytesize
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(chunk)
|
|
28
|
+
buffer = @inflater.process(chunk)
|
|
29
|
+
@bytesize -= chunk.bytesize
|
|
30
|
+
raise Error, "Unexpected end of compressed stream" if @bytesize <= 0 && !@inflater.finished?
|
|
9
31
|
|
|
10
|
-
|
|
32
|
+
buffer
|
|
11
33
|
end
|
|
12
34
|
end
|
|
13
35
|
|
|
@@ -30,19 +52,25 @@ module HTTPX
|
|
|
30
52
|
module_function
|
|
31
53
|
|
|
32
54
|
def load_dependencies(*)
|
|
55
|
+
gem "brotli", ">= 0.8.0"
|
|
33
56
|
require "brotli"
|
|
34
57
|
end
|
|
35
58
|
|
|
36
59
|
def self.extra_options(options)
|
|
37
|
-
|
|
60
|
+
supported_compression_formats = (%w[br] + options.supported_compression_formats).freeze
|
|
61
|
+
options.merge(
|
|
62
|
+
supported_compression_formats: supported_compression_formats,
|
|
63
|
+
headers: options.headers_class.new(options.headers.merge("accept-encoding" => supported_compression_formats))
|
|
64
|
+
)
|
|
38
65
|
end
|
|
39
66
|
|
|
40
67
|
def encode(body)
|
|
41
68
|
Deflater.new(body)
|
|
42
69
|
end
|
|
43
70
|
|
|
44
|
-
def decode(
|
|
45
|
-
|
|
71
|
+
def decode(response, bytesize: nil)
|
|
72
|
+
bytesize ||= response.headers.key?("content-length") ? response.headers["content-length"].to_i : Float::INFINITY
|
|
73
|
+
Inflater.new(bytesize)
|
|
46
74
|
end
|
|
47
75
|
end
|
|
48
76
|
register_plugin :brotli, Brotli
|
|
@@ -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)
|
|
@@ -46,6 +70,9 @@ module HTTPX
|
|
|
46
70
|
module RequestMethods
|
|
47
71
|
def initialize(*)
|
|
48
72
|
super
|
|
73
|
+
|
|
74
|
+
@informational_status = nil
|
|
75
|
+
|
|
49
76
|
return if @body.empty?
|
|
50
77
|
|
|
51
78
|
threshold = @options.expect_threshold_size
|
|
@@ -89,7 +116,7 @@ module HTTPX
|
|
|
89
116
|
set_request_timeout(:expect_timeout, request, expect_timeout, :expect, %i[body response]) do
|
|
90
117
|
# expect timeout expired
|
|
91
118
|
if request.state == :expect && !request.expects?
|
|
92
|
-
Expect.no_expect_store
|
|
119
|
+
Expect.no_expect_store.add(request.origin)
|
|
93
120
|
request.headers.delete("expect")
|
|
94
121
|
consume
|
|
95
122
|
end
|
|
@@ -108,7 +135,10 @@ module HTTPX
|
|
|
108
135
|
request.headers.delete("expect")
|
|
109
136
|
request.transition(:idle)
|
|
110
137
|
send_request(request, selector, options)
|
|
111
|
-
|
|
138
|
+
|
|
139
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
140
|
+
# verify retriability again.
|
|
141
|
+
return fetch_response(request, selector, options)
|
|
112
142
|
end
|
|
113
143
|
|
|
114
144
|
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
|