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
data/lib/httpx/pool.rb
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "httpx/selector"
|
|
4
|
+
require "httpx/connection"
|
|
5
|
+
require "httpx/connection/http2"
|
|
6
|
+
require "httpx/connection/http1"
|
|
7
|
+
require "httpx/resolver"
|
|
8
|
+
|
|
9
|
+
module HTTPX
|
|
10
|
+
class Pool
|
|
11
|
+
using ArrayExtensions::FilterMap
|
|
12
|
+
using URIExtensions
|
|
13
|
+
|
|
14
|
+
POOL_TIMEOUT = 5
|
|
15
|
+
|
|
16
|
+
# Sets up the connection pool with the given +options+, which can be the following:
|
|
17
|
+
#
|
|
18
|
+
# :max_connections:: the maximum number of connections held in the pool.
|
|
19
|
+
# :max_connections_per_origin :: the maximum number of connections held in the pool pointing to a given origin.
|
|
20
|
+
# :pool_timeout :: the number of seconds to wait for a connection to a given origin (before raising HTTPX::PoolTimeoutError)
|
|
21
|
+
#
|
|
22
|
+
def initialize(options)
|
|
23
|
+
@max_connections = options.fetch(:max_connections, Float::INFINITY)
|
|
24
|
+
@max_connections_per_origin = options.fetch(:max_connections_per_origin, Float::INFINITY)
|
|
25
|
+
@pool_timeout = options.fetch(:pool_timeout, POOL_TIMEOUT)
|
|
26
|
+
@resolvers = Hash.new { |hs, resolver_type| hs[resolver_type] = [] }
|
|
27
|
+
@resolver_mtx = Thread::Mutex.new
|
|
28
|
+
@connections = []
|
|
29
|
+
@connection_mtx = Thread::Mutex.new
|
|
30
|
+
@connections_counter = 0
|
|
31
|
+
@max_connections_cond = ConditionVariable.new
|
|
32
|
+
@origin_counters = Hash.new(0)
|
|
33
|
+
@origin_conds = Hash.new { |hs, orig| hs[orig] = ConditionVariable.new }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# connections returned by this function are not expected to return to the connection pool.
|
|
37
|
+
def pop_connection
|
|
38
|
+
@connection_mtx.synchronize do
|
|
39
|
+
drop_connection
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# opens a connection to the IP reachable through +uri+.
|
|
44
|
+
# Many hostnames are reachable through the same IP, so we try to
|
|
45
|
+
# maximize pipelining by opening as few connections as possible.
|
|
46
|
+
#
|
|
47
|
+
def checkout_connection(uri, options)
|
|
48
|
+
return checkout_new_connection(uri, options) if options.io
|
|
49
|
+
|
|
50
|
+
@connection_mtx.synchronize do
|
|
51
|
+
acquire_connection(uri, options) || begin
|
|
52
|
+
if @connections_counter == @max_connections
|
|
53
|
+
# this takes precedence over per-origin
|
|
54
|
+
@max_connections_cond.wait(@connection_mtx, @pool_timeout)
|
|
55
|
+
|
|
56
|
+
if (conn = acquire_connection(uri, options))
|
|
57
|
+
return conn
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if @connections_counter == @max_connections
|
|
61
|
+
# if no matching usable connection was found, the pool will make room and drop a closed connection. if none is found,
|
|
62
|
+
# this means that all of them are persistent or being used, so raise a timeout error.
|
|
63
|
+
conn = @connections.find { |c| c.state == :closed }
|
|
64
|
+
|
|
65
|
+
raise PoolTimeoutError.new(@pool_timeout,
|
|
66
|
+
"Timed out after #{@pool_timeout} seconds while waiting for a connection") unless conn
|
|
67
|
+
|
|
68
|
+
drop_connection(conn)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if @origin_counters[uri.origin] == @max_connections_per_origin
|
|
74
|
+
|
|
75
|
+
@origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
|
|
76
|
+
|
|
77
|
+
return acquire_connection(uri, options) ||
|
|
78
|
+
raise(PoolTimeoutError.new(@pool_timeout,
|
|
79
|
+
"Timed out after #{@pool_timeout} seconds while waiting for a connection to #{uri.origin}"))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
@connections_counter += 1
|
|
83
|
+
@origin_counters[uri.origin] += 1
|
|
84
|
+
|
|
85
|
+
checkout_new_connection(uri, options)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def checkin_connection(connection)
|
|
91
|
+
return if connection.options.io
|
|
92
|
+
|
|
93
|
+
@connection_mtx.synchronize do
|
|
94
|
+
@connections << connection
|
|
95
|
+
|
|
96
|
+
@max_connections_cond.signal
|
|
97
|
+
@origin_conds[connection.origin.to_s].signal
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def checkout_mergeable_connection(connection)
|
|
102
|
+
return if connection.options.io
|
|
103
|
+
|
|
104
|
+
@connection_mtx.synchronize do
|
|
105
|
+
idx = @connections.find_index do |ch|
|
|
106
|
+
ch != connection && ch.mergeable?(connection)
|
|
107
|
+
end
|
|
108
|
+
@connections.delete_at(idx) if idx
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def reset_resolvers
|
|
113
|
+
@resolver_mtx.synchronize { @resolvers.clear }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def checkout_resolver(options)
|
|
117
|
+
resolver_type = options.resolver_class
|
|
118
|
+
resolver_type = Resolver.resolver_for(resolver_type, options)
|
|
119
|
+
|
|
120
|
+
@resolver_mtx.synchronize do
|
|
121
|
+
resolvers = @resolvers[resolver_type]
|
|
122
|
+
|
|
123
|
+
idx = resolvers.find_index do |res|
|
|
124
|
+
res.options == options
|
|
125
|
+
end
|
|
126
|
+
resolvers.delete_at(idx) if idx
|
|
127
|
+
end || checkout_new_resolver(resolver_type, options)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def checkin_resolver(resolver)
|
|
131
|
+
@resolver_mtx.synchronize do
|
|
132
|
+
resolvers = @resolvers[resolver.class]
|
|
133
|
+
|
|
134
|
+
resolver = resolver.multi
|
|
135
|
+
|
|
136
|
+
resolvers << resolver unless resolvers.include?(resolver)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# :nocov:
|
|
141
|
+
def inspect
|
|
142
|
+
"#<#{self.class}:#{object_id} " \
|
|
143
|
+
"@max_connections_per_origin=#{@max_connections_per_origin} " \
|
|
144
|
+
"@pool_timeout=#{@pool_timeout} " \
|
|
145
|
+
"@connections=#{@connections.size}>"
|
|
146
|
+
end
|
|
147
|
+
# :nocov:
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def acquire_connection(uri, options)
|
|
152
|
+
idx = @connections.find_index do |connection|
|
|
153
|
+
connection.match?(uri, options)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
return unless idx
|
|
157
|
+
|
|
158
|
+
@connections.delete_at(idx)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def checkout_new_connection(uri, options)
|
|
162
|
+
options.connection_class.new(uri, options)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def checkout_new_resolver(resolver_type, options)
|
|
166
|
+
if resolver_type.multi?
|
|
167
|
+
Resolver::Multi.new(resolver_type, options)
|
|
168
|
+
else
|
|
169
|
+
resolver_type.new(options)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# drops and returns the +connection+ from the connection pool; if +connection+ is <tt>nil</tt> (default),
|
|
174
|
+
# the first available connection from the pool will be dropped.
|
|
175
|
+
def drop_connection(connection = nil)
|
|
176
|
+
if connection
|
|
177
|
+
@connections.delete(connection)
|
|
178
|
+
else
|
|
179
|
+
connection = @connections.shift
|
|
180
|
+
|
|
181
|
+
return unless connection
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
@connections_counter -= 1
|
|
185
|
+
@origin_conds.delete(connection.origin) if (@origin_counters[connection.origin.to_s] -= 1).zero?
|
|
186
|
+
|
|
187
|
+
connection
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Punycode
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "idnx"
|
|
9
|
+
|
|
10
|
+
def encode_hostname(hostname)
|
|
11
|
+
Idnx.to_punycode(hostname)
|
|
12
|
+
end
|
|
13
|
+
rescue LoadError
|
|
14
|
+
def encode_hostname(hostname)
|
|
15
|
+
warn "#{hostname} cannot be converted to punycode. Install the " \
|
|
16
|
+
"\"idnx\" gem: https://github.com/HoneyryderChuck/idnx"
|
|
17
|
+
|
|
18
|
+
hostname
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
# Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
|
|
5
|
+
class Request::Body < SimpleDelegator
|
|
6
|
+
class << self
|
|
7
|
+
def new(_, options, body: nil, **params)
|
|
8
|
+
if body.is_a?(self)
|
|
9
|
+
# request derives its options from body
|
|
10
|
+
body.options = options.merge(params)
|
|
11
|
+
return body
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_accessor :options
|
|
19
|
+
|
|
20
|
+
# inits the instance with the request +headers+, +options+ and +params+, which contain the payload definition.
|
|
21
|
+
# it wraps the given body with the appropriate encoder on initialization.
|
|
22
|
+
#
|
|
23
|
+
# ..., json: { foo: "bar" }) #=> json encoder
|
|
24
|
+
# ..., form: { foo: "bar" }) #=> form urlencoded encoder
|
|
25
|
+
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
|
|
26
|
+
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
|
|
27
|
+
# ..., form: { body: "bla") }) #=> raw data encoder
|
|
28
|
+
def initialize(h, options, **params)
|
|
29
|
+
@headers = h
|
|
30
|
+
@body = self.class.initialize_body(params)
|
|
31
|
+
@options = options.merge(params)
|
|
32
|
+
|
|
33
|
+
if @body
|
|
34
|
+
if @options.compress_request_body && @headers.key?("content-encoding")
|
|
35
|
+
|
|
36
|
+
@headers.get("content-encoding").each do |encoding|
|
|
37
|
+
@body = self.class.initialize_deflater_body(@body, encoding)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@headers["content-type"] ||= @body.content_type
|
|
42
|
+
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
super(@body)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# consumes and yields the request payload in chunks.
|
|
49
|
+
def each(&block)
|
|
50
|
+
return enum_for(__method__) unless block
|
|
51
|
+
return if @body.nil?
|
|
52
|
+
|
|
53
|
+
body = stream(@body)
|
|
54
|
+
if body.respond_to?(:read)
|
|
55
|
+
while (chunk = body.read(16_384))
|
|
56
|
+
block.call(chunk)
|
|
57
|
+
end
|
|
58
|
+
# TODO: use copy_stream once bug is resolved: https://bugs.ruby-lang.org/issues/21131
|
|
59
|
+
# IO.copy_stream(body, ProcIO.new(block))
|
|
60
|
+
elsif body.respond_to?(:each)
|
|
61
|
+
body.each(&block)
|
|
62
|
+
else
|
|
63
|
+
block[body.to_s]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def close
|
|
68
|
+
@body.close if @body.respond_to?(:close)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# if the +@body+ is rewindable, it rewinnds it.
|
|
72
|
+
def rewind
|
|
73
|
+
return if empty?
|
|
74
|
+
|
|
75
|
+
@body.rewind if @body.respond_to?(:rewind)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# return +true+ if the +body+ has been fully drained (or does nnot exist).
|
|
79
|
+
def empty?
|
|
80
|
+
return true if @body.nil?
|
|
81
|
+
return false if chunked?
|
|
82
|
+
|
|
83
|
+
@body.bytesize.zero?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# returns the +@body+ payload size in bytes.
|
|
87
|
+
def bytesize
|
|
88
|
+
return 0 if @body.nil?
|
|
89
|
+
|
|
90
|
+
@body.bytesize
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# sets the body to yield using chunked trannsfer encoding format.
|
|
94
|
+
def stream(body)
|
|
95
|
+
return body unless chunked?
|
|
96
|
+
|
|
97
|
+
Transcoder::Chunker.encode(body.enum_for(:each))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# returns whether the body yields infinitely.
|
|
101
|
+
def unbounded_body?
|
|
102
|
+
return @unbounded_body if defined?(@unbounded_body)
|
|
103
|
+
|
|
104
|
+
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# returns whether the chunked transfer encoding header is set.
|
|
108
|
+
def chunked?
|
|
109
|
+
@headers["transfer-encoding"] == "chunked"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# sets the chunked transfer encoding header.
|
|
113
|
+
def chunk!
|
|
114
|
+
@headers.add("transfer-encoding", "chunked")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# :nocov:
|
|
118
|
+
def inspect
|
|
119
|
+
"#<#{self.class}:#{object_id} " \
|
|
120
|
+
"#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
|
|
121
|
+
end
|
|
122
|
+
# :nocov:
|
|
123
|
+
|
|
124
|
+
class << self
|
|
125
|
+
def initialize_body(params)
|
|
126
|
+
if (body = params.delete(:body))
|
|
127
|
+
# @type var body: bodyIO
|
|
128
|
+
Transcoder::Body.encode(body)
|
|
129
|
+
elsif (form = params.delete(:form))
|
|
130
|
+
if Transcoder::Multipart.multipart?(form)
|
|
131
|
+
# @type var form: Transcoder::multipart_input
|
|
132
|
+
Transcoder::Multipart.encode(form)
|
|
133
|
+
else
|
|
134
|
+
# @type var form: Transcoder::urlencoded_input
|
|
135
|
+
Transcoder::Form.encode(form)
|
|
136
|
+
end
|
|
137
|
+
elsif (json = params.delete(:json))
|
|
138
|
+
# @type var body: _ToJson
|
|
139
|
+
Transcoder::JSON.encode(json)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
|
144
|
+
def initialize_deflater_body(body, encoding)
|
|
145
|
+
case encoding
|
|
146
|
+
when "gzip"
|
|
147
|
+
Transcoder::GZIP.encode(body)
|
|
148
|
+
when "deflate"
|
|
149
|
+
Transcoder::Deflate.encode(body)
|
|
150
|
+
when "identity"
|
|
151
|
+
body
|
|
152
|
+
else
|
|
153
|
+
body
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
require "forwardable"
|
|
5
|
+
|
|
6
|
+
module HTTPX
|
|
7
|
+
# Defines how an HTTP request is handled internally, both in terms of making attributes accessible,
|
|
8
|
+
# as well as maintaining the state machine which manages streaming the request onto the wire.
|
|
9
|
+
class Request
|
|
10
|
+
extend Forwardable
|
|
11
|
+
include Loggable
|
|
12
|
+
include Callbacks
|
|
13
|
+
using URIExtensions
|
|
14
|
+
|
|
15
|
+
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
|
16
|
+
|
|
17
|
+
# default value used for "user-agent" header, when not overridden.
|
|
18
|
+
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
19
|
+
|
|
20
|
+
# the upcased string HTTP verb for this request.
|
|
21
|
+
attr_reader :verb
|
|
22
|
+
|
|
23
|
+
# the absolute URI object for this request.
|
|
24
|
+
attr_reader :uri
|
|
25
|
+
|
|
26
|
+
# an HTTPX::Headers object containing the request HTTP headers.
|
|
27
|
+
attr_reader :headers
|
|
28
|
+
|
|
29
|
+
# an HTTPX::Request::Body object containing the request body payload (or +nil+, whenn there is none).
|
|
30
|
+
attr_reader :body
|
|
31
|
+
|
|
32
|
+
# a symbol describing which frame is currently being flushed.
|
|
33
|
+
attr_reader :state
|
|
34
|
+
|
|
35
|
+
# an HTTPX::Options object containing request options.
|
|
36
|
+
attr_reader :options
|
|
37
|
+
|
|
38
|
+
# the corresponding HTTPX::Response object, when there is one.
|
|
39
|
+
attr_reader :response
|
|
40
|
+
|
|
41
|
+
# Exception raised during enumerable body writes.
|
|
42
|
+
attr_reader :drain_error
|
|
43
|
+
|
|
44
|
+
# The IP address from the peer server.
|
|
45
|
+
attr_accessor :peer_address
|
|
46
|
+
|
|
47
|
+
attr_writer :persistent
|
|
48
|
+
|
|
49
|
+
attr_reader :active_timeouts
|
|
50
|
+
|
|
51
|
+
# will be +true+ when request body has been completely flushed.
|
|
52
|
+
def_delegator :@body, :empty?
|
|
53
|
+
|
|
54
|
+
# closes the body
|
|
55
|
+
def_delegator :@body, :close
|
|
56
|
+
|
|
57
|
+
# initializes the instance with the given +verb+ (an upppercase String, ex. 'GEt'),
|
|
58
|
+
# an absolute or relative +uri+ (either as String or URI::HTTP object), the
|
|
59
|
+
# request +options+ (instance of HTTPX::Options) and an optional Hash of +params+.
|
|
60
|
+
#
|
|
61
|
+
# Besides any of the options documented in HTTPX::Options (which would override or merge with what
|
|
62
|
+
# +options+ sets), it accepts also the following:
|
|
63
|
+
#
|
|
64
|
+
# :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
|
|
65
|
+
# :body :: to be encoded in the request body payload. can be a String, an IO object (i.e. a File), or an Enumerable.
|
|
66
|
+
# :form :: hash of array of key-values which will be form-urlencoded- or multipart-encoded in requests body payload.
|
|
67
|
+
# :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
|
|
68
|
+
# :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
|
|
69
|
+
#
|
|
70
|
+
# :body, :form, :json and :xml are all mutually exclusive, i.e. only one of them gets picked up.
|
|
71
|
+
def initialize(verb, uri, options, params = EMPTY_HASH)
|
|
72
|
+
@verb = verb.to_s.upcase
|
|
73
|
+
@uri = Utils.to_uri(uri)
|
|
74
|
+
|
|
75
|
+
@headers = options.headers.dup
|
|
76
|
+
merge_headers(params.delete(:headers)) if params.key?(:headers)
|
|
77
|
+
|
|
78
|
+
@headers["user-agent"] ||= USER_AGENT
|
|
79
|
+
@headers["accept"] ||= "*/*"
|
|
80
|
+
|
|
81
|
+
# forego compression in the Range request case
|
|
82
|
+
if @headers.key?("range")
|
|
83
|
+
@headers.delete("accept-encoding")
|
|
84
|
+
else
|
|
85
|
+
@headers["accept-encoding"] ||= options.supported_compression_formats
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@query_params = params.delete(:params) if params.key?(:params)
|
|
89
|
+
|
|
90
|
+
@body = options.request_body_class.new(@headers, options, **params)
|
|
91
|
+
|
|
92
|
+
@options = @body.options
|
|
93
|
+
|
|
94
|
+
if @uri.relative? || @uri.host.nil?
|
|
95
|
+
origin = @options.origin
|
|
96
|
+
raise(Error, "invalid URI: #{@uri}") unless origin
|
|
97
|
+
|
|
98
|
+
base_path = @options.base_path
|
|
99
|
+
|
|
100
|
+
@uri = origin.merge("#{base_path}#{@uri}")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
|
104
|
+
|
|
105
|
+
@state = :idle
|
|
106
|
+
@response = @peer_address = @context = @informational_status = nil
|
|
107
|
+
@ping = false
|
|
108
|
+
@persistent = @options.persistent
|
|
109
|
+
@active_timeouts = []
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def complete!(response = @response)
|
|
113
|
+
emit(:complete, response)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# whether request has been buffered with a ping
|
|
117
|
+
def ping?
|
|
118
|
+
@ping
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# marks the request as having been buffered with a ping
|
|
122
|
+
def ping!
|
|
123
|
+
@ping = true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# the read timeout defined for this request.
|
|
127
|
+
def read_timeout
|
|
128
|
+
@options.timeout[:read_timeout]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# the write timeout defined for this request.
|
|
132
|
+
def write_timeout
|
|
133
|
+
@options.timeout[:write_timeout]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# the request timeout defined for this request.
|
|
137
|
+
def request_timeout
|
|
138
|
+
@options.timeout[:request_timeout]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def persistent?
|
|
142
|
+
@persistent
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# if the request contains trailer headers
|
|
146
|
+
def trailers?
|
|
147
|
+
defined?(@trailers)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# returns an instance of HTTPX::Headers containing the trailer headers
|
|
151
|
+
def trailers
|
|
152
|
+
@trailers ||= @options.headers_class.new
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# returns +:r+ or +:w+, depending on whether the request is waiting for a response or flushing.
|
|
156
|
+
def interests
|
|
157
|
+
return :r if @state == :done || @state == :expect
|
|
158
|
+
|
|
159
|
+
:w
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def can_buffer?
|
|
163
|
+
@state != :done
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# merges +h+ into the instance of HTTPX::Headers of the request.
|
|
167
|
+
def merge_headers(h)
|
|
168
|
+
@headers = @headers.merge(h)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# the URI scheme of the request +uri+.
|
|
172
|
+
def scheme
|
|
173
|
+
@uri.scheme
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# sets the +response+ on this request.
|
|
177
|
+
def response=(response)
|
|
178
|
+
return unless response
|
|
179
|
+
|
|
180
|
+
case response
|
|
181
|
+
when Response
|
|
182
|
+
if response.status < 200
|
|
183
|
+
# deal with informational responses
|
|
184
|
+
|
|
185
|
+
if response.status == 100 && @headers.key?("expect")
|
|
186
|
+
@informational_status = response.status
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# 103 Early Hints advertises resources in document to browsers.
|
|
191
|
+
# not very relevant for an HTTP client, discard.
|
|
192
|
+
return if response.status >= 103
|
|
193
|
+
|
|
194
|
+
end
|
|
195
|
+
when ErrorResponse
|
|
196
|
+
response.error.connection = nil if response.error.respond_to?(:connection=)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
@response = response
|
|
200
|
+
|
|
201
|
+
emit(:response_started, response)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# returnns the URI path of the request +uri+.
|
|
205
|
+
def path
|
|
206
|
+
path = uri.path.dup
|
|
207
|
+
path = +"" if path.nil?
|
|
208
|
+
path << "/" if path.empty?
|
|
209
|
+
path << "?#{query}" unless query.empty?
|
|
210
|
+
path
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# returs the URI authority of the request.
|
|
214
|
+
#
|
|
215
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "google.com"
|
|
216
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
|
|
217
|
+
def authority
|
|
218
|
+
@uri.authority
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# returs the URI origin of the request.
|
|
222
|
+
#
|
|
223
|
+
# session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
|
|
224
|
+
# session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
|
|
225
|
+
def origin
|
|
226
|
+
@uri.origin
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# returs the URI query string of the request (when available).
|
|
230
|
+
#
|
|
231
|
+
# session.build_request("GET", "https://search.com").query #=> ""
|
|
232
|
+
# session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
|
|
233
|
+
# session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
|
|
234
|
+
# session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
|
|
235
|
+
def query
|
|
236
|
+
return @query if defined?(@query)
|
|
237
|
+
|
|
238
|
+
query = []
|
|
239
|
+
if (q = @query_params) && !q.empty?
|
|
240
|
+
query << Transcoder::Form.encode(q)
|
|
241
|
+
end
|
|
242
|
+
query << @uri.query if @uri.query
|
|
243
|
+
@query = query.join("&")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# consumes and returns the next available chunk of request body that can be sent
|
|
247
|
+
def drain_body
|
|
248
|
+
return nil if @body.nil?
|
|
249
|
+
|
|
250
|
+
@drainer ||= @body.each
|
|
251
|
+
chunk = @drainer.next.dup
|
|
252
|
+
|
|
253
|
+
emit(:body_chunk, chunk)
|
|
254
|
+
chunk
|
|
255
|
+
rescue StopIteration
|
|
256
|
+
nil
|
|
257
|
+
rescue StandardError => e
|
|
258
|
+
@drain_error = e
|
|
259
|
+
nil
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# :nocov:
|
|
263
|
+
def inspect
|
|
264
|
+
"#<#{self.class}:#{object_id} " \
|
|
265
|
+
"#{@verb} " \
|
|
266
|
+
"#{uri} " \
|
|
267
|
+
"@headers=#{@headers} " \
|
|
268
|
+
"@body=#{@body}>"
|
|
269
|
+
end
|
|
270
|
+
# :nocov:
|
|
271
|
+
|
|
272
|
+
# moves on to the +nextstate+ of the request state machine (when all preconditions are met)
|
|
273
|
+
def transition(nextstate)
|
|
274
|
+
case nextstate
|
|
275
|
+
when :idle
|
|
276
|
+
@body.rewind
|
|
277
|
+
@ping = false
|
|
278
|
+
@response = nil
|
|
279
|
+
@drainer = nil
|
|
280
|
+
@active_timeouts.clear
|
|
281
|
+
when :headers
|
|
282
|
+
return unless @state == :idle
|
|
283
|
+
|
|
284
|
+
when :body
|
|
285
|
+
return unless @state == :headers ||
|
|
286
|
+
@state == :expect
|
|
287
|
+
|
|
288
|
+
if @headers.key?("expect")
|
|
289
|
+
if @informational_status && @informational_status == 100
|
|
290
|
+
# check for 100 Continue response, and deallocate the var
|
|
291
|
+
# if @informational_status == 100
|
|
292
|
+
# @response = nil
|
|
293
|
+
# end
|
|
294
|
+
else
|
|
295
|
+
return if @state == :expect # do not re-set it
|
|
296
|
+
|
|
297
|
+
nextstate = :expect
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
when :trailers
|
|
301
|
+
return unless @state == :body
|
|
302
|
+
when :done
|
|
303
|
+
return if @state == :expect
|
|
304
|
+
|
|
305
|
+
end
|
|
306
|
+
log(level: 3) { "#{@state}] -> #{nextstate}" }
|
|
307
|
+
@state = nextstate
|
|
308
|
+
emit(@state, self)
|
|
309
|
+
nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# whether the request supports the 100-continue handshake and already processed the 100 response.
|
|
313
|
+
def expects?
|
|
314
|
+
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def set_timeout_callback(event, &callback)
|
|
318
|
+
clb = once(event, &callback)
|
|
319
|
+
|
|
320
|
+
# reset timeout callbacks when requests get rerouted to a different connection
|
|
321
|
+
once(:idle) do
|
|
322
|
+
callbacks(event).delete(clb)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
require_relative "request/body"
|