httpx 1.2.6 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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 +8 -4
- data/lib/httpx/adapters/faraday.rb +2 -1
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/connection/http1.rb +11 -7
- data/lib/httpx/connection/http2.rb +15 -11
- data/lib/httpx/connection.rb +51 -24
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/io/unix.rb +1 -1
- data/lib/httpx/options.rb +4 -7
- data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
- data/lib/httpx/plugins/aws_sigv4.rb +5 -1
- data/lib/httpx/plugins/circuit_breaker.rb +10 -0
- data/lib/httpx/plugins/cookies.rb +9 -6
- data/lib/httpx/plugins/digest_auth.rb +3 -0
- data/lib/httpx/plugins/expect.rb +5 -0
- data/lib/httpx/plugins/follow_redirects.rb +65 -29
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +9 -4
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +24 -13
- data/lib/httpx/plugins/retries.rb +25 -4
- data/lib/httpx/plugins/ssrf_filter.rb +4 -1
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +1 -1
- data/lib/httpx/request/body.rb +37 -41
- data/lib/httpx/request.rb +42 -13
- data/lib/httpx/resolver/https.rb +7 -5
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/resolver/resolver.rb +1 -1
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/response.rb +2 -2
- data/lib/httpx/session.rb +34 -19
- data/lib/httpx/timers.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +2 -2
- data/sig/connection/http1.rbs +2 -2
- data/sig/connection/http2.rbs +17 -17
- data/sig/connection.rbs +10 -4
- data/sig/httpx.rbs +3 -3
- data/sig/io/tcp.rbs +1 -1
- data/sig/io/unix.rbs +1 -1
- data/sig/options.rbs +1 -13
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy.rbs +2 -0
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/pool.rbs +1 -1
- data/sig/request/body.rbs +1 -3
- data/sig/request.rbs +2 -1
- data/sig/resolver/resolver.rbs +2 -2
- data/sig/response.rbs +1 -1
- data/sig/session.rbs +11 -6
- metadata +11 -6
@@ -4,12 +4,17 @@ module HTTPX
|
|
4
4
|
InsecureRedirectError = Class.new(Error)
|
5
5
|
module Plugins
|
6
6
|
#
|
7
|
-
# This plugin adds support for following redirect (status 30X) responses.
|
7
|
+
# This plugin adds support for automatically following redirect (status 30X) responses.
|
8
8
|
#
|
9
|
-
# It has
|
10
|
-
# will return the last redirect response. It will **not** raise an exception.
|
9
|
+
# It has a default upper bound of followed redirects (see *MAX_REDIRECTS* and the *max_redirects* option),
|
10
|
+
# after which it will return the last redirect response. It will **not** raise an exception.
|
11
11
|
#
|
12
|
-
# It
|
12
|
+
# It doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
|
13
|
+
#
|
14
|
+
# It doesn't propagate authorization related headers to requests redirecting to different origins
|
15
|
+
# (see *allow_auth_to_other_origins*) to override.
|
16
|
+
#
|
17
|
+
# It allows customization of when to redirect via the *redirect_on* callback option).
|
13
18
|
#
|
14
19
|
# https://gitlab.com/os85/httpx/wikis/Follow-Redirects
|
15
20
|
#
|
@@ -20,6 +25,14 @@ module HTTPX
|
|
20
25
|
|
21
26
|
using URIExtensions
|
22
27
|
|
28
|
+
# adds support for the following options:
|
29
|
+
#
|
30
|
+
# :max_redirects :: max number of times a request will be redirected (defaults to <tt>3</tt>).
|
31
|
+
# :follow_insecure_redirects :: whether redirects to an "http://" URI, when coming from an "https//", are allowed
|
32
|
+
# (defaults to <tt>false</tt>).
|
33
|
+
# :allow_auth_to_other_origins :: whether auth-related headers, such as "Authorization", are propagated on redirection
|
34
|
+
# (defaults to <tt>false</tt>).
|
35
|
+
# :redirect_on :: optional callback which receives the redirect location and can halt the redirect chain if it returns <tt>false</tt>.
|
23
36
|
module OptionsMethods
|
24
37
|
def option_max_redirects(value)
|
25
38
|
num = Integer(value)
|
@@ -44,6 +57,7 @@ module HTTPX
|
|
44
57
|
end
|
45
58
|
|
46
59
|
module InstanceMethods
|
60
|
+
# returns a session with the *max_redirects* option set to +n+
|
47
61
|
def max_redirects(n)
|
48
62
|
with(max_redirects: n.to_i)
|
49
63
|
end
|
@@ -71,40 +85,40 @@ module HTTPX
|
|
71
85
|
# build redirect request
|
72
86
|
request_body = redirect_request.body
|
73
87
|
redirect_method = "GET"
|
88
|
+
redirect_params = {}
|
74
89
|
|
75
90
|
if response.status == 305 && options.respond_to?(:proxy)
|
76
91
|
request_body.rewind
|
77
92
|
# The requested resource MUST be accessed through the proxy given by
|
78
93
|
# the Location field. The Location field gives the URI of the proxy.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
94
|
+
redirect_options = options.merge(headers: redirect_request.headers,
|
95
|
+
proxy: { uri: redirect_uri },
|
96
|
+
max_redirects: max_redirects - 1)
|
97
|
+
|
98
|
+
redirect_params[:body] = request_body
|
83
99
|
redirect_uri = redirect_request.uri
|
84
|
-
options =
|
100
|
+
options = redirect_options
|
85
101
|
else
|
86
102
|
redirect_headers = redirect_request_headers(redirect_request.uri, redirect_uri, request.headers, options)
|
87
|
-
|
88
|
-
|
103
|
+
redirect_opts = Hash[options]
|
104
|
+
redirect_params[:max_redirects] = max_redirects - 1
|
89
105
|
|
90
106
|
unless request_body.empty?
|
91
107
|
if response.status == 307
|
92
108
|
# The method and the body of the original request are reused to perform the redirected request.
|
93
109
|
redirect_method = redirect_request.verb
|
94
110
|
request_body.rewind
|
95
|
-
|
111
|
+
redirect_params[:body] = request_body
|
96
112
|
else
|
97
113
|
# redirects are **ALWAYS** GET, so remove body-related headers
|
98
114
|
REQUEST_BODY_HEADERS.each do |h|
|
99
115
|
redirect_headers.delete(h)
|
100
116
|
end
|
101
|
-
|
117
|
+
redirect_params[:body] = nil
|
102
118
|
end
|
103
119
|
end
|
104
120
|
|
105
|
-
|
106
|
-
|
107
|
-
retry_options = options.class.new(retry_opts)
|
121
|
+
options = options.class.new(redirect_opts.merge(headers: redirect_headers.to_h))
|
108
122
|
end
|
109
123
|
|
110
124
|
redirect_uri = Utils.to_uri(redirect_uri)
|
@@ -114,27 +128,35 @@ module HTTPX
|
|
114
128
|
redirect_uri.scheme == "http"
|
115
129
|
error = InsecureRedirectError.new(redirect_uri.to_s)
|
116
130
|
error.set_backtrace(caller)
|
117
|
-
return ErrorResponse.new(request, error
|
131
|
+
return ErrorResponse.new(request, error)
|
118
132
|
end
|
119
133
|
|
120
|
-
retry_request = build_request(redirect_method, redirect_uri,
|
134
|
+
retry_request = build_request(redirect_method, redirect_uri, redirect_params, options)
|
121
135
|
|
122
136
|
request.redirect_request = retry_request
|
123
137
|
|
124
|
-
|
138
|
+
redirect_after = response.headers["retry-after"]
|
125
139
|
|
126
|
-
if
|
140
|
+
if redirect_after
|
127
141
|
# Servers send the "Retry-After" header field to indicate how long the
|
128
142
|
# user agent ought to wait before making a follow-up request.
|
129
143
|
# When sent with any 3xx (Redirection) response, Retry-After indicates
|
130
144
|
# the minimum time that the user agent is asked to wait before issuing
|
131
145
|
# the redirected request.
|
132
146
|
#
|
133
|
-
|
147
|
+
redirect_after = Utils.parse_retry_after(redirect_after)
|
134
148
|
|
135
|
-
log { "redirecting after #{
|
136
|
-
|
137
|
-
|
149
|
+
log { "redirecting after #{redirect_after} secs..." }
|
150
|
+
|
151
|
+
deactivate_connection(request, connections, options)
|
152
|
+
|
153
|
+
pool.after(redirect_after) do
|
154
|
+
if request.response
|
155
|
+
# request has terminated abruptly meanwhile
|
156
|
+
retry_request.emit(:response, request.response)
|
157
|
+
else
|
158
|
+
send_request(retry_request, connections, options)
|
159
|
+
end
|
138
160
|
end
|
139
161
|
else
|
140
162
|
send_request(retry_request, connections, options)
|
@@ -142,6 +164,7 @@ module HTTPX
|
|
142
164
|
nil
|
143
165
|
end
|
144
166
|
|
167
|
+
# :nodoc:
|
145
168
|
def redirect_request_headers(original_uri, redirect_uri, headers, options)
|
146
169
|
headers = headers.dup
|
147
170
|
|
@@ -149,14 +172,14 @@ module HTTPX
|
|
149
172
|
|
150
173
|
return headers unless headers.key?("authorization")
|
151
174
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
175
|
+
return headers if original_uri.origin == redirect_uri.origin
|
176
|
+
|
177
|
+
headers.delete("authorization")
|
156
178
|
|
157
179
|
headers
|
158
180
|
end
|
159
181
|
|
182
|
+
# :nodoc:
|
160
183
|
def __get_location_from_response(response)
|
161
184
|
# @type var location_uri: http_uri
|
162
185
|
location_uri = URI(response.headers["location"])
|
@@ -166,12 +189,15 @@ module HTTPX
|
|
166
189
|
end
|
167
190
|
|
168
191
|
module RequestMethods
|
192
|
+
# returns the top-most original HTTPX::Request from the redirect chain
|
169
193
|
attr_accessor :root_request
|
170
194
|
|
195
|
+
# returns the follow-up redirect request, or itself
|
171
196
|
def redirect_request
|
172
197
|
@redirect_request || self
|
173
198
|
end
|
174
199
|
|
200
|
+
# sets the follow-up redirect request
|
175
201
|
def redirect_request=(req)
|
176
202
|
@redirect_request = req
|
177
203
|
req.root_request = @root_request || self
|
@@ -179,7 +205,7 @@ module HTTPX
|
|
179
205
|
end
|
180
206
|
|
181
207
|
def response
|
182
|
-
return super unless @redirect_request
|
208
|
+
return super unless @redirect_request && @response.nil?
|
183
209
|
|
184
210
|
@redirect_request.response
|
185
211
|
end
|
@@ -188,6 +214,16 @@ module HTTPX
|
|
188
214
|
@options.max_redirects || MAX_REDIRECTS
|
189
215
|
end
|
190
216
|
end
|
217
|
+
|
218
|
+
module ConnectionMethods
|
219
|
+
private
|
220
|
+
|
221
|
+
def set_request_request_timeout(request)
|
222
|
+
return unless request.root_request.nil?
|
223
|
+
|
224
|
+
super
|
225
|
+
end
|
226
|
+
end
|
191
227
|
end
|
192
228
|
register_plugin :follow_redirects, FollowRedirects
|
193
229
|
end
|
data/lib/httpx/plugins/grpc.rb
CHANGED
@@ -110,10 +110,10 @@ module HTTPX
|
|
110
110
|
end
|
111
111
|
|
112
112
|
module RequestBodyMethods
|
113
|
-
def initialize(
|
113
|
+
def initialize(*, **)
|
114
114
|
super
|
115
115
|
|
116
|
-
if (compression = headers["grpc-encoding"])
|
116
|
+
if (compression = @headers["grpc-encoding"])
|
117
117
|
deflater_body = self.class.initialize_deflater_body(@body, compression)
|
118
118
|
@body = Transcoder::GRPCEncoding.encode(deflater_body || @body, compressed: !deflater_body.nil?)
|
119
119
|
else
|
data/lib/httpx/plugins/h2c.rb
CHANGED
@@ -39,7 +39,7 @@ module HTTPX
|
|
39
39
|
upgrade_request.headers.add("connection", "upgrade")
|
40
40
|
upgrade_request.headers.add("connection", "http2-settings")
|
41
41
|
upgrade_request.headers["upgrade"] = "h2c"
|
42
|
-
upgrade_request.headers["http2-settings"] =
|
42
|
+
upgrade_request.headers["http2-settings"] = ::HTTP2::Client.settings_header(upgrade_request.options.http2_settings)
|
43
43
|
|
44
44
|
super(upgrade_request, *remainder)
|
45
45
|
end
|
data/lib/httpx/plugins/oauth.rb
CHANGED
@@ -155,7 +155,7 @@ module HTTPX
|
|
155
155
|
with(oauth_session: oauth_session.merge(access_token: access_token, refresh_token: refresh_token))
|
156
156
|
end
|
157
157
|
|
158
|
-
def build_request(
|
158
|
+
def build_request(*)
|
159
159
|
request = super
|
160
160
|
|
161
161
|
return request if request.headers.key?("authorization")
|
@@ -32,9 +32,14 @@ module HTTPX
|
|
32
32
|
!request.headers.key?("proxy-authorization") &&
|
33
33
|
response.headers.key?("proxy-authenticate")
|
34
34
|
|
35
|
-
|
35
|
+
uri = request.uri
|
36
36
|
|
37
|
-
|
37
|
+
proxy_options = proxy_options(uri, options)
|
38
|
+
connection = connections.find do |conn|
|
39
|
+
conn.match?(uri, proxy_options)
|
40
|
+
end
|
41
|
+
|
42
|
+
if connection && connection.options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
|
38
43
|
request.transition(:idle)
|
39
44
|
request.headers["proxy-authorization"] =
|
40
45
|
connection.options.proxy.authenticate(request, response.headers["proxy-authenticate"])
|
@@ -163,8 +168,8 @@ module HTTPX
|
|
163
168
|
end
|
164
169
|
|
165
170
|
class ConnectRequest < Request
|
166
|
-
def initialize(uri,
|
167
|
-
super("CONNECT", uri,
|
171
|
+
def initialize(uri, options)
|
172
|
+
super("CONNECT", uri, options)
|
168
173
|
@headers.delete("accept")
|
169
174
|
end
|
170
175
|
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -89,6 +89,10 @@ module HTTPX
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
+
# adds support for the following options:
|
93
|
+
#
|
94
|
+
# :proxy :: proxy options defining *:uri*, *:username*, *:password* or
|
95
|
+
# *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
|
92
96
|
module OptionsMethods
|
93
97
|
def option_proxy(value)
|
94
98
|
value.is_a?(Parameters) ? value : Hash[value]
|
@@ -107,16 +111,29 @@ module HTTPX
|
|
107
111
|
def find_connection(request, connections, options)
|
108
112
|
return super unless options.respond_to?(:proxy)
|
109
113
|
|
110
|
-
uri =
|
114
|
+
uri = request.uri
|
111
115
|
|
112
|
-
|
116
|
+
proxy_options = proxy_options(uri, options)
|
117
|
+
|
118
|
+
return super(request, connections, proxy_options) unless proxy_options.proxy
|
119
|
+
|
120
|
+
connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
|
121
|
+
unless connections.nil? || connections.include?(connection)
|
122
|
+
connections << connection
|
123
|
+
set_connection_callbacks(connection, connections, options)
|
124
|
+
end
|
125
|
+
connection
|
126
|
+
end
|
127
|
+
|
128
|
+
def proxy_options(request_uri, options)
|
129
|
+
proxy_opts = if (next_proxy = request_uri.find_proxy)
|
113
130
|
{ uri: next_proxy }
|
114
131
|
else
|
115
132
|
proxy = options.proxy
|
116
133
|
|
117
|
-
return
|
134
|
+
return options unless proxy
|
118
135
|
|
119
|
-
return
|
136
|
+
return options.merge(proxy: nil) unless proxy.key?(:uri)
|
120
137
|
|
121
138
|
@_proxy_uris ||= Array(proxy[:uri])
|
122
139
|
|
@@ -133,8 +150,8 @@ module HTTPX
|
|
133
150
|
no_proxy = proxy[:no_proxy]
|
134
151
|
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
135
152
|
|
136
|
-
return
|
137
|
-
|
153
|
+
return options.merge(proxy: nil) unless URI::Generic.use_proxy?(request_uri.host, next_proxy.host,
|
154
|
+
next_proxy.port, no_proxy)
|
138
155
|
end
|
139
156
|
|
140
157
|
proxy.merge(uri: next_proxy)
|
@@ -142,13 +159,7 @@ module HTTPX
|
|
142
159
|
|
143
160
|
proxy = Parameters.new(**proxy_opts)
|
144
161
|
|
145
|
-
|
146
|
-
connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
|
147
|
-
unless connections.nil? || connections.include?(connection)
|
148
|
-
connections << connection
|
149
|
-
set_connection_callbacks(connection, connections, options)
|
150
|
-
end
|
151
|
-
connection
|
162
|
+
options.merge(proxy: proxy)
|
152
163
|
end
|
153
164
|
|
154
165
|
def fetch_response(request, connections, options)
|
@@ -3,7 +3,12 @@
|
|
3
3
|
module HTTPX
|
4
4
|
module Plugins
|
5
5
|
#
|
6
|
-
# This plugin adds support for retrying requests when
|
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).
|
7
12
|
#
|
8
13
|
# https://gitlab.com/os85/httpx/wikis/Retries
|
9
14
|
#
|
@@ -38,6 +43,14 @@ module HTTPX
|
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
46
|
+
# adds support for the following options:
|
47
|
+
#
|
48
|
+
# :max_retries :: max number of times a request will be retried (defaults to <tt>3</tt>).
|
49
|
+
# :retry_change_requests :: whether idempotent requests are retried (defaults to <tt>false</tt>).
|
50
|
+
# :retry_after:: seconds after which a request is retried; can also be a callable object (i.e. <tt>->(req, res) { ... } </tt>)
|
51
|
+
# :retry_jitter :: number of seconds applied to *:retry_after* (must be a callable, i.e. <tt>->(retry_after) { ... } </tt>).
|
52
|
+
# :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
|
53
|
+
# (i.e. <tt>->(res) { ... }</tt>).
|
41
54
|
module OptionsMethods
|
42
55
|
def option_retry_after(value)
|
43
56
|
# return early if callable
|
@@ -76,7 +89,7 @@ module HTTPX
|
|
76
89
|
|
77
90
|
module InstanceMethods
|
78
91
|
def max_retries(n)
|
79
|
-
with(max_retries: n
|
92
|
+
with(max_retries: n)
|
80
93
|
end
|
81
94
|
|
82
95
|
private
|
@@ -111,9 +124,17 @@ module HTTPX
|
|
111
124
|
|
112
125
|
retry_start = Utils.now
|
113
126
|
log { "retrying after #{retry_after} secs..." }
|
127
|
+
|
128
|
+
deactivate_connection(request, connections, options)
|
129
|
+
|
114
130
|
pool.after(retry_after) do
|
115
|
-
|
116
|
-
|
131
|
+
if request.response
|
132
|
+
# request has terminated abruptly meanwhile
|
133
|
+
request.emit(:response, request.response)
|
134
|
+
else
|
135
|
+
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
136
|
+
send_request(request, connections, options)
|
137
|
+
end
|
117
138
|
end
|
118
139
|
else
|
119
140
|
send_request(request, connections, options)
|
@@ -87,6 +87,9 @@ module HTTPX
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
+
# adds support for the following options:
|
91
|
+
#
|
92
|
+
# :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
|
90
93
|
module OptionsMethods
|
91
94
|
def option_allowed_schemes(value)
|
92
95
|
Array(value)
|
@@ -100,7 +103,7 @@ module HTTPX
|
|
100
103
|
|
101
104
|
error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
|
102
105
|
error.set_backtrace(caller)
|
103
|
-
response = ErrorResponse.new(request, error
|
106
|
+
response = ErrorResponse.new(request, error)
|
104
107
|
request.emit(:response, response)
|
105
108
|
response
|
106
109
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thread"
|
4
|
+
|
5
|
+
module HTTPX
|
6
|
+
class SynchPool < Pool
|
7
|
+
def initialize(options)
|
8
|
+
super
|
9
|
+
|
10
|
+
@connections = ConnectionStore.new(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: #wrap
|
14
|
+
def find_or_new_connection(uri, options, &blk)
|
15
|
+
@connections.find_or_new(uri, options) do |new_conn|
|
16
|
+
catch(:coalesced) do
|
17
|
+
init_connection(new_conn, options)
|
18
|
+
blk.call(new_conn) if blk
|
19
|
+
new_conn
|
20
|
+
end
|
21
|
+
end
|
22
|
+
find_connection(uri, options) || new_connection(uri, options, &blk)
|
23
|
+
end
|
24
|
+
|
25
|
+
class ConnectionManager
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
def initialize(limit = 3)
|
29
|
+
@connections = []
|
30
|
+
@used = 0
|
31
|
+
@limit = limit
|
32
|
+
end
|
33
|
+
|
34
|
+
def each(*args, &blk)
|
35
|
+
@connections.each(*args, &blk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_or_new(uri, options, &blk)
|
39
|
+
raise "over limit" if @used >= @limit
|
40
|
+
|
41
|
+
@used += 1
|
42
|
+
conn = @connections.find do |connection|
|
43
|
+
connection.match?(uri, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
if conn
|
47
|
+
@connections.delete(conn)
|
48
|
+
else
|
49
|
+
conn = options.connection_class.new(uri, options)
|
50
|
+
blk[conn]
|
51
|
+
end
|
52
|
+
|
53
|
+
conn
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ConnectionStore
|
58
|
+
include Enumerable
|
59
|
+
|
60
|
+
def initialize(options)
|
61
|
+
@connections = Hash.new { |hs, k| hs[k] ||= ConnectionManager.new }
|
62
|
+
@conn_mtx = Thread::Mutex.new
|
63
|
+
@conn_waiter = ConditionVariable.new
|
64
|
+
@timeout = Float(options.fetch(:pool_timeout, 5))
|
65
|
+
end
|
66
|
+
|
67
|
+
def each(&block)
|
68
|
+
return enum_for(__meth__) unless block
|
69
|
+
|
70
|
+
@conn_mtx.synchronize do
|
71
|
+
@connections.each_value do |conns|
|
72
|
+
conns.each(&block)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_or_new(uri, options, &blk)
|
78
|
+
@connections[uri.origin].find_or_new(uri, options, &blk)
|
79
|
+
end
|
80
|
+
|
81
|
+
# def <<(conn)
|
82
|
+
# @conn_mtx.synchronize do
|
83
|
+
# origin, conns = @connections.find { |_orig, _| conn.origins.include?(origin) }
|
84
|
+
# (conns || @connections[conn.origin.to_s]) << conn
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
|
88
|
+
def empty?
|
89
|
+
@conn_mtx.synchronize { super }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/httpx/pool.rb
CHANGED
@@ -108,7 +108,7 @@ module HTTPX
|
|
108
108
|
resolve_connection(connection) unless connection.family
|
109
109
|
end
|
110
110
|
|
111
|
-
def deactivate(connections)
|
111
|
+
def deactivate(*connections)
|
112
112
|
connections.each do |connection|
|
113
113
|
connection.deactivate
|
114
114
|
deselect_connection(connection) if connection.state == :inactive
|
data/lib/httpx/request/body.rb
CHANGED
@@ -4,30 +4,53 @@ module HTTPX
|
|
4
4
|
# Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
|
5
5
|
class Request::Body < SimpleDelegator
|
6
6
|
class << self
|
7
|
-
def new(_, options)
|
8
|
-
|
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
|
9
13
|
|
10
14
|
super
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
|
-
|
15
|
-
def initialize(headers, options)
|
16
|
-
@headers = headers
|
18
|
+
attr_accessor :options
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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(headers, options, body: nil, form: nil, json: nil, xml: nil, **params)
|
29
|
+
@headers = headers
|
30
|
+
@options = options.merge(params)
|
31
|
+
|
32
|
+
@body = if body
|
33
|
+
Transcoder::Body.encode(body)
|
34
|
+
elsif form
|
35
|
+
Transcoder::Form.encode(form)
|
36
|
+
elsif json
|
37
|
+
Transcoder::JSON.encode(json)
|
38
|
+
elsif xml
|
39
|
+
Transcoder::Xml.encode(xml)
|
23
40
|
end
|
24
41
|
|
25
|
-
|
42
|
+
if @body
|
43
|
+
if @options.compress_request_body && @headers.key?("content-encoding")
|
26
44
|
|
27
|
-
|
45
|
+
@headers.get("content-encoding").each do |encoding|
|
46
|
+
@body = self.class.initialize_deflater_body(@body, encoding)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@headers["content-type"] ||= @body.content_type
|
51
|
+
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
52
|
+
end
|
28
53
|
|
29
|
-
@headers["content-type"] ||= @body.content_type
|
30
|
-
@headers["content-length"] = @body.bytesize unless unbounded_body?
|
31
54
|
super(@body)
|
32
55
|
end
|
33
56
|
|
@@ -99,33 +122,6 @@ module HTTPX
|
|
99
122
|
end
|
100
123
|
# :nocov:
|
101
124
|
|
102
|
-
private
|
103
|
-
|
104
|
-
# wraps the given body with the appropriate encoder.
|
105
|
-
#
|
106
|
-
# ..., json: { foo: "bar" }) #=> json encoder
|
107
|
-
# ..., form: { foo: "bar" }) #=> form urlencoded encoder
|
108
|
-
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
|
109
|
-
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
|
110
|
-
# ..., form: { body: "bla") }) #=> raw data encoder
|
111
|
-
def initialize_body(options)
|
112
|
-
@body = if options.body
|
113
|
-
Transcoder::Body.encode(options.body)
|
114
|
-
elsif options.form
|
115
|
-
Transcoder::Form.encode(options.form)
|
116
|
-
elsif options.json
|
117
|
-
Transcoder::JSON.encode(options.json)
|
118
|
-
elsif options.xml
|
119
|
-
Transcoder::Xml.encode(options.xml)
|
120
|
-
end
|
121
|
-
|
122
|
-
return unless @body && options.compress_request_body && @headers.key?("content-encoding")
|
123
|
-
|
124
|
-
@headers.get("content-encoding").each do |encoding|
|
125
|
-
@body = self.class.initialize_deflater_body(@body, encoding)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
125
|
class << self
|
130
126
|
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
131
127
|
def initialize_deflater_body(body, encoding)
|