httpx 1.2.6 → 1.4.4
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/README.md +1 -2
- 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/lib/httpx/adapters/datadog.rb +56 -80
- data/lib/httpx/adapters/faraday.rb +5 -2
- data/lib/httpx/adapters/webmock.rb +24 -8
- data/lib/httpx/callbacks.rb +2 -7
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http1.rb +11 -7
- data/lib/httpx/connection/http2.rb +57 -34
- data/lib/httpx/connection.rb +270 -71
- data/lib/httpx/errors.rb +15 -4
- data/lib/httpx/io/ssl.rb +6 -3
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/io/unix.rb +1 -1
- data/lib/httpx/loggable.rb +17 -10
- data/lib/httpx/options.rb +30 -23
- data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
- data/lib/httpx/plugins/aws_sigv4.rb +36 -17
- data/lib/httpx/plugins/callbacks.rb +13 -2
- data/lib/httpx/plugins/circuit_breaker.rb +11 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/cookies.rb +9 -6
- data/lib/httpx/plugins/digest_auth.rb +3 -0
- data/lib/httpx/plugins/expect.rb +10 -4
- data/lib/httpx/plugins/follow_redirects.rb +68 -33
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +48 -1
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +19 -16
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +96 -85
- data/lib/httpx/plugins/retries.rb +28 -10
- data/lib/httpx/plugins/ssrf_filter.rb +4 -1
- data/lib/httpx/plugins/stream.rb +42 -18
- data/lib/httpx/plugins/upgrade.rb +5 -10
- data/lib/httpx/plugins/webdav.rb +6 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pool.rb +73 -244
- data/lib/httpx/request/body.rb +50 -55
- data/lib/httpx/request.rb +77 -14
- data/lib/httpx/resolver/https.rb +17 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +140 -61
- data/lib/httpx/resolver/resolver.rb +64 -19
- data/lib/httpx/resolver/system.rb +32 -16
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response/body.rb +12 -1
- data/lib/httpx/response.rb +16 -9
- data/lib/httpx/selector.rb +170 -91
- data/lib/httpx/session.rb +282 -139
- data/lib/httpx/timers.rb +17 -2
- data/lib/httpx/transcoder/body.rb +15 -29
- data/lib/httpx/transcoder/form.rb +2 -0
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +16 -2
- data/lib/httpx/transcoder/multipart/encoder.rb +11 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +20 -21
- data/sig/callbacks.rbs +2 -3
- data/sig/chainable.rbs +6 -2
- data/sig/connection/http1.rbs +2 -2
- data/sig/connection/http2.rbs +22 -18
- data/sig/connection.rbs +40 -9
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +3 -3
- data/sig/io/tcp.rbs +1 -1
- data/sig/io/unix.rbs +1 -1
- data/sig/loggable.rbs +4 -2
- data/sig/options.rbs +8 -13
- data/sig/plugins/aws_sigv4.rbs +8 -2
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +9 -0
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -9
- data/sig/plugins/push_promise.rbs +6 -3
- data/sig/plugins/rate_limiter.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/stream.rbs +3 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +27 -33
- data/sig/request/body.rbs +4 -10
- data/sig/request.rbs +14 -1
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +6 -3
- data/sig/resolver/resolver.rbs +22 -3
- data/sig/resolver.rbs +5 -1
- data/sig/response/body.rbs +2 -2
- data/sig/response/buffer.rbs +2 -2
- data/sig/response.rbs +9 -4
- data/sig/selector.rbs +31 -4
- data/sig/session.rbs +54 -20
- data/sig/timers.rbs +15 -4
- data/sig/transcoder/body.rbs +2 -4
- data/sig/transcoder/chunker.rbs +1 -1
- data/sig/transcoder/deflate.rbs +1 -0
- data/sig/transcoder/form.rbs +8 -0
- data/sig/transcoder/gzip.rbs +4 -1
- data/sig/transcoder/json.rbs +1 -1
- data/sig/transcoder/multipart.rbs +6 -4
- data/sig/transcoder/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +2 -3
- metadata +32 -14
- data/lib/httpx/session2.rb +0 -23
- data/lib/httpx/transcoder/utils/inflater.rb +0 -19
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/utils/inflater.rbs +0 -12
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/session.rb
CHANGED
@@ -9,8 +9,6 @@ module HTTPX
|
|
9
9
|
include Loggable
|
10
10
|
include Chainable
|
11
11
|
|
12
|
-
EMPTY_HASH = {}.freeze
|
13
|
-
|
14
12
|
# initializes the session with a set of +options+, which will be shared by all
|
15
13
|
# requests sent from it.
|
16
14
|
#
|
@@ -19,6 +17,9 @@ module HTTPX
|
|
19
17
|
@options = self.class.default_options.merge(options)
|
20
18
|
@responses = {}
|
21
19
|
@persistent = @options.persistent
|
20
|
+
@pool = @options.pool_class.new(@options.pool_options)
|
21
|
+
@wrapped = false
|
22
|
+
@closing = false
|
22
23
|
wrap(&blk) if blk
|
23
24
|
end
|
24
25
|
|
@@ -28,21 +29,54 @@ module HTTPX
|
|
28
29
|
# http.get("https://wikipedia.com")
|
29
30
|
# end # wikipedia connection closes here
|
30
31
|
def wrap
|
31
|
-
|
32
|
-
@
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
prev_wrapped = @wrapped
|
33
|
+
@wrapped = true
|
34
|
+
was_initialized = false
|
35
|
+
current_selector = get_current_selector do
|
36
|
+
selector = Selector.new
|
37
|
+
|
38
|
+
set_current_selector(selector)
|
39
|
+
|
40
|
+
was_initialized = true
|
41
|
+
|
42
|
+
selector
|
43
|
+
end
|
44
|
+
begin
|
45
|
+
yield self
|
46
|
+
ensure
|
47
|
+
unless prev_wrapped
|
48
|
+
if @persistent
|
49
|
+
deactivate(current_selector)
|
50
|
+
else
|
51
|
+
close(current_selector)
|
52
|
+
end
|
39
53
|
end
|
54
|
+
@wrapped = prev_wrapped
|
55
|
+
set_current_selector(nil) if was_initialized
|
40
56
|
end
|
41
57
|
end
|
42
58
|
|
43
|
-
# closes all the active connections from the session
|
44
|
-
|
45
|
-
|
59
|
+
# closes all the active connections from the session.
|
60
|
+
#
|
61
|
+
# when called directly without specifying +selector+, all available connections
|
62
|
+
# will be picked up from the connection pool and closed. Connections in use
|
63
|
+
# by other sessions, or same session in a different thread, will not be reaped.
|
64
|
+
def close(selector = Selector.new)
|
65
|
+
# throw resolvers away from the pool
|
66
|
+
@pool.reset_resolvers
|
67
|
+
|
68
|
+
# preparing to throw away connections
|
69
|
+
while (connection = @pool.pop_connection)
|
70
|
+
next if connection.state == :closed
|
71
|
+
|
72
|
+
select_connection(connection, selector)
|
73
|
+
end
|
74
|
+
begin
|
75
|
+
@closing = true
|
76
|
+
selector.terminate
|
77
|
+
ensure
|
78
|
+
@closing = false
|
79
|
+
end
|
46
80
|
end
|
47
81
|
|
48
82
|
# performs one, or multple requests; it accepts:
|
@@ -65,10 +99,10 @@ module HTTPX
|
|
65
99
|
# resp1, resp2 = session.request(["POST", "https://server.org/a", form: { "foo" => "bar" }], ["GET", "https://server.org/b"])
|
66
100
|
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"], headers: { "x-api-token" => "TOKEN" })
|
67
101
|
#
|
68
|
-
def request(*args, **
|
102
|
+
def request(*args, **params)
|
69
103
|
raise ArgumentError, "must perform at least one request" if args.empty?
|
70
104
|
|
71
|
-
requests = args.first.is_a?(Request) ? args : build_requests(*args,
|
105
|
+
requests = args.first.is_a?(Request) ? args : build_requests(*args, params)
|
72
106
|
responses = send_requests(*requests)
|
73
107
|
return responses.first if responses.size == 1
|
74
108
|
|
@@ -81,133 +115,150 @@ module HTTPX
|
|
81
115
|
#
|
82
116
|
# req = session.build_request("GET", "https://server.com")
|
83
117
|
# resp = session.request(req)
|
84
|
-
def build_request(verb, uri, options =
|
85
|
-
rklass =
|
86
|
-
|
87
|
-
request = rklass.new(verb, uri, options)
|
118
|
+
def build_request(verb, uri, params = EMPTY_HASH, options = @options)
|
119
|
+
rklass = options.request_class
|
120
|
+
request = rklass.new(verb, uri, options, params)
|
88
121
|
request.persistent = @persistent
|
89
122
|
set_request_callbacks(request)
|
90
123
|
request
|
91
124
|
end
|
92
125
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# perform requests.
|
97
|
-
def pool
|
98
|
-
Thread.current[:httpx_connection_pool] ||= Pool.new
|
126
|
+
def select_connection(connection, selector)
|
127
|
+
pin_connection(connection, selector)
|
128
|
+
selector.register(connection)
|
99
129
|
end
|
100
130
|
|
101
|
-
|
102
|
-
|
103
|
-
|
131
|
+
def pin_connection(connection, selector)
|
132
|
+
connection.current_session = self
|
133
|
+
connection.current_selector = selector
|
104
134
|
end
|
105
135
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
136
|
+
alias_method :select_resolver, :select_connection
|
137
|
+
|
138
|
+
def deselect_connection(connection, selector, cloned = false)
|
139
|
+
selector.deregister(connection)
|
140
|
+
|
141
|
+
# when connections coalesce
|
142
|
+
return if connection.state == :idle
|
143
|
+
|
144
|
+
return if cloned
|
145
|
+
|
146
|
+
return if @closing && connection.state == :closed
|
147
|
+
|
148
|
+
@pool.checkin_connection(connection)
|
110
149
|
end
|
111
150
|
|
112
|
-
|
113
|
-
|
114
|
-
|
151
|
+
def deselect_resolver(resolver, selector)
|
152
|
+
selector.deregister(resolver)
|
153
|
+
|
154
|
+
return if @closing && resolver.closed?
|
155
|
+
|
156
|
+
@pool.checkin_resolver(resolver)
|
115
157
|
end
|
116
158
|
|
117
|
-
|
118
|
-
|
119
|
-
uri = request.uri
|
159
|
+
def try_clone_connection(connection, selector, family)
|
160
|
+
connection.family ||= family
|
120
161
|
|
121
|
-
connection
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
162
|
+
return connection if connection.family == family
|
163
|
+
|
164
|
+
new_connection = connection.class.new(connection.origin, connection.options)
|
165
|
+
|
166
|
+
new_connection.family = family
|
167
|
+
|
168
|
+
connection.sibling = new_connection
|
169
|
+
|
170
|
+
do_init_connection(new_connection, selector)
|
171
|
+
new_connection
|
127
172
|
end
|
128
173
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
connection
|
174
|
+
# returns the HTTPX::Connection through which the +request+ should be sent through.
|
175
|
+
def find_connection(request_uri, selector, options)
|
176
|
+
if (connection = selector.find_connection(request_uri, options))
|
177
|
+
return connection
|
133
178
|
end
|
134
|
-
return unless error.is_a?(Error)
|
135
179
|
|
136
|
-
|
137
|
-
end
|
180
|
+
connection = @pool.checkout_connection(request_uri, options)
|
138
181
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
182
|
+
case connection.state
|
183
|
+
when :idle
|
184
|
+
do_init_connection(connection, selector)
|
185
|
+
when :open
|
186
|
+
if options.io
|
187
|
+
select_connection(connection, selector)
|
188
|
+
else
|
189
|
+
pin_connection(connection, selector)
|
147
190
|
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
connection.only(:altsvc) do |alt_origin, origin, alt_params|
|
154
|
-
other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
|
155
|
-
connections << other_connection if other_connection
|
191
|
+
when :closing, :closed
|
192
|
+
connection.idling
|
193
|
+
select_connection(connection, selector)
|
194
|
+
else
|
195
|
+
pin_connection(connection, selector)
|
156
196
|
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
|
161
|
-
end
|
162
|
-
|
163
|
-
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
|
164
|
-
def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
|
165
|
-
# do not allow security downgrades on altsvc negotiation
|
166
|
-
return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
|
167
197
|
|
168
|
-
|
198
|
+
connection
|
199
|
+
end
|
169
200
|
|
170
|
-
|
171
|
-
return unless altsvc
|
201
|
+
private
|
172
202
|
|
173
|
-
|
203
|
+
def deactivate(selector)
|
204
|
+
selector.each_connection do |connection|
|
205
|
+
connection.deactivate
|
206
|
+
deselect_connection(connection, selector) if connection.state == :inactive
|
207
|
+
end
|
208
|
+
end
|
174
209
|
|
175
|
-
|
210
|
+
# callback executed when a response for a given request has been received.
|
211
|
+
def on_response(request, response)
|
212
|
+
@responses[request] = response
|
213
|
+
end
|
176
214
|
|
177
|
-
|
178
|
-
|
215
|
+
# callback executed when an HTTP/2 promise frame has been received.
|
216
|
+
def on_promise(_, stream)
|
217
|
+
log(level: 2) { "#{stream.id}: refusing stream!" }
|
218
|
+
stream.refuse
|
219
|
+
end
|
179
220
|
|
180
|
-
|
221
|
+
# returns the corresponding HTTP::Response to the given +request+ if it has been received.
|
222
|
+
def fetch_response(request, _selector, _options)
|
223
|
+
@responses.delete(request)
|
224
|
+
end
|
181
225
|
|
182
|
-
|
226
|
+
# sends the +request+ to the corresponding HTTPX::Connection
|
227
|
+
def send_request(request, selector, options = request.options)
|
228
|
+
error = begin
|
229
|
+
catch(:resolve_error) do
|
230
|
+
connection = find_connection(request.uri, selector, options)
|
231
|
+
connection.send(request)
|
232
|
+
end
|
233
|
+
rescue StandardError => e
|
234
|
+
e
|
235
|
+
end
|
236
|
+
return unless error && error.is_a?(Exception)
|
183
237
|
|
184
|
-
|
238
|
+
raise error unless error.is_a?(Error)
|
185
239
|
|
186
|
-
|
187
|
-
existing_connection.terminate
|
188
|
-
connection
|
189
|
-
rescue UnsupportedSchemeError
|
190
|
-
altsvc["noop"] = true
|
191
|
-
nil
|
240
|
+
request.emit(:response, ErrorResponse.new(request, error))
|
192
241
|
end
|
193
242
|
|
194
243
|
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
|
195
|
-
def build_requests(*args,
|
196
|
-
request_options = @options.merge(options)
|
197
|
-
|
244
|
+
def build_requests(*args, params)
|
198
245
|
requests = if args.size == 1
|
199
246
|
reqs = args.first
|
200
|
-
reqs.map do |verb, uri,
|
201
|
-
|
247
|
+
reqs.map do |verb, uri, ps = EMPTY_HASH|
|
248
|
+
request_params = params
|
249
|
+
request_params = request_params.merge(ps) unless ps.empty?
|
250
|
+
build_request(verb, uri, request_params)
|
202
251
|
end
|
203
252
|
else
|
204
253
|
verb, uris = args
|
205
254
|
if uris.respond_to?(:each)
|
206
|
-
uris.enum_for(:each).map do |uri,
|
207
|
-
|
255
|
+
uris.enum_for(:each).map do |uri, ps = EMPTY_HASH|
|
256
|
+
request_params = params
|
257
|
+
request_params = request_params.merge(ps) unless ps.empty?
|
258
|
+
build_request(verb, uri, request_params)
|
208
259
|
end
|
209
260
|
else
|
210
|
-
[build_request(verb, uris,
|
261
|
+
[build_request(verb, uris, params)]
|
211
262
|
end
|
212
263
|
end
|
213
264
|
raise ArgumentError, "wrong number of URIs (given 0, expect 1..+1)" if requests.empty?
|
@@ -220,67 +271,159 @@ module HTTPX
|
|
220
271
|
request.on(:promise, &method(:on_promise))
|
221
272
|
end
|
222
273
|
|
223
|
-
def
|
224
|
-
connection
|
225
|
-
catch(:coalesced) do
|
226
|
-
pool.init_connection(connection, options)
|
227
|
-
connection
|
228
|
-
end
|
274
|
+
def do_init_connection(connection, selector)
|
275
|
+
resolve_connection(connection, selector) unless connection.family
|
229
276
|
end
|
230
277
|
|
231
278
|
# sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
|
232
279
|
def send_requests(*requests)
|
233
|
-
|
234
|
-
|
280
|
+
selector = get_current_selector { Selector.new }
|
281
|
+
begin
|
282
|
+
_send_requests(requests, selector)
|
283
|
+
receive_requests(requests, selector)
|
284
|
+
ensure
|
285
|
+
unless @wrapped
|
286
|
+
if @persistent
|
287
|
+
deactivate(selector)
|
288
|
+
else
|
289
|
+
close(selector)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
235
293
|
end
|
236
294
|
|
237
295
|
# sends an array of HTTPX::Request objects
|
238
|
-
def _send_requests(requests)
|
239
|
-
connections = []
|
240
|
-
|
296
|
+
def _send_requests(requests, selector)
|
241
297
|
requests.each do |request|
|
242
|
-
send_request(request,
|
298
|
+
send_request(request, selector)
|
243
299
|
end
|
244
|
-
|
245
|
-
connections
|
246
300
|
end
|
247
301
|
|
248
302
|
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
|
249
|
-
def receive_requests(requests,
|
303
|
+
def receive_requests(requests, selector)
|
250
304
|
# @type var responses: Array[response]
|
251
305
|
responses = []
|
252
306
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
307
|
+
# guarantee ordered responses
|
308
|
+
loop do
|
309
|
+
request = requests.first
|
310
|
+
|
311
|
+
return responses unless request
|
257
312
|
|
258
|
-
|
313
|
+
catch(:coalesced) { selector.next_tick } until (response = fetch_response(request, selector, request.options))
|
314
|
+
request.emit(:complete, response)
|
259
315
|
|
260
|
-
|
316
|
+
responses << response
|
317
|
+
requests.shift
|
261
318
|
|
319
|
+
break if requests.empty?
|
320
|
+
|
321
|
+
next unless selector.empty?
|
322
|
+
|
323
|
+
# in some cases, the pool of connections might have been drained because there was some
|
324
|
+
# handshake error, and the error responses have already been emitted, but there was no
|
325
|
+
# opportunity to traverse the requests, hence we're returning only a fraction of the errors
|
326
|
+
# we were supposed to. This effectively fetches the existing responses and return them.
|
327
|
+
while (request = requests.shift)
|
328
|
+
response = fetch_response(request, selector, request.options)
|
329
|
+
request.emit(:complete, response) if response
|
262
330
|
responses << response
|
263
|
-
|
331
|
+
end
|
332
|
+
break
|
333
|
+
end
|
334
|
+
responses
|
335
|
+
end
|
264
336
|
|
265
|
-
|
337
|
+
def resolve_connection(connection, selector)
|
338
|
+
if connection.addresses || connection.open?
|
339
|
+
#
|
340
|
+
# there are two cases in which we want to activate initialization of
|
341
|
+
# connection immediately:
|
342
|
+
#
|
343
|
+
# 1. when the connection already has addresses, i.e. it doesn't need to
|
344
|
+
# resolve a name (not the same as name being an IP, yet)
|
345
|
+
# 2. when the connection is initialized with an external already open IO.
|
346
|
+
#
|
347
|
+
on_resolver_connection(connection, selector)
|
348
|
+
return
|
349
|
+
end
|
266
350
|
|
267
|
-
|
351
|
+
resolver = find_resolver_for(connection, selector)
|
268
352
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
353
|
+
resolver.early_resolve(connection) || resolver.lazy_resolve(connection)
|
354
|
+
end
|
355
|
+
|
356
|
+
def on_resolver_connection(connection, selector)
|
357
|
+
from_pool = false
|
358
|
+
found_connection = selector.find_mergeable_connection(connection) || begin
|
359
|
+
from_pool = true
|
360
|
+
@pool.checkout_mergeable_connection(connection)
|
361
|
+
end
|
362
|
+
|
363
|
+
return select_connection(connection, selector) unless found_connection
|
364
|
+
|
365
|
+
if found_connection.open?
|
366
|
+
coalesce_connections(found_connection, connection, selector, from_pool)
|
367
|
+
else
|
368
|
+
found_connection.once(:open) do
|
369
|
+
next unless found_connection.current_session == self
|
370
|
+
|
371
|
+
coalesce_connections(found_connection, connection, selector, from_pool)
|
277
372
|
end
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def on_resolver_close(resolver, selector)
|
377
|
+
return if resolver.closed?
|
378
|
+
|
379
|
+
deselect_resolver(resolver, selector)
|
380
|
+
resolver.close unless resolver.closed?
|
381
|
+
end
|
382
|
+
|
383
|
+
def find_resolver_for(connection, selector)
|
384
|
+
resolver = selector.find_resolver(connection.options)
|
385
|
+
|
386
|
+
unless resolver
|
387
|
+
resolver = @pool.checkout_resolver(connection.options)
|
388
|
+
resolver.current_session = self
|
389
|
+
resolver.current_selector = selector
|
390
|
+
end
|
391
|
+
|
392
|
+
resolver
|
393
|
+
end
|
394
|
+
|
395
|
+
# coalesces +conn2+ into +conn1+. if +conn1+ was loaded from the connection pool
|
396
|
+
# (it is known via +from_pool+), then it adds its to the +selector+.
|
397
|
+
def coalesce_connections(conn1, conn2, selector, from_pool)
|
398
|
+
unless conn1.coalescable?(conn2)
|
399
|
+
select_connection(conn2, selector)
|
400
|
+
@pool.checkin_connection(conn1) if from_pool
|
401
|
+
return false
|
402
|
+
end
|
403
|
+
|
404
|
+
conn2.coalesced_connection = conn1
|
405
|
+
select_connection(conn1, selector) if from_pool
|
406
|
+
deselect_connection(conn2, selector)
|
407
|
+
true
|
408
|
+
end
|
409
|
+
|
410
|
+
def get_current_selector
|
411
|
+
selector_store[self] || (yield if block_given?)
|
412
|
+
end
|
413
|
+
|
414
|
+
def set_current_selector(selector)
|
415
|
+
if selector
|
416
|
+
selector_store[self] = selector
|
417
|
+
else
|
418
|
+
selector_store.delete(self)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def selector_store
|
423
|
+
th_current = Thread.current
|
424
|
+
th_current.thread_variable_get(:httpx_persistent_selector_store) || begin
|
425
|
+
{}.compare_by_identity.tap do |store|
|
426
|
+
th_current.thread_variable_set(:httpx_persistent_selector_store, store)
|
284
427
|
end
|
285
428
|
end
|
286
429
|
end
|
data/lib/httpx/timers.rb
CHANGED
@@ -26,7 +26,7 @@ module HTTPX
|
|
26
26
|
|
27
27
|
@next_interval_at = nil
|
28
28
|
|
29
|
-
interval
|
29
|
+
Timer.new(interval, callback)
|
30
30
|
end
|
31
31
|
|
32
32
|
def wait_interval
|
@@ -43,11 +43,22 @@ module HTTPX
|
|
43
43
|
|
44
44
|
elapsed_time = Utils.elapsed_time(@next_interval_at)
|
45
45
|
|
46
|
-
@intervals.
|
46
|
+
@intervals = @intervals.drop_while { |interval| interval.elapse(elapsed_time) <= 0 }
|
47
47
|
|
48
48
|
@next_interval_at = nil if @intervals.empty?
|
49
49
|
end
|
50
50
|
|
51
|
+
class Timer
|
52
|
+
def initialize(interval, callback)
|
53
|
+
@interval = interval
|
54
|
+
@callback = callback
|
55
|
+
end
|
56
|
+
|
57
|
+
def cancel
|
58
|
+
@interval.delete(@callback)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
51
62
|
class Interval
|
52
63
|
include Comparable
|
53
64
|
|
@@ -63,6 +74,10 @@ module HTTPX
|
|
63
74
|
@on_empty = blk
|
64
75
|
end
|
65
76
|
|
77
|
+
def cancel
|
78
|
+
@on_empty.call
|
79
|
+
end
|
80
|
+
|
66
81
|
def <=>(other)
|
67
82
|
@interval <=> other.interval
|
68
83
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "delegate"
|
4
4
|
|
5
5
|
module HTTPX::Transcoder
|
6
6
|
module Body
|
@@ -8,46 +8,32 @@ module HTTPX::Transcoder
|
|
8
8
|
|
9
9
|
module_function
|
10
10
|
|
11
|
-
class Encoder
|
12
|
-
extend Forwardable
|
13
|
-
|
14
|
-
def_delegator :@raw, :to_s
|
15
|
-
|
11
|
+
class Encoder < SimpleDelegator
|
16
12
|
def initialize(body)
|
17
|
-
|
13
|
+
body = body.open(File::RDONLY, encoding: Encoding::BINARY) if Object.const_defined?(:Pathname) && body.is_a?(Pathname)
|
14
|
+
@body = body
|
15
|
+
super(body)
|
18
16
|
end
|
19
17
|
|
20
18
|
def bytesize
|
21
|
-
if @
|
22
|
-
@
|
23
|
-
elsif @
|
24
|
-
@
|
25
|
-
elsif @
|
26
|
-
@
|
27
|
-
elsif @
|
28
|
-
@
|
29
|
-
elsif @
|
19
|
+
if @body.respond_to?(:bytesize)
|
20
|
+
@body.bytesize
|
21
|
+
elsif @body.respond_to?(:to_ary)
|
22
|
+
@body.sum(&:bytesize)
|
23
|
+
elsif @body.respond_to?(:size)
|
24
|
+
@body.size || Float::INFINITY
|
25
|
+
elsif @body.respond_to?(:length)
|
26
|
+
@body.length || Float::INFINITY
|
27
|
+
elsif @body.respond_to?(:each)
|
30
28
|
Float::INFINITY
|
31
29
|
else
|
32
|
-
raise Error, "cannot determine size of body: #{@
|
30
|
+
raise Error, "cannot determine size of body: #{@body.inspect}"
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
34
|
def content_type
|
37
35
|
"application/octet-stream"
|
38
36
|
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def respond_to_missing?(meth, *args)
|
43
|
-
@raw.respond_to?(meth, *args) || super
|
44
|
-
end
|
45
|
-
|
46
|
-
def method_missing(meth, *args, &block)
|
47
|
-
return super unless @raw.respond_to?(meth)
|
48
|
-
|
49
|
-
@raw.__send__(meth, *args, &block)
|
50
|
-
end
|
51
37
|
end
|
52
38
|
|
53
39
|
def encode(body)
|