httpx 1.2.6 → 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/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)
|