httpx-patched 1.6.2.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 +7 -0
- data/LICENSE.txt +191 -0
- data/README.md +162 -0
- data/doc/release_notes/0_0_1.md +7 -0
- data/doc/release_notes/0_0_2.md +9 -0
- data/doc/release_notes/0_0_3.md +9 -0
- data/doc/release_notes/0_0_4.md +7 -0
- data/doc/release_notes/0_0_5.md +5 -0
- data/doc/release_notes/0_10_0.md +66 -0
- data/doc/release_notes/0_10_1.md +37 -0
- data/doc/release_notes/0_10_2.md +5 -0
- data/doc/release_notes/0_11_0.md +74 -0
- data/doc/release_notes/0_11_1.md +5 -0
- data/doc/release_notes/0_11_2.md +5 -0
- data/doc/release_notes/0_11_3.md +5 -0
- data/doc/release_notes/0_12_0.md +55 -0
- data/doc/release_notes/0_13_0.md +58 -0
- data/doc/release_notes/0_13_1.md +5 -0
- data/doc/release_notes/0_13_2.md +9 -0
- data/doc/release_notes/0_14_0.md +79 -0
- data/doc/release_notes/0_14_1.md +7 -0
- data/doc/release_notes/0_14_2.md +6 -0
- data/doc/release_notes/0_14_3.md +5 -0
- data/doc/release_notes/0_14_4.md +5 -0
- data/doc/release_notes/0_14_5.md +11 -0
- data/doc/release_notes/0_15_0.md +53 -0
- data/doc/release_notes/0_15_1.md +8 -0
- data/doc/release_notes/0_15_2.md +9 -0
- data/doc/release_notes/0_15_3.md +5 -0
- data/doc/release_notes/0_15_4.md +5 -0
- data/doc/release_notes/0_16_0.md +93 -0
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/doc/release_notes/0_18_0.md +69 -0
- data/doc/release_notes/0_18_1.md +12 -0
- data/doc/release_notes/0_18_2.md +10 -0
- data/doc/release_notes/0_18_3.md +7 -0
- data/doc/release_notes/0_18_4.md +14 -0
- data/doc/release_notes/0_18_5.md +10 -0
- data/doc/release_notes/0_18_6.md +5 -0
- data/doc/release_notes/0_18_7.md +5 -0
- data/doc/release_notes/0_19_0.md +39 -0
- data/doc/release_notes/0_19_1.md +5 -0
- data/doc/release_notes/0_19_2.md +7 -0
- data/doc/release_notes/0_19_3.md +6 -0
- data/doc/release_notes/0_19_4.md +14 -0
- data/doc/release_notes/0_19_5.md +13 -0
- data/doc/release_notes/0_19_6.md +5 -0
- data/doc/release_notes/0_19_7.md +5 -0
- data/doc/release_notes/0_19_8.md +5 -0
- data/doc/release_notes/0_1_0.md +9 -0
- data/doc/release_notes/0_20_0.md +36 -0
- 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/0_2_0.md +5 -0
- data/doc/release_notes/0_2_1.md +16 -0
- data/doc/release_notes/0_3_0.md +12 -0
- data/doc/release_notes/0_3_1.md +6 -0
- data/doc/release_notes/0_4_0.md +51 -0
- data/doc/release_notes/0_4_1.md +3 -0
- data/doc/release_notes/0_5_0.md +15 -0
- data/doc/release_notes/0_5_1.md +14 -0
- data/doc/release_notes/0_6_0.md +5 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_6_2.md +6 -0
- data/doc/release_notes/0_6_3.md +13 -0
- data/doc/release_notes/0_6_4.md +21 -0
- data/doc/release_notes/0_6_5.md +22 -0
- data/doc/release_notes/0_6_6.md +19 -0
- data/doc/release_notes/0_6_7.md +5 -0
- data/doc/release_notes/0_7_0.md +46 -0
- data/doc/release_notes/0_8_0.md +27 -0
- data/doc/release_notes/0_8_1.md +8 -0
- data/doc/release_notes/0_8_2.md +7 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/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/doc/release_notes/1_3_2.md +6 -0
- data/doc/release_notes/1_3_3.md +5 -0
- data/doc/release_notes/1_3_4.md +6 -0
- data/doc/release_notes/1_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/doc/release_notes/1_4_2.md +20 -0
- data/doc/release_notes/1_4_3.md +11 -0
- data/doc/release_notes/1_4_4.md +14 -0
- data/doc/release_notes/1_5_0.md +126 -0
- data/doc/release_notes/1_5_1.md +6 -0
- data/doc/release_notes/1_6_0.md +50 -0
- data/doc/release_notes/1_6_1.md +17 -0
- data/doc/release_notes/1_6_2.md +11 -0
- data/lib/httpx/adapters/datadog.rb +359 -0
- data/lib/httpx/adapters/faraday.rb +303 -0
- data/lib/httpx/adapters/sentry.rb +121 -0
- data/lib/httpx/adapters/webmock.rb +175 -0
- data/lib/httpx/altsvc.rb +163 -0
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +61 -0
- data/lib/httpx/callbacks.rb +35 -0
- data/lib/httpx/chainable.rb +106 -0
- data/lib/httpx/connection/http1.rb +399 -0
- data/lib/httpx/connection/http2.rb +468 -0
- data/lib/httpx/connection.rb +954 -0
- data/lib/httpx/domain_name.rb +145 -0
- data/lib/httpx/errors.rb +111 -0
- data/lib/httpx/extensions.rb +59 -0
- data/lib/httpx/headers.rb +176 -0
- data/lib/httpx/io/ssl.rb +163 -0
- data/lib/httpx/io/tcp.rb +239 -0
- data/lib/httpx/io/udp.rb +62 -0
- data/lib/httpx/io/unix.rb +71 -0
- data/lib/httpx/io.rb +11 -0
- data/lib/httpx/loggable.rb +56 -0
- data/lib/httpx/options.rb +463 -0
- data/lib/httpx/parser/http1.rb +186 -0
- data/lib/httpx/plugins/auth/basic.rb +20 -0
- data/lib/httpx/plugins/auth/digest.rb +102 -0
- data/lib/httpx/plugins/auth/ntlm.rb +35 -0
- data/lib/httpx/plugins/auth/socks5.rb +22 -0
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +111 -0
- data/lib/httpx/plugins/aws_sigv4.rb +239 -0
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +127 -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 +147 -0
- data/lib/httpx/plugins/content_digest.rb +204 -0
- data/lib/httpx/plugins/cookies/cookie.rb +174 -0
- data/lib/httpx/plugins/cookies/jar.rb +95 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +143 -0
- data/lib/httpx/plugins/cookies.rb +107 -0
- data/lib/httpx/plugins/digest_auth.rb +67 -0
- data/lib/httpx/plugins/expect.rb +120 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +195 -0
- data/lib/httpx/plugins/follow_redirects.rb +233 -0
- data/lib/httpx/plugins/grpc/call.rb +63 -0
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +90 -0
- data/lib/httpx/plugins/grpc/message.rb +55 -0
- data/lib/httpx/plugins/grpc.rb +282 -0
- data/lib/httpx/plugins/h2c.rb +127 -0
- data/lib/httpx/plugins/internal_telemetry.rb +107 -0
- data/lib/httpx/plugins/ntlm_auth.rb +62 -0
- data/lib/httpx/plugins/oauth.rb +183 -0
- data/lib/httpx/plugins/persistent.rb +82 -0
- data/lib/httpx/plugins/proxy/http.rb +184 -0
- data/lib/httpx/plugins/proxy/socks4.rb +135 -0
- data/lib/httpx/plugins/proxy/socks5.rb +194 -0
- data/lib/httpx/plugins/proxy/ssh.rb +94 -0
- data/lib/httpx/plugins/proxy.rb +349 -0
- data/lib/httpx/plugins/push_promise.rb +81 -0
- data/lib/httpx/plugins/query.rb +35 -0
- data/lib/httpx/plugins/rate_limiter.rb +55 -0
- data/lib/httpx/plugins/response_cache/file_store.rb +140 -0
- data/lib/httpx/plugins/response_cache/store.rb +33 -0
- data/lib/httpx/plugins/response_cache.rb +333 -0
- data/lib/httpx/plugins/retries.rb +230 -0
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +183 -0
- data/lib/httpx/plugins/stream_bidi.rb +315 -0
- data/lib/httpx/plugins/upgrade/h2.rb +64 -0
- data/lib/httpx/plugins/upgrade.rb +86 -0
- data/lib/httpx/plugins/webdav.rb +86 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pmatch_extensions.rb +33 -0
- data/lib/httpx/pool.rb +190 -0
- data/lib/httpx/punycode.rb +22 -0
- data/lib/httpx/request/body.rb +158 -0
- data/lib/httpx/request.rb +328 -0
- data/lib/httpx/resolver/entry.rb +30 -0
- data/lib/httpx/resolver/https.rb +256 -0
- data/lib/httpx/resolver/multi.rb +102 -0
- data/lib/httpx/resolver/native.rb +547 -0
- data/lib/httpx/resolver/resolver.rb +173 -0
- data/lib/httpx/resolver/system.rb +255 -0
- data/lib/httpx/resolver.rb +189 -0
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +115 -0
- data/lib/httpx/response.rb +304 -0
- data/lib/httpx/selector.rb +282 -0
- data/lib/httpx/session.rb +612 -0
- data/lib/httpx/session_extensions.rb +30 -0
- data/lib/httpx/timers.rb +133 -0
- data/lib/httpx/transcoder/body.rb +43 -0
- data/lib/httpx/transcoder/chunker.rb +115 -0
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +68 -0
- data/lib/httpx/transcoder/gzip.rb +71 -0
- data/lib/httpx/transcoder/json.rb +71 -0
- data/lib/httpx/transcoder/multipart/decoder.rb +141 -0
- data/lib/httpx/transcoder/multipart/encoder.rb +120 -0
- data/lib/httpx/transcoder/multipart/mime_type_detector.rb +78 -0
- data/lib/httpx/transcoder/multipart/part.rb +35 -0
- data/lib/httpx/transcoder/multipart.rb +31 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +75 -0
- data/lib/httpx/transcoder.rb +91 -0
- data/lib/httpx/utils.rb +75 -0
- data/lib/httpx/version.rb +5 -0
- data/lib/httpx.rb +66 -0
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +27 -0
- data/sig/callbacks.rbs +15 -0
- data/sig/chainable.rbs +55 -0
- data/sig/connection/http1.rbs +85 -0
- data/sig/connection/http2.rbs +116 -0
- data/sig/connection.rbs +169 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +69 -0
- data/sig/headers.rbs +49 -0
- data/sig/httpx.rbs +27 -0
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +72 -0
- data/sig/io/udp.rbs +25 -0
- data/sig/io/unix.rbs +26 -0
- data/sig/io.rbs +3 -0
- data/sig/loggable.rbs +17 -0
- data/sig/options.rbs +202 -0
- data/sig/parser/http1.rbs +59 -0
- data/sig/plugins/auth/basic.rbs +17 -0
- data/sig/plugins/auth/digest.rbs +25 -0
- data/sig/plugins/auth/ntlm.rbs +20 -0
- data/sig/plugins/auth/socks5.rbs +18 -0
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/aws_sdk_authentication.rbs +43 -0
- data/sig/plugins/aws_sigv4.rbs +78 -0
- data/sig/plugins/basic_auth.rbs +15 -0
- 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 +57 -0
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +55 -0
- data/sig/plugins/cookies/jar.rbs +26 -0
- data/sig/plugins/cookies/set_cookie_parser.rbs +22 -0
- data/sig/plugins/cookies.rbs +28 -0
- data/sig/plugins/digest_auth.rbs +21 -0
- data/sig/plugins/expect.rbs +15 -0
- data/sig/plugins/fiber_concurrency.rbs +51 -0
- data/sig/plugins/follow_redirects.rbs +47 -0
- data/sig/plugins/grpc/call.rbs +23 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +65 -0
- data/sig/plugins/h2c.rbs +27 -0
- data/sig/plugins/ntlm_auth.rbs +21 -0
- data/sig/plugins/oauth.rbs +68 -0
- data/sig/plugins/persistent.rbs +14 -0
- data/sig/plugins/proxy/http.rbs +30 -0
- data/sig/plugins/proxy/socks4.rbs +37 -0
- data/sig/plugins/proxy/socks5.rbs +49 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/proxy.rbs +70 -0
- data/sig/plugins/push_promise.rbs +23 -0
- data/sig/plugins/query.rbs +18 -0
- data/sig/plugins/rate_limiter.rbs +13 -0
- data/sig/plugins/response_cache/file_store.rbs +19 -0
- data/sig/plugins/response_cache/store.rbs +13 -0
- data/sig/plugins/response_cache.rbs +86 -0
- data/sig/plugins/retries.rbs +66 -0
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/stream.rbs +54 -0
- data/sig/plugins/stream_bidi.rbs +68 -0
- data/sig/plugins/upgrade/h2.rbs +9 -0
- data/sig/plugins/upgrade.rbs +29 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +51 -0
- data/sig/punycode.rbs +5 -0
- data/sig/request/body.rbs +34 -0
- data/sig/request.rbs +88 -0
- data/sig/resolver/entry.rbs +13 -0
- data/sig/resolver/https.rbs +45 -0
- data/sig/resolver/multi.rbs +32 -0
- data/sig/resolver/native.rbs +74 -0
- data/sig/resolver/resolver.rbs +64 -0
- data/sig/resolver/system.rbs +34 -0
- data/sig/resolver.rbs +48 -0
- data/sig/response/body.rbs +52 -0
- data/sig/response/buffer.rbs +23 -0
- data/sig/response.rbs +103 -0
- data/sig/selector.rbs +68 -0
- data/sig/session.rbs +104 -0
- data/sig/timers.rbs +54 -0
- data/sig/transcoder/body.rbs +24 -0
- data/sig/transcoder/chunker.rbs +49 -0
- data/sig/transcoder/deflate.rbs +12 -0
- data/sig/transcoder/form.rbs +34 -0
- data/sig/transcoder/gzip.rbs +27 -0
- data/sig/transcoder/json.rbs +28 -0
- data/sig/transcoder/multipart.rbs +103 -0
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +28 -0
- data/sig/transcoder.rbs +43 -0
- data/sig/utils.rbs +19 -0
- metadata +518 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds support for retrying requests when errors happen.
|
|
7
|
+
#
|
|
8
|
+
# It has a default max number of retries (see *MAX_RETRIES* and the *max_retries* option),
|
|
9
|
+
# after which it will return the last response, error or not. It will **not** raise an exception.
|
|
10
|
+
#
|
|
11
|
+
# It does not retry which are not considered idempotent (see *retry_change_requests* to override).
|
|
12
|
+
#
|
|
13
|
+
# https://gitlab.com/os85/httpx/wikis/Retries
|
|
14
|
+
#
|
|
15
|
+
module Retries
|
|
16
|
+
MAX_RETRIES = 3
|
|
17
|
+
# TODO: pass max_retries in a configure/load block
|
|
18
|
+
|
|
19
|
+
IDEMPOTENT_METHODS = %w[GET OPTIONS HEAD PUT DELETE].freeze
|
|
20
|
+
|
|
21
|
+
# subset of retryable errors which are safe to retry when reconnecting
|
|
22
|
+
RECONNECTABLE_ERRORS = [
|
|
23
|
+
IOError,
|
|
24
|
+
EOFError,
|
|
25
|
+
Errno::ECONNRESET,
|
|
26
|
+
Errno::ECONNABORTED,
|
|
27
|
+
Errno::EPIPE,
|
|
28
|
+
Errno::EINVAL,
|
|
29
|
+
Errno::ETIMEDOUT,
|
|
30
|
+
ConnectionError,
|
|
31
|
+
TLSError,
|
|
32
|
+
Connection::HTTP2::Error,
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
RETRYABLE_ERRORS = (RECONNECTABLE_ERRORS + [
|
|
36
|
+
Parser::Error,
|
|
37
|
+
TimeoutError,
|
|
38
|
+
]).freeze
|
|
39
|
+
DEFAULT_JITTER = ->(interval) { interval * ((rand + 1) * 0.5) }
|
|
40
|
+
|
|
41
|
+
if ENV.key?("HTTPX_NO_JITTER")
|
|
42
|
+
def self.extra_options(options)
|
|
43
|
+
options.merge(max_retries: MAX_RETRIES)
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
def self.extra_options(options)
|
|
47
|
+
options.merge(max_retries: MAX_RETRIES, retry_jitter: DEFAULT_JITTER)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# adds support for the following options:
|
|
52
|
+
#
|
|
53
|
+
# :max_retries :: max number of times a request will be retried (defaults to <tt>3</tt>).
|
|
54
|
+
# :retry_change_requests :: whether idempotent requests are retried (defaults to <tt>false</tt>).
|
|
55
|
+
# :retry_after:: seconds after which a request is retried; can also be a callable object (i.e. <tt>->(req, res) { ... } </tt>)
|
|
56
|
+
# :retry_jitter :: number of seconds applied to *:retry_after* (must be a callable, i.e. <tt>->(retry_after) { ... } </tt>).
|
|
57
|
+
# :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
|
|
58
|
+
# (i.e. <tt>->(res) { ... }</tt>).
|
|
59
|
+
module OptionsMethods
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def option_retry_after(value)
|
|
63
|
+
# return early if callable
|
|
64
|
+
unless value.respond_to?(:call)
|
|
65
|
+
value = Float(value)
|
|
66
|
+
raise TypeError, ":retry_after must be positive" unless value.positive?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def option_retry_jitter(value)
|
|
73
|
+
# return early if callable
|
|
74
|
+
raise TypeError, ":retry_jitter must be callable" unless value.respond_to?(:call)
|
|
75
|
+
|
|
76
|
+
value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def option_max_retries(value)
|
|
80
|
+
num = Integer(value)
|
|
81
|
+
raise TypeError, ":max_retries must be positive" unless num >= 0
|
|
82
|
+
|
|
83
|
+
num
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def option_retry_change_requests(v)
|
|
87
|
+
v
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def option_retry_on(value)
|
|
91
|
+
raise TypeError, ":retry_on must be called with the response" unless value.respond_to?(:call)
|
|
92
|
+
|
|
93
|
+
value
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
module InstanceMethods
|
|
98
|
+
# returns a `:retries` plugin enabled session with +n+ maximum retries per request setting.
|
|
99
|
+
def max_retries(n)
|
|
100
|
+
with(max_retries: n)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def fetch_response(request, selector, options)
|
|
106
|
+
response = super
|
|
107
|
+
|
|
108
|
+
if response &&
|
|
109
|
+
request.retries.positive? &&
|
|
110
|
+
repeatable_request?(request, options) &&
|
|
111
|
+
(
|
|
112
|
+
(
|
|
113
|
+
response.is_a?(ErrorResponse) && retryable_error?(response.error)
|
|
114
|
+
) ||
|
|
115
|
+
(
|
|
116
|
+
options.retry_on && options.retry_on.call(response)
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
try_partial_retry(request, response)
|
|
120
|
+
log { "failed to get response, #{request.retries} tries to go..." }
|
|
121
|
+
request.retries -= 1 unless request.ping? # do not exhaust retries on connection liveness probes
|
|
122
|
+
request.transition(:idle)
|
|
123
|
+
|
|
124
|
+
retry_after = options.retry_after
|
|
125
|
+
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
126
|
+
|
|
127
|
+
if retry_after
|
|
128
|
+
# apply jitter
|
|
129
|
+
if (jitter = request.options.retry_jitter)
|
|
130
|
+
retry_after = jitter.call(retry_after)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
retry_start = Utils.now
|
|
134
|
+
log { "retrying after #{retry_after} secs..." }
|
|
135
|
+
selector.after(retry_after) do
|
|
136
|
+
if (response = request.response)
|
|
137
|
+
response.finish!
|
|
138
|
+
# request has terminated abruptly meanwhile
|
|
139
|
+
request.emit(:response, response)
|
|
140
|
+
else
|
|
141
|
+
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
|
142
|
+
send_request(request, selector, options)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
send_request(request, selector, options)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
response
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# returns whether +request+ can be retried.
|
|
155
|
+
def repeatable_request?(request, options)
|
|
156
|
+
IDEMPOTENT_METHODS.include?(request.verb) || options.retry_change_requests
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# returns whether the +ex+ exception happend for a retriable request.
|
|
160
|
+
def retryable_error?(ex)
|
|
161
|
+
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def proxy_error?(request, response, _)
|
|
165
|
+
super && !request.retries.positive?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#
|
|
169
|
+
# Attempt to set the request to perform a partial range request.
|
|
170
|
+
# This happens if the peer server accepts byte-range requests, and
|
|
171
|
+
# the last response contains some body payload.
|
|
172
|
+
#
|
|
173
|
+
def try_partial_retry(request, response)
|
|
174
|
+
response = response.response if response.is_a?(ErrorResponse)
|
|
175
|
+
|
|
176
|
+
return unless response
|
|
177
|
+
|
|
178
|
+
unless response.headers.key?("accept-ranges") &&
|
|
179
|
+
response.headers["accept-ranges"] == "bytes" && # there's nothing else supported though...
|
|
180
|
+
(original_body = response.body)
|
|
181
|
+
response.body.close
|
|
182
|
+
return
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
request.partial_response = response
|
|
186
|
+
|
|
187
|
+
size = original_body.bytesize
|
|
188
|
+
|
|
189
|
+
request.headers["range"] = "bytes=#{size}-"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
module RequestMethods
|
|
194
|
+
# number of retries left.
|
|
195
|
+
attr_accessor :retries
|
|
196
|
+
|
|
197
|
+
# a response partially received before.
|
|
198
|
+
attr_writer :partial_response
|
|
199
|
+
|
|
200
|
+
# initializes the request instance, sets the number of retries for the request.
|
|
201
|
+
def initialize(*args)
|
|
202
|
+
super
|
|
203
|
+
@retries = @options.max_retries
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def response=(response)
|
|
207
|
+
if @partial_response
|
|
208
|
+
if response.is_a?(Response) && response.status == 206
|
|
209
|
+
response.from_partial_response(@partial_response)
|
|
210
|
+
else
|
|
211
|
+
@partial_response.close
|
|
212
|
+
end
|
|
213
|
+
@partial_response = nil
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
super
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
module ResponseMethods
|
|
221
|
+
def from_partial_response(response)
|
|
222
|
+
@status = response.status
|
|
223
|
+
@headers = response.headers
|
|
224
|
+
@body = response.body
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
register_plugin :retries, Retries
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
class ServerSideRequestForgeryError < Error; end
|
|
5
|
+
|
|
6
|
+
module Plugins
|
|
7
|
+
#
|
|
8
|
+
# This plugin adds support for preventing Server-Side Request Forgery attacks.
|
|
9
|
+
#
|
|
10
|
+
# https://gitlab.com/os85/httpx/wikis/Server-Side-Request-Forgery-Filter
|
|
11
|
+
#
|
|
12
|
+
module SsrfFilter
|
|
13
|
+
module IPAddrExtensions
|
|
14
|
+
refine IPAddr do
|
|
15
|
+
def prefixlen
|
|
16
|
+
mask_addr = @mask_addr
|
|
17
|
+
raise "Invalid mask" if mask_addr.zero?
|
|
18
|
+
|
|
19
|
+
mask_addr >>= 1 while (mask_addr & 0x1).zero?
|
|
20
|
+
|
|
21
|
+
length = 0
|
|
22
|
+
while mask_addr & 0x1 == 0x1
|
|
23
|
+
length += 1
|
|
24
|
+
mask_addr >>= 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
length
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
using IPAddrExtensions
|
|
33
|
+
|
|
34
|
+
# https://en.wikipedia.org/wiki/Reserved_IP_addresses
|
|
35
|
+
IPV4_BLACKLIST = [
|
|
36
|
+
IPAddr.new("0.0.0.0/8"), # Current network (only valid as source address)
|
|
37
|
+
IPAddr.new("10.0.0.0/8"), # Private network
|
|
38
|
+
IPAddr.new("100.64.0.0/10"), # Shared Address Space
|
|
39
|
+
IPAddr.new("127.0.0.0/8"), # Loopback
|
|
40
|
+
IPAddr.new("169.254.0.0/16"), # Link-local
|
|
41
|
+
IPAddr.new("172.16.0.0/12"), # Private network
|
|
42
|
+
IPAddr.new("192.0.0.0/24"), # IETF Protocol Assignments
|
|
43
|
+
IPAddr.new("192.0.2.0/24"), # TEST-NET-1, documentation and examples
|
|
44
|
+
IPAddr.new("192.88.99.0/24"), # IPv6 to IPv4 relay (includes 2002::/16)
|
|
45
|
+
IPAddr.new("192.168.0.0/16"), # Private network
|
|
46
|
+
IPAddr.new("198.18.0.0/15"), # Network benchmark tests
|
|
47
|
+
IPAddr.new("198.51.100.0/24"), # TEST-NET-2, documentation and examples
|
|
48
|
+
IPAddr.new("203.0.113.0/24"), # TEST-NET-3, documentation and examples
|
|
49
|
+
IPAddr.new("224.0.0.0/4"), # IP multicast (former Class D network)
|
|
50
|
+
IPAddr.new("240.0.0.0/4"), # Reserved (former Class E network)
|
|
51
|
+
IPAddr.new("255.255.255.255"), # Broadcast
|
|
52
|
+
].freeze
|
|
53
|
+
|
|
54
|
+
IPV6_BLACKLIST = ([
|
|
55
|
+
IPAddr.new("::1/128"), # Loopback
|
|
56
|
+
IPAddr.new("64:ff9b::/96"), # IPv4/IPv6 translation (RFC 6052)
|
|
57
|
+
IPAddr.new("100::/64"), # Discard prefix (RFC 6666)
|
|
58
|
+
IPAddr.new("2001::/32"), # Teredo tunneling
|
|
59
|
+
IPAddr.new("2001:10::/28"), # Deprecated (previously ORCHID)
|
|
60
|
+
IPAddr.new("2001:20::/28"), # ORCHIDv2
|
|
61
|
+
IPAddr.new("2001:db8::/32"), # Addresses used in documentation and example source code
|
|
62
|
+
IPAddr.new("2002::/16"), # 6to4
|
|
63
|
+
IPAddr.new("fc00::/7"), # Unique local address
|
|
64
|
+
IPAddr.new("fe80::/10"), # Link-local address
|
|
65
|
+
IPAddr.new("ff00::/8"), # Multicast
|
|
66
|
+
] + IPV4_BLACKLIST.flat_map do |ipaddr|
|
|
67
|
+
prefixlen = ipaddr.prefixlen
|
|
68
|
+
|
|
69
|
+
ipv4_compatible = ipaddr.ipv4_compat.mask(96 + prefixlen)
|
|
70
|
+
ipv4_mapped = ipaddr.ipv4_mapped.mask(80 + prefixlen)
|
|
71
|
+
|
|
72
|
+
[ipv4_compatible, ipv4_mapped]
|
|
73
|
+
end).freeze
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
def extra_options(options)
|
|
77
|
+
options.merge(allowed_schemes: %w[https http])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def unsafe_ip_address?(ipaddr)
|
|
81
|
+
range = ipaddr.to_range
|
|
82
|
+
return true if range.first != range.last
|
|
83
|
+
|
|
84
|
+
return IPV6_BLACKLIST.any? { |r| r.include?(ipaddr) } if ipaddr.ipv6?
|
|
85
|
+
|
|
86
|
+
IPV4_BLACKLIST.any? { |r| r.include?(ipaddr) } # then it's IPv4
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# adds support for the following options:
|
|
91
|
+
#
|
|
92
|
+
# :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
|
|
93
|
+
module OptionsMethods
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def option_allowed_schemes(value)
|
|
97
|
+
Array(value)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
module InstanceMethods
|
|
102
|
+
def send_requests(*requests)
|
|
103
|
+
responses = requests.map do |request|
|
|
104
|
+
next if @options.allowed_schemes.include?(request.uri.scheme)
|
|
105
|
+
|
|
106
|
+
error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
|
|
107
|
+
error.set_backtrace(caller)
|
|
108
|
+
response = ErrorResponse.new(request, error)
|
|
109
|
+
request.emit(:response, response)
|
|
110
|
+
response
|
|
111
|
+
end
|
|
112
|
+
allowed_requests = requests.select { |req| responses[requests.index(req)].nil? }
|
|
113
|
+
allowed_responses = super(*allowed_requests)
|
|
114
|
+
allowed_responses.each_with_index do |res, idx|
|
|
115
|
+
req = allowed_requests[idx]
|
|
116
|
+
responses[requests.index(req)] = res
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
responses
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
module ConnectionMethods
|
|
124
|
+
def initialize(*)
|
|
125
|
+
begin
|
|
126
|
+
super
|
|
127
|
+
rescue ServerSideRequestForgeryError => e
|
|
128
|
+
# may raise when IPs are passed as options via :addresses
|
|
129
|
+
throw(:resolve_error, e)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def addresses=(addrs)
|
|
134
|
+
addrs.reject!(&SsrfFilter.method(:unsafe_ip_address?))
|
|
135
|
+
|
|
136
|
+
raise ServerSideRequestForgeryError, "#{@origin.host} has no public IP addresses" if addrs.empty?
|
|
137
|
+
|
|
138
|
+
super
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
register_plugin :ssrf_filter, SsrfFilter
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
class StreamResponse
|
|
5
|
+
attr_reader :request
|
|
6
|
+
|
|
7
|
+
def initialize(request, session)
|
|
8
|
+
@request = request
|
|
9
|
+
@options = @request.options
|
|
10
|
+
@session = session
|
|
11
|
+
@response_enum = nil
|
|
12
|
+
@buffered_chunks = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def each(&block)
|
|
16
|
+
return enum_for(__method__) unless block
|
|
17
|
+
|
|
18
|
+
if (response_enum = @response_enum)
|
|
19
|
+
@response_enum = nil
|
|
20
|
+
# streaming already started, let's finish it
|
|
21
|
+
|
|
22
|
+
while (chunk = @buffered_chunks.shift)
|
|
23
|
+
block.call(chunk)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# consume enum til the end
|
|
27
|
+
begin
|
|
28
|
+
while (chunk = response_enum.next)
|
|
29
|
+
block.call(chunk)
|
|
30
|
+
end
|
|
31
|
+
rescue StopIteration
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
@request.stream = self
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
@on_chunk = block
|
|
40
|
+
|
|
41
|
+
response = @session.request(@request)
|
|
42
|
+
|
|
43
|
+
response.raise_for_status
|
|
44
|
+
ensure
|
|
45
|
+
@on_chunk = nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def each_line
|
|
50
|
+
return enum_for(__method__) unless block_given?
|
|
51
|
+
|
|
52
|
+
line = "".b
|
|
53
|
+
|
|
54
|
+
each do |chunk|
|
|
55
|
+
line << chunk
|
|
56
|
+
|
|
57
|
+
while (idx = line.index("\n"))
|
|
58
|
+
yield line.byteslice(0..idx - 1)
|
|
59
|
+
|
|
60
|
+
line = line.byteslice(idx + 1..-1)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
yield line unless line.empty?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# This is a ghost method. It's to be used ONLY internally, when processing streams
|
|
68
|
+
def on_chunk(chunk)
|
|
69
|
+
raise NoMethodError unless @on_chunk
|
|
70
|
+
|
|
71
|
+
@on_chunk.call(chunk)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# :nocov:
|
|
75
|
+
def inspect
|
|
76
|
+
"#<#{self.class}:#{object_id}>"
|
|
77
|
+
end
|
|
78
|
+
# :nocov:
|
|
79
|
+
|
|
80
|
+
def to_s
|
|
81
|
+
if @request.response
|
|
82
|
+
@request.response.to_s
|
|
83
|
+
else
|
|
84
|
+
@buffered_chunks.join
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def response
|
|
91
|
+
@request.response || begin
|
|
92
|
+
response_enum = each
|
|
93
|
+
while (chunk = response_enum.next)
|
|
94
|
+
@buffered_chunks << chunk
|
|
95
|
+
break if @request.response
|
|
96
|
+
end
|
|
97
|
+
@response_enum = response_enum
|
|
98
|
+
@request.response
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def respond_to_missing?(meth, include_private)
|
|
103
|
+
if (response = @request.response)
|
|
104
|
+
response.respond_to_missing?(meth, include_private)
|
|
105
|
+
else
|
|
106
|
+
@options.response_class.method_defined?(meth) || (include_private && @options.response_class.private_method_defined?(meth))
|
|
107
|
+
end || super
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def method_missing(meth, *args, **kwargs, &block)
|
|
111
|
+
return super unless response.respond_to?(meth)
|
|
112
|
+
|
|
113
|
+
response.__send__(meth, *args, **kwargs, &block)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
module Plugins
|
|
118
|
+
#
|
|
119
|
+
# This plugin adds support for streaming a response (useful for i.e. "text/event-stream" payloads).
|
|
120
|
+
#
|
|
121
|
+
# https://gitlab.com/os85/httpx/wikis/Stream
|
|
122
|
+
#
|
|
123
|
+
module Stream
|
|
124
|
+
def self.extra_options(options)
|
|
125
|
+
options.merge(timeout: { read_timeout: Float::INFINITY, operation_timeout: 60 })
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
module InstanceMethods
|
|
129
|
+
def request(*args, stream: false, **options)
|
|
130
|
+
return super(*args, **options) unless stream
|
|
131
|
+
|
|
132
|
+
requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
|
|
133
|
+
raise Error, "only 1 response at a time is supported for streaming requests" unless requests.size == 1
|
|
134
|
+
|
|
135
|
+
request = requests.first
|
|
136
|
+
|
|
137
|
+
StreamResponse.new(request, self)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
module RequestMethods
|
|
142
|
+
attr_accessor :stream
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
module ResponseMethods
|
|
146
|
+
def stream
|
|
147
|
+
request = @request.root_request if @request.respond_to?(:root_request)
|
|
148
|
+
request ||= @request
|
|
149
|
+
|
|
150
|
+
request.stream
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
module ResponseBodyMethods
|
|
155
|
+
def initialize(*)
|
|
156
|
+
super
|
|
157
|
+
@stream = @response.stream
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def write(chunk)
|
|
161
|
+
return super unless @stream
|
|
162
|
+
|
|
163
|
+
return 0 if chunk.empty?
|
|
164
|
+
|
|
165
|
+
chunk = decode_chunk(chunk)
|
|
166
|
+
|
|
167
|
+
@stream.on_chunk(chunk.dup)
|
|
168
|
+
|
|
169
|
+
chunk.size
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def transition(*)
|
|
175
|
+
return if @stream
|
|
176
|
+
|
|
177
|
+
super
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
register_plugin :stream, Stream
|
|
182
|
+
end
|
|
183
|
+
end
|