httpx 0.20.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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/session.rb
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
+
EMPTY_HASH = {}.freeze
|
5
|
+
|
6
|
+
# Class implementing the APIs being used publicly.
|
7
|
+
#
|
8
|
+
# HTTPX.get(..) #=> delegating to an internal HTTPX::Session object.
|
9
|
+
# HTTPX.plugin(..).get(..) #=> creating an intermediate HTTPX::Session with plugin, then sending the GET request
|
4
10
|
class Session
|
5
11
|
include Loggable
|
6
12
|
include Chainable
|
7
13
|
|
8
|
-
|
9
|
-
|
14
|
+
# initializes the session with a set of +options+, which will be shared by all
|
15
|
+
# requests sent from it.
|
16
|
+
#
|
17
|
+
# When pass a block, it'll yield itself to it, then closes after the block is evaluated.
|
10
18
|
def initialize(options = EMPTY_HASH, &blk)
|
11
19
|
@options = self.class.default_options.merge(options)
|
12
20
|
@responses = {}
|
@@ -14,63 +22,102 @@ module HTTPX
|
|
14
22
|
wrap(&blk) if blk
|
15
23
|
end
|
16
24
|
|
25
|
+
# Yields itself the block, then closes it after the block is evaluated.
|
26
|
+
#
|
27
|
+
# session.wrap do |http|
|
28
|
+
# http.get("https://wikipedia.com")
|
29
|
+
# end # wikipedia connection closes here
|
17
30
|
def wrap
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
31
|
+
prev_persistent = @persistent
|
32
|
+
@persistent = true
|
33
|
+
pool.wrap do
|
34
|
+
begin
|
35
|
+
yield self
|
36
|
+
ensure
|
37
|
+
@persistent = prev_persistent
|
38
|
+
close unless @persistent
|
39
|
+
end
|
25
40
|
end
|
26
41
|
end
|
27
42
|
|
43
|
+
# closes all the active connections from the session
|
28
44
|
def close(*args)
|
29
45
|
pool.close(*args)
|
30
46
|
end
|
31
47
|
|
32
|
-
|
48
|
+
# performs one, or multple requests; it accepts:
|
49
|
+
#
|
50
|
+
# 1. one or multiple HTTPX::Request objects;
|
51
|
+
# 2. an HTTP verb, then a sequence of URIs or URI/options tuples;
|
52
|
+
# 3. one or multiple HTTP verb / uri / (optional) options tuples;
|
53
|
+
#
|
54
|
+
# when present, the set of +options+ kwargs is applied to all of the
|
55
|
+
# sent requests.
|
56
|
+
#
|
57
|
+
# respectively returns a single HTTPX::Response response, or all of them in an Array, in the same order.
|
58
|
+
#
|
59
|
+
# resp1 = session.request(req1)
|
60
|
+
# resp1, resp2 = session.request(req1, req2)
|
61
|
+
# resp1 = session.request("GET", "https://server.org/a")
|
62
|
+
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"])
|
63
|
+
# resp1, resp2 = session.request(["GET", "https://server.org/a"], ["GET", "https://server.org/b"])
|
64
|
+
# resp1 = session.request("POST", "https://server.org/a", form: { "foo" => "bar" })
|
65
|
+
# resp1, resp2 = session.request(["POST", "https://server.org/a", form: { "foo" => "bar" }], ["GET", "https://server.org/b"])
|
66
|
+
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"], headers: { "x-api-token" => "TOKEN" })
|
67
|
+
#
|
68
|
+
def request(*args, **params)
|
33
69
|
raise ArgumentError, "must perform at least one request" if args.empty?
|
34
70
|
|
35
|
-
requests = args.first.is_a?(Request) ? args : build_requests(*args,
|
71
|
+
requests = args.first.is_a?(Request) ? args : build_requests(*args, params)
|
36
72
|
responses = send_requests(*requests)
|
37
73
|
return responses.first if responses.size == 1
|
38
74
|
|
39
75
|
responses
|
40
76
|
end
|
41
77
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
78
|
+
# returns a HTTP::Request instance built from the HTTP +verb+, the request +uri+, and
|
79
|
+
# the optional set of request-specific +options+. This request **must** be sent through
|
80
|
+
# the same session it was built from.
|
81
|
+
#
|
82
|
+
# req = session.build_request("GET", "https://server.com")
|
83
|
+
# resp = session.request(req)
|
84
|
+
def build_request(verb, uri, params = EMPTY_HASH, options = @options)
|
85
|
+
rklass = options.request_class
|
86
|
+
request = rklass.new(verb, uri, options, params)
|
87
|
+
request.persistent = @persistent
|
88
|
+
set_request_callbacks(request)
|
48
89
|
request
|
49
90
|
end
|
50
91
|
|
51
92
|
private
|
52
93
|
|
94
|
+
# returns the HTTPX::Pool object which manages the networking required to
|
95
|
+
# perform requests.
|
53
96
|
def pool
|
54
97
|
Thread.current[:httpx_connection_pool] ||= Pool.new
|
55
98
|
end
|
56
99
|
|
100
|
+
# callback executed when a response for a given request has been received.
|
57
101
|
def on_response(request, response)
|
58
102
|
@responses[request] = response
|
59
103
|
end
|
60
104
|
|
105
|
+
# callback executed when an HTTP/2 promise frame has been received.
|
61
106
|
def on_promise(_, stream)
|
62
107
|
log(level: 2) { "#{stream.id}: refusing stream!" }
|
63
108
|
stream.refuse
|
64
109
|
end
|
65
110
|
|
111
|
+
# returns the corresponding HTTP::Response to the given +request+ if it has been received.
|
66
112
|
def fetch_response(request, _, _)
|
67
113
|
@responses.delete(request)
|
68
114
|
end
|
69
115
|
|
116
|
+
# returns the HTTPX::Connection through which the +request+ should be sent through.
|
70
117
|
def find_connection(request, connections, options)
|
71
118
|
uri = request.uri
|
72
119
|
|
73
|
-
connection = pool.find_connection(uri, options) ||
|
120
|
+
connection = pool.find_connection(uri, options) || init_connection(uri, options)
|
74
121
|
unless connections.nil? || connections.include?(connection)
|
75
122
|
connections << connection
|
76
123
|
set_connection_callbacks(connection, connections, options)
|
@@ -78,7 +125,20 @@ module HTTPX
|
|
78
125
|
connection
|
79
126
|
end
|
80
127
|
|
81
|
-
|
128
|
+
# sends the +request+ to the corresponding HTTPX::Connection
|
129
|
+
def send_request(request, connections, options = request.options)
|
130
|
+
error = catch(:resolve_error) do
|
131
|
+
connection = find_connection(request, connections, options)
|
132
|
+
connection.send(request)
|
133
|
+
end
|
134
|
+
return unless error.is_a?(Error)
|
135
|
+
|
136
|
+
request.emit(:response, ErrorResponse.new(request, error))
|
137
|
+
end
|
138
|
+
|
139
|
+
# sets the callbacks on the +connection+ required to process certain specific
|
140
|
+
# connection lifecycle events which deal with request rerouting.
|
141
|
+
def set_connection_callbacks(connection, connections, options, cloned: false)
|
82
142
|
connection.only(:misdirected) do |misdirected_request|
|
83
143
|
other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
|
84
144
|
other_connection.merge(connection)
|
@@ -94,17 +154,13 @@ module HTTPX
|
|
94
154
|
other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
|
95
155
|
connections << other_connection if other_connection
|
96
156
|
end
|
97
|
-
connection.only(:
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
pool.init_connection(other_connection, options)
|
102
|
-
end
|
103
|
-
set_connection_callbacks(other_connection, connections, options)
|
104
|
-
connections << other_connection
|
105
|
-
end
|
157
|
+
connection.only(:cloned) do |cloned_conn|
|
158
|
+
set_connection_callbacks(cloned_conn, connections, options, cloned: true)
|
159
|
+
connections << cloned_conn
|
160
|
+
end unless cloned
|
106
161
|
end
|
107
162
|
|
163
|
+
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
|
108
164
|
def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
|
109
165
|
# do not allow security downgrades on altsvc negotiation
|
110
166
|
return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
|
@@ -116,46 +172,46 @@ module HTTPX
|
|
116
172
|
|
117
173
|
alt_options = options.merge(ssl: options.ssl.merge(hostname: URI(origin).host))
|
118
174
|
|
119
|
-
connection = pool.find_connection(alt_origin, alt_options) ||
|
175
|
+
connection = pool.find_connection(alt_origin, alt_options) || init_connection(alt_origin, alt_options)
|
176
|
+
|
120
177
|
# advertised altsvc is the same origin being used, ignore
|
121
178
|
return if connection == existing_connection
|
122
179
|
|
180
|
+
connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
|
181
|
+
|
123
182
|
set_connection_callbacks(connection, connections, alt_options)
|
124
183
|
|
125
184
|
log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
|
126
185
|
|
127
|
-
# get uninitialized requests
|
128
|
-
# incidentally, all requests will be re-routed to the first
|
129
|
-
# advertised alt-svc, which incidentally follows the spec.
|
130
|
-
existing_connection.purge_pending do |request|
|
131
|
-
request.origin == origin &&
|
132
|
-
request.state == :idle &&
|
133
|
-
!request.headers.key?("alt-used")
|
134
|
-
end
|
135
|
-
|
136
186
|
connection.merge(existing_connection)
|
187
|
+
existing_connection.terminate
|
137
188
|
connection
|
138
189
|
rescue UnsupportedSchemeError
|
139
190
|
altsvc["noop"] = true
|
140
191
|
nil
|
141
192
|
end
|
142
193
|
|
143
|
-
|
144
|
-
|
145
|
-
|
194
|
+
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
|
195
|
+
def build_requests(*args, params)
|
146
196
|
requests = if args.size == 1
|
147
197
|
reqs = args.first
|
148
|
-
|
149
|
-
|
198
|
+
# TODO: find a way to make requests share same options object
|
199
|
+
reqs.map do |verb, uri, ps = EMPTY_HASH|
|
200
|
+
request_params = params
|
201
|
+
request_params = request_params.merge(ps) unless ps.empty?
|
202
|
+
build_request(verb, uri, request_params)
|
150
203
|
end
|
151
204
|
else
|
152
205
|
verb, uris = args
|
153
206
|
if uris.respond_to?(:each)
|
154
|
-
|
155
|
-
|
207
|
+
# TODO: find a way to make requests share same options object
|
208
|
+
uris.enum_for(:each).map do |uri, ps = EMPTY_HASH|
|
209
|
+
request_params = params
|
210
|
+
request_params = request_params.merge(ps) unless ps.empty?
|
211
|
+
build_request(verb, uri, request_params)
|
156
212
|
end
|
157
213
|
else
|
158
|
-
[build_request(verb, uris,
|
214
|
+
[build_request(verb, uris, params)]
|
159
215
|
end
|
160
216
|
end
|
161
217
|
raise ArgumentError, "wrong number of URIs (given 0, expect 1..+1)" if requests.empty?
|
@@ -163,46 +219,47 @@ module HTTPX
|
|
163
219
|
requests
|
164
220
|
end
|
165
221
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
else
|
174
|
-
raise UnsupportedSchemeError, "#{uri}: #{uri.scheme}: unsupported URI scheme"
|
175
|
-
end
|
176
|
-
end
|
177
|
-
connection = options.connection_class.new(type, uri, options)
|
222
|
+
def set_request_callbacks(request)
|
223
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
224
|
+
request.on(:promise, &method(:on_promise))
|
225
|
+
end
|
226
|
+
|
227
|
+
def init_connection(uri, options)
|
228
|
+
connection = options.connection_class.new(uri, options)
|
178
229
|
catch(:coalesced) do
|
179
230
|
pool.init_connection(connection, options)
|
180
231
|
connection
|
181
232
|
end
|
182
233
|
end
|
183
234
|
|
235
|
+
def deactivate_connection(request, connections, options)
|
236
|
+
conn = connections.find do |c|
|
237
|
+
c.match?(request.uri, options)
|
238
|
+
end
|
239
|
+
|
240
|
+
pool.deactivate(conn) if conn
|
241
|
+
end
|
242
|
+
|
243
|
+
# sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
|
184
244
|
def send_requests(*requests)
|
185
245
|
connections = _send_requests(requests)
|
186
246
|
receive_requests(requests, connections)
|
187
247
|
end
|
188
248
|
|
249
|
+
# sends an array of HTTPX::Request objects
|
189
250
|
def _send_requests(requests)
|
190
251
|
connections = []
|
191
252
|
|
192
253
|
requests.each do |request|
|
193
|
-
|
194
|
-
connection = find_connection(request, connections, request.options)
|
195
|
-
connection.send(request)
|
196
|
-
end
|
197
|
-
next unless error.is_a?(ResolveError)
|
198
|
-
|
199
|
-
request.emit(:response, ErrorResponse.new(request, error, request.options))
|
254
|
+
send_request(request, connections)
|
200
255
|
end
|
201
256
|
|
202
257
|
connections
|
203
258
|
end
|
204
259
|
|
260
|
+
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
|
205
261
|
def receive_requests(requests, connections)
|
262
|
+
# @type var responses: Array[response]
|
206
263
|
responses = []
|
207
264
|
|
208
265
|
begin
|
@@ -213,6 +270,7 @@ module HTTPX
|
|
213
270
|
return responses unless request
|
214
271
|
|
215
272
|
catch(:coalesced) { pool.next_tick } until (response = fetch_response(request, connections, request.options))
|
273
|
+
request.emit(:complete, response)
|
216
274
|
|
217
275
|
responses << response
|
218
276
|
requests.shift
|
@@ -226,14 +284,16 @@ module HTTPX
|
|
226
284
|
# opportunity to traverse the requests, hence we're returning only a fraction of the errors
|
227
285
|
# we were supposed to. This effectively fetches the existing responses and return them.
|
228
286
|
while (request = requests.shift)
|
229
|
-
|
287
|
+
response = fetch_response(request, connections, request.options)
|
288
|
+
request.emit(:complete, response) if response
|
289
|
+
responses << response
|
230
290
|
end
|
231
291
|
break
|
232
292
|
end
|
233
293
|
responses
|
234
294
|
ensure
|
235
295
|
if @persistent
|
236
|
-
pool.deactivate(connections)
|
296
|
+
pool.deactivate(*connections)
|
237
297
|
else
|
238
298
|
close(connections)
|
239
299
|
end
|
@@ -251,8 +311,14 @@ module HTTPX
|
|
251
311
|
super
|
252
312
|
klass.instance_variable_set(:@default_options, @default_options)
|
253
313
|
klass.instance_variable_set(:@plugins, @plugins.dup)
|
314
|
+
klass.instance_variable_set(:@callbacks, @callbacks.dup)
|
254
315
|
end
|
255
316
|
|
317
|
+
# returns a new HTTPX::Session instance, with the plugin pointed by +pl+ loaded.
|
318
|
+
#
|
319
|
+
# session_with_retries = session.plugin(:retries)
|
320
|
+
# session_with_custom = session.plugin(CustomPlugin)
|
321
|
+
#
|
256
322
|
def plugin(pl, options = nil, &block)
|
257
323
|
# raise Error, "Cannot add a plugin to a frozen config" if frozen?
|
258
324
|
pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
|
@@ -266,19 +332,8 @@ module HTTPX
|
|
266
332
|
extend(pl::ClassMethods) if defined?(pl::ClassMethods)
|
267
333
|
|
268
334
|
opts = @default_options
|
269
|
-
opts.
|
270
|
-
opts.request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods)
|
271
|
-
opts.response_class.__send__(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods)
|
272
|
-
opts.response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods)
|
273
|
-
opts.headers_class.__send__(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods)
|
274
|
-
opts.headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods)
|
275
|
-
opts.request_body_class.__send__(:include, pl::RequestBodyMethods) if defined?(pl::RequestBodyMethods)
|
276
|
-
opts.request_body_class.extend(pl::RequestBodyClassMethods) if defined?(pl::RequestBodyClassMethods)
|
277
|
-
opts.response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
|
278
|
-
opts.response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
|
279
|
-
opts.connection_class.__send__(:include, pl::ConnectionMethods) if defined?(pl::ConnectionMethods)
|
335
|
+
opts.extend_with_plugin_classes(pl)
|
280
336
|
if defined?(pl::OptionsMethods)
|
281
|
-
opts.options_class.__send__(:include, pl::OptionsMethods)
|
282
337
|
|
283
338
|
(pl::OptionsMethods.instance_methods - Object.instance_methods).each do |meth|
|
284
339
|
opts.options_class.method_added(meth)
|
@@ -302,16 +357,9 @@ module HTTPX
|
|
302
357
|
end
|
303
358
|
self
|
304
359
|
end
|
305
|
-
|
306
|
-
# :nocov:
|
307
|
-
def plugins(pls)
|
308
|
-
warn ":#{__method__} is deprecated, use :plugin instead"
|
309
|
-
pls.each do |pl|
|
310
|
-
plugin(pl)
|
311
|
-
end
|
312
|
-
self
|
313
|
-
end
|
314
|
-
# :nocov:
|
315
360
|
end
|
316
361
|
end
|
362
|
+
|
363
|
+
# session may be overridden by certain adapters.
|
364
|
+
S = Session
|
317
365
|
end
|
@@ -9,10 +9,13 @@ module HTTPX
|
|
9
9
|
# redefine the default options static var, which needs to
|
10
10
|
# refresh options_class
|
11
11
|
options = proxy_session.class.default_options.to_hash
|
12
|
-
options.freeze
|
13
12
|
original_verbosity = $VERBOSE
|
14
13
|
$VERBOSE = nil
|
14
|
+
const_set(:Options, proxy_session.class.default_options.options_class)
|
15
|
+
options[:options_class] = Class.new(options[:options_class])
|
16
|
+
options.freeze
|
15
17
|
Options.send(:const_set, :DEFAULT_OPTIONS, options)
|
18
|
+
Session.instance_variable_set(:@default_options, Options.new(options))
|
16
19
|
$VERBOSE = original_verbosity
|
17
20
|
end
|
18
21
|
|
data/lib/httpx/timers.rb
CHANGED
@@ -6,20 +6,27 @@ module HTTPX
|
|
6
6
|
@intervals = []
|
7
7
|
end
|
8
8
|
|
9
|
-
def after(interval_in_secs, &blk)
|
9
|
+
def after(interval_in_secs, cb = nil, &blk)
|
10
10
|
return unless interval_in_secs
|
11
11
|
|
12
|
+
callback = cb || blk
|
13
|
+
|
12
14
|
# I'm assuming here that most requests will have the same
|
13
15
|
# request timeout, as in most cases they share common set of
|
14
16
|
# options. A user setting different request timeouts for 100s of
|
15
17
|
# requests will already have a hard time dealing with that.
|
16
|
-
unless (interval = @intervals.find { |t| t == interval_in_secs })
|
18
|
+
unless (interval = @intervals.find { |t| t.interval == interval_in_secs })
|
17
19
|
interval = Interval.new(interval_in_secs)
|
20
|
+
interval.on_empty { @intervals.delete(interval) }
|
18
21
|
@intervals << interval
|
19
22
|
@intervals.sort!
|
20
23
|
end
|
21
24
|
|
22
|
-
interval <<
|
25
|
+
interval << callback
|
26
|
+
|
27
|
+
@next_interval_at = nil
|
28
|
+
|
29
|
+
interval
|
23
30
|
end
|
24
31
|
|
25
32
|
def wait_interval
|
@@ -36,11 +43,9 @@ module HTTPX
|
|
36
43
|
|
37
44
|
elapsed_time = Utils.elapsed_time(@next_interval_at)
|
38
45
|
|
39
|
-
@intervals.
|
40
|
-
end
|
46
|
+
@intervals = @intervals.drop_while { |interval| interval.elapse(elapsed_time) <= 0 }
|
41
47
|
|
42
|
-
|
43
|
-
@intervals.clear
|
48
|
+
@next_interval_at = nil if @intervals.empty?
|
44
49
|
end
|
45
50
|
|
46
51
|
class Interval
|
@@ -51,6 +56,11 @@ module HTTPX
|
|
51
56
|
def initialize(interval)
|
52
57
|
@interval = interval
|
53
58
|
@callbacks = []
|
59
|
+
@on_empty = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_empty(&blk)
|
63
|
+
@on_empty = blk
|
54
64
|
end
|
55
65
|
|
56
66
|
def <=>(other)
|
@@ -71,10 +81,26 @@ module HTTPX
|
|
71
81
|
@callbacks << callback
|
72
82
|
end
|
73
83
|
|
84
|
+
def delete(callback)
|
85
|
+
@callbacks.delete(callback)
|
86
|
+
@on_empty.call if @callbacks.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
def no_callbacks?
|
90
|
+
@callbacks.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def elapsed?
|
94
|
+
@interval <= 0
|
95
|
+
end
|
96
|
+
|
74
97
|
def elapse(elapsed)
|
75
98
|
@interval -= elapsed
|
76
99
|
|
77
|
-
|
100
|
+
if @interval <= 0
|
101
|
+
cb = @callbacks.dup
|
102
|
+
cb.each(&:call)
|
103
|
+
end
|
78
104
|
|
79
105
|
@interval
|
80
106
|
end
|
@@ -9,7 +9,6 @@ module HTTPX::Transcoder
|
|
9
9
|
module_function
|
10
10
|
|
11
11
|
class Encoder
|
12
|
-
using HTTPX::ArrayExtensions
|
13
12
|
extend Forwardable
|
14
13
|
|
15
14
|
def_delegator :@raw, :to_s
|
@@ -55,5 +54,4 @@ module HTTPX::Transcoder
|
|
55
54
|
Encoder.new(body)
|
56
55
|
end
|
57
56
|
end
|
58
|
-
register "body", Body
|
59
57
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
require_relative "utils/deflater"
|
5
|
+
|
6
|
+
module HTTPX
|
7
|
+
module Transcoder
|
8
|
+
module Deflate
|
9
|
+
class Deflater < Transcoder::Deflater
|
10
|
+
def deflate(chunk)
|
11
|
+
@deflater ||= Zlib::Deflate.new
|
12
|
+
|
13
|
+
if chunk.nil?
|
14
|
+
unless @deflater.closed?
|
15
|
+
last = @deflater.finish
|
16
|
+
@deflater.close
|
17
|
+
last.empty? ? nil : last
|
18
|
+
end
|
19
|
+
else
|
20
|
+
@deflater.deflate(chunk)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module_function
|
26
|
+
|
27
|
+
def encode(body)
|
28
|
+
Deflater.new(body)
|
29
|
+
end
|
30
|
+
|
31
|
+
def decode(response, bytesize: nil)
|
32
|
+
bytesize ||= response.headers.key?("content-length") ? response.headers["content-length"].to_i : Float::INFINITY
|
33
|
+
GZIP::Inflater.new(bytesize)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -2,58 +2,77 @@
|
|
2
2
|
|
3
3
|
require "forwardable"
|
4
4
|
require "uri"
|
5
|
+
require_relative "multipart"
|
5
6
|
|
6
|
-
module HTTPX
|
7
|
-
module
|
8
|
-
|
7
|
+
module HTTPX
|
8
|
+
module Transcoder
|
9
|
+
module Form
|
10
|
+
module_function
|
9
11
|
|
10
|
-
|
12
|
+
PARAM_DEPTH_LIMIT = 32
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
class Encoder
|
15
|
+
extend Forwardable
|
14
16
|
|
15
|
-
|
17
|
+
def_delegator :@raw, :to_s
|
16
18
|
|
17
|
-
|
19
|
+
def_delegator :@raw, :to_str
|
18
20
|
|
19
|
-
|
21
|
+
def_delegator :@raw, :bytesize
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def initialize(form)
|
24
|
+
@raw = form.each_with_object("".b) do |(key, val), buf|
|
25
|
+
HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
|
26
|
+
buf << "&" unless buf.empty?
|
27
|
+
buf << URI.encode_www_form_component(k)
|
28
|
+
buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
|
29
|
+
end
|
27
30
|
end
|
28
31
|
end
|
29
|
-
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
def content_type
|
34
|
+
"application/x-www-form-urlencoded"
|
35
|
+
end
|
33
36
|
end
|
34
|
-
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
+
module Decoder
|
39
|
+
module_function
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
41
|
+
def call(response, *)
|
42
|
+
URI.decode_www_form(response.to_s).each_with_object({}) do |(field, value), params|
|
43
|
+
HTTPX::Transcoder.normalize_query(params, field, value, PARAM_DEPTH_LIMIT)
|
44
|
+
end
|
42
45
|
end
|
43
46
|
end
|
44
|
-
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
def encode(form)
|
49
|
+
if multipart?(form)
|
50
|
+
Multipart::Encoder.new(form)
|
51
|
+
else
|
52
|
+
Encoder.new(form)
|
53
|
+
end
|
54
|
+
end
|
49
55
|
|
50
|
-
|
51
|
-
|
56
|
+
def decode(response)
|
57
|
+
content_type = response.content_type.mime_type
|
52
58
|
|
53
|
-
|
59
|
+
case content_type
|
60
|
+
when "application/x-www-form-urlencoded"
|
61
|
+
Decoder
|
62
|
+
when "multipart/form-data"
|
63
|
+
Multipart::Decoder.new(response)
|
64
|
+
else
|
65
|
+
raise Error, "invalid form mime type (#{content_type})"
|
66
|
+
end
|
67
|
+
end
|
54
68
|
|
55
|
-
|
69
|
+
def multipart?(data)
|
70
|
+
data.any? do |_, v|
|
71
|
+
Multipart::MULTIPART_VALUE_COND.call(v) ||
|
72
|
+
(v.respond_to?(:to_ary) && v.to_ary.any?(&Multipart::MULTIPART_VALUE_COND)) ||
|
73
|
+
(v.respond_to?(:to_hash) && v.to_hash.any? { |_, e| Multipart::MULTIPART_VALUE_COND.call(e) })
|
74
|
+
end
|
75
|
+
end
|
56
76
|
end
|
57
77
|
end
|
58
|
-
register "form", Form
|
59
78
|
end
|