httpx 0.20.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +0 -48
- data/README.md +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +5 -5
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -14
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +52 -0
- data/lib/httpx/transcoder.rb +5 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +71 -0
- data/sig/plugins/compression.rbs +7 -5
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +18 -4
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +7 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- data/sig/registry.rbs +0 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
-
class HTTPProxyError <
|
4
|
+
class HTTPProxyError < ConnectionError; end
|
5
5
|
|
6
6
|
module Plugins
|
7
7
|
#
|
@@ -12,12 +12,24 @@ module HTTPX
|
|
12
12
|
# * Socks4/4a proxies
|
13
13
|
# * Socks5 proxies
|
14
14
|
#
|
15
|
-
# https://gitlab.com/
|
15
|
+
# https://gitlab.com/os85/httpx/wikis/Proxy
|
16
16
|
#
|
17
17
|
module Proxy
|
18
18
|
Error = HTTPProxyError
|
19
19
|
PROXY_ERRORS = [TimeoutError, IOError, SystemCallError, Error].freeze
|
20
20
|
|
21
|
+
class << self
|
22
|
+
def configure(klass)
|
23
|
+
klass.plugin(:"proxy/http")
|
24
|
+
klass.plugin(:"proxy/socks4")
|
25
|
+
klass.plugin(:"proxy/socks5")
|
26
|
+
end
|
27
|
+
|
28
|
+
def extra_options(options)
|
29
|
+
options.merge(supported_proxy_protocols: [])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
21
33
|
class Parameters
|
22
34
|
attr_reader :uri, :username, :password, :scheme
|
23
35
|
|
@@ -41,7 +53,7 @@ module HTTPX
|
|
41
53
|
|
42
54
|
auth_scheme = scheme.to_s.capitalize
|
43
55
|
|
44
|
-
require_relative "
|
56
|
+
require_relative "auth/#{scheme}" unless defined?(Authentication) && Authentication.const_defined?(auth_scheme, false)
|
45
57
|
|
46
58
|
@authenticator = Authentication.const_get(auth_scheme).new(@username, @password, **extra)
|
47
59
|
end
|
@@ -77,48 +89,35 @@ module HTTPX
|
|
77
89
|
end
|
78
90
|
end
|
79
91
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
klass.plugin(:"proxy/socks5")
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
92
|
+
# adds support for the following options:
|
93
|
+
#
|
94
|
+
# :proxy :: proxy options defining *:uri*, *:username*, *:password* or
|
95
|
+
# *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
|
88
96
|
module OptionsMethods
|
89
97
|
def option_proxy(value)
|
90
98
|
value.is_a?(Parameters) ? value : Hash[value]
|
91
99
|
end
|
100
|
+
|
101
|
+
def option_supported_proxy_protocols(value)
|
102
|
+
raise TypeError, ":supported_proxy_protocols must be an Array" unless value.is_a?(Array)
|
103
|
+
|
104
|
+
value.map(&:to_s)
|
105
|
+
end
|
92
106
|
end
|
93
107
|
|
94
108
|
module InstanceMethods
|
95
109
|
private
|
96
110
|
|
97
|
-
def proxy_uris(uri, options)
|
98
|
-
@_proxy_uris ||= begin
|
99
|
-
uris = options.proxy ? Array(options.proxy[:uri]) : []
|
100
|
-
if uris.empty?
|
101
|
-
uri = URI(uri).find_proxy
|
102
|
-
uris << uri if uri
|
103
|
-
end
|
104
|
-
uris
|
105
|
-
end
|
106
|
-
return if @_proxy_uris.empty?
|
107
|
-
|
108
|
-
proxy_opts = { uri: @_proxy_uris.first }
|
109
|
-
proxy_opts = options.proxy.merge(proxy_opts) if options.proxy
|
110
|
-
proxy_opts
|
111
|
-
end
|
112
|
-
|
113
111
|
def find_connection(request, connections, options)
|
114
112
|
return super unless options.respond_to?(:proxy)
|
115
113
|
|
116
|
-
uri =
|
117
|
-
|
118
|
-
|
114
|
+
uri = request.uri
|
115
|
+
|
116
|
+
proxy_options = proxy_options(uri, options)
|
117
|
+
|
118
|
+
return super(request, connections, proxy_options) unless proxy_options.proxy
|
119
119
|
|
120
|
-
|
121
|
-
connection = pool.find_connection(uri, proxy_options) || build_connection(uri, proxy_options)
|
120
|
+
connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
|
122
121
|
unless connections.nil? || connections.include?(connection)
|
123
122
|
connections << connection
|
124
123
|
set_connection_callbacks(connection, connections, options)
|
@@ -126,40 +125,61 @@ module HTTPX
|
|
126
125
|
connection
|
127
126
|
end
|
128
127
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
128
|
+
def proxy_options(request_uri, options)
|
129
|
+
proxy_opts = if (next_proxy = request_uri.find_proxy)
|
130
|
+
{ uri: next_proxy }
|
131
|
+
else
|
132
|
+
proxy = options.proxy
|
133
|
+
|
134
|
+
return options unless proxy
|
135
|
+
|
136
|
+
return options.merge(proxy: nil) unless proxy.key?(:uri)
|
137
|
+
|
138
|
+
@_proxy_uris ||= Array(proxy[:uri])
|
139
|
+
|
140
|
+
next_proxy = @_proxy_uris.first
|
141
|
+
raise Error, "Failed to connect to proxy" unless next_proxy
|
142
|
+
|
143
|
+
next_proxy = URI(next_proxy)
|
144
|
+
|
145
|
+
raise Error,
|
146
|
+
"#{next_proxy.scheme}: unsupported proxy protocol" unless options.supported_proxy_protocols.include?(next_proxy.scheme)
|
132
147
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
148
|
+
if proxy.key?(:no_proxy)
|
149
|
+
|
150
|
+
no_proxy = proxy[:no_proxy]
|
151
|
+
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
152
|
+
|
153
|
+
return options.merge(proxy: nil) unless URI::Generic.use_proxy?(request_uri.host, next_proxy.host,
|
154
|
+
next_proxy.port, no_proxy)
|
155
|
+
end
|
156
|
+
|
157
|
+
proxy.merge(uri: next_proxy)
|
137
158
|
end
|
159
|
+
|
160
|
+
proxy = Parameters.new(**proxy_opts)
|
161
|
+
|
162
|
+
options.merge(proxy: proxy)
|
138
163
|
end
|
139
164
|
|
140
165
|
def fetch_response(request, connections, options)
|
141
166
|
response = super
|
142
167
|
|
143
|
-
if response.is_a?(ErrorResponse) &&
|
144
|
-
__proxy_error?(response) && !@_proxy_uris.empty?
|
168
|
+
if response.is_a?(ErrorResponse) && proxy_error?(request, response)
|
145
169
|
@_proxy_uris.shift
|
170
|
+
|
171
|
+
# return last error response if no more proxies to try
|
172
|
+
return response if @_proxy_uris.empty?
|
173
|
+
|
146
174
|
log { "failed connecting to proxy, trying next..." }
|
147
175
|
request.transition(:idle)
|
148
|
-
|
149
|
-
connections << connection unless connections.include?(connection)
|
150
|
-
connection.send(request)
|
176
|
+
send_request(request, connections, options)
|
151
177
|
return
|
152
178
|
end
|
153
179
|
response
|
154
180
|
end
|
155
181
|
|
156
|
-
def
|
157
|
-
return if options.proxy
|
158
|
-
|
159
|
-
super
|
160
|
-
end
|
161
|
-
|
162
|
-
def __proxy_error?(response)
|
182
|
+
def proxy_error?(_request, response)
|
163
183
|
error = response.error
|
164
184
|
case error
|
165
185
|
when NativeResolveError
|
@@ -216,13 +236,6 @@ module HTTPX
|
|
216
236
|
end
|
217
237
|
end
|
218
238
|
|
219
|
-
def send(request)
|
220
|
-
return super unless @options.proxy
|
221
|
-
return super unless connecting?
|
222
|
-
|
223
|
-
@pending << request
|
224
|
-
end
|
225
|
-
|
226
239
|
def connecting?
|
227
240
|
return super unless @options.proxy
|
228
241
|
|
@@ -244,13 +257,19 @@ module HTTPX
|
|
244
257
|
return super unless @options.proxy
|
245
258
|
|
246
259
|
@state = :open
|
247
|
-
|
248
|
-
|
260
|
+
|
261
|
+
super
|
249
262
|
emit(:close)
|
250
263
|
end
|
251
264
|
|
252
265
|
private
|
253
266
|
|
267
|
+
def initialize_type(uri, options)
|
268
|
+
return super unless options.proxy
|
269
|
+
|
270
|
+
"tcp"
|
271
|
+
end
|
272
|
+
|
254
273
|
def connect
|
255
274
|
return super unless @options.proxy
|
256
275
|
|
@@ -278,7 +297,7 @@ module HTTPX
|
|
278
297
|
register_plugin :proxy, Proxy
|
279
298
|
end
|
280
299
|
|
281
|
-
class ProxySSL <
|
300
|
+
class ProxySSL < SSL
|
282
301
|
def initialize(tcp, request_uri, options)
|
283
302
|
@io = tcp.to_io
|
284
303
|
super(request_uri, tcp.addresses, options)
|
@@ -8,7 +8,7 @@ module HTTPX
|
|
8
8
|
# In order to benefit from this, requests are sent one at a time, so that
|
9
9
|
# no push responses are received after corresponding request has been sent.
|
10
10
|
#
|
11
|
-
# https://gitlab.com/
|
11
|
+
# https://gitlab.com/os85/httpx/wikis/Server-Push
|
12
12
|
#
|
13
13
|
module PushPromise
|
14
14
|
def self.extra_options(options)
|
@@ -9,7 +9,7 @@ module HTTPX
|
|
9
9
|
# * when the server is unavailable (503);
|
10
10
|
# * when a 3xx request comes with a "retry-after" value
|
11
11
|
#
|
12
|
-
# https://gitlab.com/
|
12
|
+
# https://gitlab.com/os85/httpx/wikis/Rate-Limiter
|
13
13
|
#
|
14
14
|
module RateLimiter
|
15
15
|
class << self
|
@@ -23,6 +23,8 @@ module HTTPX
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def retry_on_rate_limited_response(response)
|
26
|
+
return false unless response.is_a?(Response)
|
27
|
+
|
26
28
|
status = response.status
|
27
29
|
|
28
30
|
RATE_LIMIT_CODES.include?(status)
|
@@ -37,6 +39,8 @@ module HTTPX
|
|
37
39
|
# the redirected request.
|
38
40
|
#
|
39
41
|
def retry_after_rate_limit(_, response)
|
42
|
+
return unless response.is_a?(Response)
|
43
|
+
|
40
44
|
retry_after = response.headers["retry-after"]
|
41
45
|
|
42
46
|
return unless retry_after
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require_relative "store"
|
5
|
+
|
6
|
+
module HTTPX::Plugins
|
7
|
+
module ResponseCache
|
8
|
+
class FileStore < Store
|
9
|
+
def initialize(dir = Dir.tmpdir)
|
10
|
+
@dir = Pathname.new(dir)
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear
|
14
|
+
# delete all files
|
15
|
+
end
|
16
|
+
|
17
|
+
def cached?(request)
|
18
|
+
file_path = @dir.join(request.response_cache_key)
|
19
|
+
|
20
|
+
exist?(file_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def _get(request)
|
26
|
+
return unless cached?(request)
|
27
|
+
|
28
|
+
File.open(@dir.join(request.response_cache_key))
|
29
|
+
end
|
30
|
+
|
31
|
+
def _set(request, response)
|
32
|
+
file_path = @dir.join(request.response_cache_key)
|
33
|
+
|
34
|
+
response.copy_to(file_path)
|
35
|
+
|
36
|
+
response.body.rewind
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,47 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
|
-
|
5
3
|
module HTTPX::Plugins
|
6
4
|
module ResponseCache
|
7
5
|
class Store
|
8
|
-
extend Forwardable
|
9
|
-
|
10
|
-
def_delegator :@store, :clear
|
11
|
-
|
12
6
|
def initialize
|
13
7
|
@store = {}
|
8
|
+
@store_mutex = Thread::Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def clear
|
12
|
+
@store_mutex.synchronize { @store.clear }
|
14
13
|
end
|
15
14
|
|
16
|
-
def lookup(
|
17
|
-
|
15
|
+
def lookup(request)
|
16
|
+
responses = _get(request)
|
17
|
+
|
18
|
+
return unless responses
|
19
|
+
|
20
|
+
responses.find(&method(:match_by_vary?).curry(2)[request])
|
18
21
|
end
|
19
22
|
|
20
|
-
def cached?(
|
21
|
-
|
23
|
+
def cached?(request)
|
24
|
+
lookup(request)
|
22
25
|
end
|
23
26
|
|
24
|
-
def cache(
|
25
|
-
|
27
|
+
def cache(request, response)
|
28
|
+
return unless ResponseCache.cacheable_request?(request) && ResponseCache.cacheable_response?(response)
|
29
|
+
|
30
|
+
_set(request, response)
|
26
31
|
end
|
27
32
|
|
28
33
|
def prepare(request)
|
29
|
-
cached_response =
|
34
|
+
cached_response = lookup(request)
|
30
35
|
|
31
36
|
return unless cached_response
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
if (vary = cached_response.headers["vary"])
|
36
|
-
if vary == "*"
|
37
|
-
return unless request.headers.same_headers?(original_request.headers)
|
38
|
-
else
|
39
|
-
return unless vary.split(/ *, */).all? do |cache_field|
|
40
|
-
cache_field.downcase!
|
41
|
-
!original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
38
|
+
return unless match_by_vary?(request, cached_response)
|
45
39
|
|
46
40
|
if !request.headers.key?("if-modified-since") && (last_modified = cached_response.headers["last-modified"])
|
47
41
|
request.headers.add("if-modified-since", last_modified)
|
@@ -51,6 +45,49 @@ module HTTPX::Plugins
|
|
51
45
|
request.headers.add("if-none-match", etag)
|
52
46
|
end
|
53
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def match_by_vary?(request, response)
|
52
|
+
vary = response.vary
|
53
|
+
|
54
|
+
return true unless vary
|
55
|
+
|
56
|
+
original_request = response.instance_variable_get(:@request)
|
57
|
+
|
58
|
+
return request.headers.same_headers?(original_request.headers) if vary == %w[*]
|
59
|
+
|
60
|
+
vary.all? do |cache_field|
|
61
|
+
cache_field.downcase!
|
62
|
+
!original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def _get(request)
|
67
|
+
@store_mutex.synchronize do
|
68
|
+
responses = @store[request.response_cache_key]
|
69
|
+
|
70
|
+
return unless responses
|
71
|
+
|
72
|
+
responses.select! do |res|
|
73
|
+
!res.body.closed? && res.fresh?
|
74
|
+
end
|
75
|
+
|
76
|
+
responses
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def _set(request, response)
|
81
|
+
@store_mutex.synchronize do
|
82
|
+
responses = (@store[request.response_cache_key] ||= [])
|
83
|
+
|
84
|
+
responses.reject! do |res|
|
85
|
+
res.body.closed? || !res.fresh? || match_by_vary?(request, res)
|
86
|
+
end
|
87
|
+
|
88
|
+
responses << response
|
89
|
+
end
|
90
|
+
end
|
54
91
|
end
|
55
92
|
end
|
56
93
|
end
|
@@ -5,11 +5,13 @@ module HTTPX
|
|
5
5
|
#
|
6
6
|
# This plugin adds support for retrying requests when certain errors happen.
|
7
7
|
#
|
8
|
-
# https://gitlab.com/
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Response-Cache
|
9
9
|
#
|
10
10
|
module ResponseCache
|
11
|
-
CACHEABLE_VERBS = %
|
11
|
+
CACHEABLE_VERBS = %w[GET HEAD].freeze
|
12
|
+
CACHEABLE_STATUS_CODES = [200, 203, 206, 300, 301, 410].freeze
|
12
13
|
private_constant :CACHEABLE_VERBS
|
14
|
+
private_constant :CACHEABLE_STATUS_CODES
|
13
15
|
|
14
16
|
class << self
|
15
17
|
def load_dependencies(*)
|
@@ -17,14 +19,28 @@ module HTTPX
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def cacheable_request?(request)
|
20
|
-
CACHEABLE_VERBS.include?(request.verb)
|
22
|
+
CACHEABLE_VERBS.include?(request.verb) &&
|
23
|
+
(
|
24
|
+
!request.headers.key?("cache-control") || !request.headers.get("cache-control").include?("no-store")
|
25
|
+
)
|
21
26
|
end
|
22
27
|
|
23
28
|
def cacheable_response?(response)
|
24
29
|
response.is_a?(Response) &&
|
25
|
-
|
30
|
+
(
|
31
|
+
response.cache_control.nil? ||
|
32
|
+
# TODO: !response.cache_control.include?("private") && is shared cache
|
33
|
+
!response.cache_control.include?("no-store")
|
34
|
+
) &&
|
35
|
+
CACHEABLE_STATUS_CODES.include?(response.status) &&
|
36
|
+
# RFC 2616 13.4 - A response received with a status code of 200, 203, 206, 300, 301 or
|
37
|
+
# 410 MAY be stored by a cache and used in reply to a subsequent
|
38
|
+
# request, subject to the expiration mechanism, unless a cache-control
|
39
|
+
# directive prohibits caching. However, a cache that does not support
|
40
|
+
# the Range and Content-Range headers MUST NOT cache 206 (Partial
|
41
|
+
# Content) responses.
|
26
42
|
response.status != 206 && (
|
27
|
-
response.headers.key?("etag") || response.headers.key?("last-modified
|
43
|
+
response.headers.key?("etag") || response.headers.key?("last-modified") || response.fresh?
|
28
44
|
)
|
29
45
|
end
|
30
46
|
|
@@ -52,7 +68,7 @@ module HTTPX
|
|
52
68
|
|
53
69
|
def build_request(*)
|
54
70
|
request = super
|
55
|
-
return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request
|
71
|
+
return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request)
|
56
72
|
|
57
73
|
@options.response_cache_store.prepare(request)
|
58
74
|
|
@@ -62,24 +78,101 @@ module HTTPX
|
|
62
78
|
def fetch_response(request, *)
|
63
79
|
response = super
|
64
80
|
|
65
|
-
|
81
|
+
return unless response
|
82
|
+
|
83
|
+
if ResponseCache.cached_response?(response)
|
66
84
|
log { "returning cached response for #{request.uri}" }
|
67
|
-
cached_response = @options.response_cache_store.lookup(request
|
85
|
+
cached_response = @options.response_cache_store.lookup(request)
|
68
86
|
|
69
87
|
response.copy_from_cached(cached_response)
|
70
|
-
end
|
71
88
|
|
72
|
-
|
89
|
+
else
|
90
|
+
@options.response_cache_store.cache(request, response)
|
91
|
+
end
|
73
92
|
|
74
93
|
response
|
75
94
|
end
|
76
95
|
end
|
77
96
|
|
97
|
+
module RequestMethods
|
98
|
+
def response_cache_key
|
99
|
+
@response_cache_key ||= Digest::SHA1.hexdigest("httpx-response-cache-#{@verb}-#{@uri}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
78
103
|
module ResponseMethods
|
79
104
|
def copy_from_cached(other)
|
80
|
-
|
105
|
+
# 304 responses do not have content-type, which are needed for decoding.
|
106
|
+
@headers = @headers.class.new(other.headers.merge(@headers))
|
107
|
+
|
108
|
+
@body = other.body.dup
|
109
|
+
|
110
|
+
@body.rewind
|
111
|
+
end
|
112
|
+
|
113
|
+
# A response is fresh if its age has not yet exceeded its freshness lifetime.
|
114
|
+
def fresh?
|
115
|
+
if cache_control
|
116
|
+
return false if cache_control.include?("no-cache")
|
117
|
+
|
118
|
+
# check age: max-age
|
119
|
+
max_age = cache_control.find { |directive| directive.start_with?("s-maxage") }
|
120
|
+
|
121
|
+
max_age ||= cache_control.find { |directive| directive.start_with?("max-age") }
|
122
|
+
|
123
|
+
max_age = max_age[/age=(\d+)/, 1] if max_age
|
124
|
+
|
125
|
+
max_age = max_age.to_i if max_age
|
126
|
+
|
127
|
+
return max_age > age if max_age
|
128
|
+
end
|
129
|
+
|
130
|
+
# check age: expires
|
131
|
+
if @headers.key?("expires")
|
132
|
+
begin
|
133
|
+
expires = Time.httpdate(@headers["expires"])
|
134
|
+
rescue ArgumentError
|
135
|
+
return true
|
136
|
+
end
|
137
|
+
|
138
|
+
return (expires - Time.now).to_i.positive?
|
139
|
+
end
|
140
|
+
|
141
|
+
true
|
142
|
+
end
|
143
|
+
|
144
|
+
def cache_control
|
145
|
+
return @cache_control if defined?(@cache_control)
|
146
|
+
|
147
|
+
@cache_control = begin
|
148
|
+
return unless @headers.key?("cache-control")
|
149
|
+
|
150
|
+
@headers["cache-control"].split(/ *, */)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def vary
|
155
|
+
return @vary if defined?(@vary)
|
156
|
+
|
157
|
+
@vary = begin
|
158
|
+
return unless @headers.key?("vary")
|
159
|
+
|
160
|
+
@headers["vary"].split(/ *, */)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def age
|
167
|
+
return @headers["age"].to_i if @headers.key?("age")
|
168
|
+
|
169
|
+
(Time.now - date).to_i
|
170
|
+
end
|
81
171
|
|
82
|
-
|
172
|
+
def date
|
173
|
+
@date ||= Time.httpdate(@headers["date"])
|
174
|
+
rescue NoMethodError, ArgumentError
|
175
|
+
Time.now
|
83
176
|
end
|
84
177
|
end
|
85
178
|
end
|