httpx 1.3.4 → 1.4.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_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +18 -6
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +12 -8
- data/lib/httpx/connection.rb +192 -22
- data/lib/httpx/errors.rb +12 -0
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +26 -16
- data/lib/httpx/plugins/aws_sigv4.rb +31 -16
- data/lib/httpx/plugins/callbacks.rb +12 -2
- data/lib/httpx/plugins/circuit_breaker.rb +0 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/expect.rb +4 -3
- data/lib/httpx/plugins/follow_redirects.rb +7 -8
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +27 -0
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +17 -19
- data/lib/httpx/plugins/proxy.rb +91 -93
- data/lib/httpx/plugins/retries.rb +5 -8
- 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 +25 -26
- data/lib/httpx/request.rb +7 -1
- data/lib/httpx/resolver/https.rb +15 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +66 -25
- data/lib/httpx/resolver/resolver.rb +59 -15
- data/lib/httpx/resolver/system.rb +31 -15
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +160 -95
- data/lib/httpx/session.rb +273 -140
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder/utils/inflater.rb +2 -0
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +20 -21
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +29 -3
- data/sig/errors.rbs +6 -0
- data/sig/loggable.rbs +2 -0
- data/sig/options.rbs +7 -0
- 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/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -11
- data/sig/plugins/push_promise.rbs +3 -0
- 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/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +25 -33
- data/sig/request/body.rbs +5 -9
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +2 -2
- data/sig/resolver/resolver.rbs +21 -2
- data/sig/resolver.rbs +5 -1
- data/sig/response/buffer.rbs +1 -1
- data/sig/selector.rbs +30 -4
- data/sig/session.rbs +47 -18
- 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/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +3 -3
- metadata +12 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/session.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module HTTPX
|
4
|
-
EMPTY_HASH = {}.freeze
|
5
|
-
|
6
4
|
# Class implementing the APIs being used publicly.
|
7
5
|
#
|
8
6
|
# HTTPX.get(..) #=> delegating to an internal HTTPX::Session object.
|
@@ -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:
|
@@ -89,113 +123,134 @@ module HTTPX
|
|
89
123
|
request
|
90
124
|
end
|
91
125
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
# perform requests.
|
96
|
-
def pool
|
97
|
-
Thread.current[:httpx_connection_pool] ||= Pool.new
|
126
|
+
def select_connection(connection, selector)
|
127
|
+
pin_connection(connection, selector)
|
128
|
+
selector.register(connection)
|
98
129
|
end
|
99
130
|
|
100
|
-
|
101
|
-
|
102
|
-
|
131
|
+
def pin_connection(connection, selector)
|
132
|
+
connection.current_session = self
|
133
|
+
connection.current_selector = selector
|
103
134
|
end
|
104
135
|
|
105
|
-
|
106
|
-
def on_promise(_, stream)
|
107
|
-
log(level: 2) { "#{stream.id}: refusing stream!" }
|
108
|
-
stream.refuse
|
109
|
-
end
|
136
|
+
alias_method :select_resolver, :select_connection
|
110
137
|
|
111
|
-
|
112
|
-
|
113
|
-
|
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)
|
114
149
|
end
|
115
150
|
|
116
|
-
|
117
|
-
|
118
|
-
uri = request.uri
|
151
|
+
def deselect_resolver(resolver, selector)
|
152
|
+
selector.deregister(resolver)
|
119
153
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
set_connection_callbacks(connection, connections, options)
|
124
|
-
end
|
125
|
-
connection
|
154
|
+
return if @closing && resolver.closed?
|
155
|
+
|
156
|
+
@pool.checkin_resolver(resolver)
|
126
157
|
end
|
127
158
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
159
|
+
def try_clone_connection(connection, selector, family)
|
160
|
+
connection.family ||= family
|
161
|
+
|
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
|
135
169
|
|
136
|
-
|
170
|
+
do_init_connection(new_connection, selector)
|
171
|
+
new_connection
|
137
172
|
end
|
138
173
|
|
139
|
-
#
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
|
144
|
-
other_connection.merge(connection)
|
145
|
-
catch(:coalesced) do
|
146
|
-
pool.init_connection(other_connection, options)
|
147
|
-
end
|
148
|
-
set_connection_callbacks(other_connection, connections, options)
|
149
|
-
connections << other_connection
|
150
|
-
misdirected_request.transition(:idle)
|
151
|
-
other_connection.send(misdirected_request)
|
152
|
-
end
|
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
|
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
|
156
178
|
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
179
|
|
163
|
-
|
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"
|
180
|
+
connection = @pool.checkout_connection(request_uri, options)
|
167
181
|
|
168
|
-
|
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)
|
190
|
+
end
|
191
|
+
when :closed
|
192
|
+
connection.idling
|
193
|
+
select_connection(connection, selector)
|
194
|
+
when :closing
|
195
|
+
connection.once(:close) do
|
196
|
+
connection.idling
|
197
|
+
select_connection(connection, selector)
|
198
|
+
end
|
199
|
+
else
|
200
|
+
pin_connection(connection, selector)
|
201
|
+
end
|
169
202
|
|
170
|
-
|
171
|
-
|
203
|
+
connection
|
204
|
+
end
|
172
205
|
|
173
|
-
|
206
|
+
private
|
174
207
|
|
175
|
-
|
208
|
+
def deactivate(selector)
|
209
|
+
selector.each_connection do |connection|
|
210
|
+
connection.deactivate
|
211
|
+
deselect_connection(connection, selector) if connection.state == :inactive
|
212
|
+
end
|
213
|
+
end
|
176
214
|
|
177
|
-
|
178
|
-
|
215
|
+
# callback executed when a response for a given request has been received.
|
216
|
+
def on_response(request, response)
|
217
|
+
@responses[request] = response
|
218
|
+
end
|
179
219
|
|
180
|
-
|
220
|
+
# callback executed when an HTTP/2 promise frame has been received.
|
221
|
+
def on_promise(_, stream)
|
222
|
+
log(level: 2) { "#{stream.id}: refusing stream!" }
|
223
|
+
stream.refuse
|
224
|
+
end
|
181
225
|
|
182
|
-
|
226
|
+
# returns the corresponding HTTP::Response to the given +request+ if it has been received.
|
227
|
+
def fetch_response(request, _selector, _options)
|
228
|
+
@responses.delete(request)
|
229
|
+
end
|
183
230
|
|
184
|
-
|
231
|
+
# sends the +request+ to the corresponding HTTPX::Connection
|
232
|
+
def send_request(request, selector, options = request.options)
|
233
|
+
error = begin
|
234
|
+
catch(:resolve_error) do
|
235
|
+
connection = find_connection(request.uri, selector, options)
|
236
|
+
connection.send(request)
|
237
|
+
end
|
238
|
+
rescue StandardError => e
|
239
|
+
e
|
240
|
+
end
|
241
|
+
return unless error && error.is_a?(Exception)
|
185
242
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
nil
|
243
|
+
if error.is_a?(Error)
|
244
|
+
request.emit(:response, ErrorResponse.new(request, error))
|
245
|
+
else
|
246
|
+
raise error if selector.empty?
|
247
|
+
end
|
192
248
|
end
|
193
249
|
|
194
250
|
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
|
195
251
|
def build_requests(*args, params)
|
196
252
|
requests = if args.size == 1
|
197
253
|
reqs = args.first
|
198
|
-
# TODO: find a way to make requests share same options object
|
199
254
|
reqs.map do |verb, uri, ps = EMPTY_HASH|
|
200
255
|
request_params = params
|
201
256
|
request_params = request_params.merge(ps) unless ps.empty?
|
@@ -204,7 +259,6 @@ module HTTPX
|
|
204
259
|
else
|
205
260
|
verb, uris = args
|
206
261
|
if uris.respond_to?(:each)
|
207
|
-
# TODO: find a way to make requests share same options object
|
208
262
|
uris.enum_for(:each).map do |uri, ps = EMPTY_HASH|
|
209
263
|
request_params = params
|
210
264
|
request_params = request_params.merge(ps) unless ps.empty?
|
@@ -224,78 +278,157 @@ module HTTPX
|
|
224
278
|
request.on(:promise, &method(:on_promise))
|
225
279
|
end
|
226
280
|
|
227
|
-
def
|
228
|
-
connection
|
229
|
-
catch(:coalesced) do
|
230
|
-
pool.init_connection(connection, options)
|
231
|
-
connection
|
232
|
-
end
|
233
|
-
end
|
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
|
281
|
+
def do_init_connection(connection, selector)
|
282
|
+
resolve_connection(connection, selector) unless connection.family
|
241
283
|
end
|
242
284
|
|
243
285
|
# sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
|
244
286
|
def send_requests(*requests)
|
245
|
-
|
246
|
-
|
287
|
+
selector = get_current_selector { Selector.new }
|
288
|
+
begin
|
289
|
+
_send_requests(requests, selector)
|
290
|
+
receive_requests(requests, selector)
|
291
|
+
ensure
|
292
|
+
unless @wrapped
|
293
|
+
if @persistent
|
294
|
+
deactivate(selector)
|
295
|
+
else
|
296
|
+
close(selector)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
247
300
|
end
|
248
301
|
|
249
302
|
# sends an array of HTTPX::Request objects
|
250
|
-
def _send_requests(requests)
|
251
|
-
connections = []
|
252
|
-
|
303
|
+
def _send_requests(requests, selector)
|
253
304
|
requests.each do |request|
|
254
|
-
send_request(request,
|
305
|
+
send_request(request, selector)
|
255
306
|
end
|
256
|
-
|
257
|
-
connections
|
258
307
|
end
|
259
308
|
|
260
309
|
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
|
261
|
-
def receive_requests(requests,
|
310
|
+
def receive_requests(requests, selector)
|
262
311
|
# @type var responses: Array[response]
|
263
312
|
responses = []
|
264
313
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
314
|
+
# guarantee ordered responses
|
315
|
+
loop do
|
316
|
+
request = requests.first
|
317
|
+
|
318
|
+
return responses unless request
|
269
319
|
|
270
|
-
|
320
|
+
catch(:coalesced) { selector.next_tick } until (response = fetch_response(request, selector, request.options))
|
321
|
+
request.emit(:complete, response)
|
271
322
|
|
272
|
-
|
273
|
-
|
323
|
+
responses << response
|
324
|
+
requests.shift
|
274
325
|
|
326
|
+
break if requests.empty?
|
327
|
+
|
328
|
+
next unless selector.empty?
|
329
|
+
|
330
|
+
# in some cases, the pool of connections might have been drained because there was some
|
331
|
+
# handshake error, and the error responses have already been emitted, but there was no
|
332
|
+
# opportunity to traverse the requests, hence we're returning only a fraction of the errors
|
333
|
+
# we were supposed to. This effectively fetches the existing responses and return them.
|
334
|
+
while (request = requests.shift)
|
335
|
+
response = fetch_response(request, selector, request.options)
|
336
|
+
request.emit(:complete, response) if response
|
275
337
|
responses << response
|
276
|
-
|
338
|
+
end
|
339
|
+
break
|
340
|
+
end
|
341
|
+
responses
|
342
|
+
end
|
277
343
|
|
278
|
-
|
344
|
+
def resolve_connection(connection, selector)
|
345
|
+
if connection.addresses || connection.open?
|
346
|
+
#
|
347
|
+
# there are two cases in which we want to activate initialization of
|
348
|
+
# connection immediately:
|
349
|
+
#
|
350
|
+
# 1. when the connection already has addresses, i.e. it doesn't need to
|
351
|
+
# resolve a name (not the same as name being an IP, yet)
|
352
|
+
# 2. when the connection is initialized with an external already open IO.
|
353
|
+
#
|
354
|
+
on_resolver_connection(connection, selector)
|
355
|
+
return
|
356
|
+
end
|
279
357
|
|
280
|
-
|
358
|
+
resolver = find_resolver_for(connection, selector)
|
281
359
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
360
|
+
resolver.early_resolve(connection) || resolver.lazy_resolve(connection)
|
361
|
+
end
|
362
|
+
|
363
|
+
def on_resolver_connection(connection, selector)
|
364
|
+
from_pool = false
|
365
|
+
found_connection = selector.find_mergeable_connection(connection) || begin
|
366
|
+
from_pool = true
|
367
|
+
@pool.checkout_mergeable_connection(connection)
|
368
|
+
end
|
369
|
+
|
370
|
+
return select_connection(connection, selector) unless found_connection
|
371
|
+
|
372
|
+
if found_connection.open?
|
373
|
+
coalesce_connections(found_connection, connection, selector, from_pool)
|
374
|
+
else
|
375
|
+
found_connection.once(:open) do
|
376
|
+
coalesce_connections(found_connection, connection, selector, from_pool)
|
292
377
|
end
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def on_resolver_close(resolver, selector)
|
382
|
+
return if resolver.closed?
|
383
|
+
|
384
|
+
deselect_resolver(resolver, selector)
|
385
|
+
resolver.close unless resolver.closed?
|
386
|
+
end
|
387
|
+
|
388
|
+
def find_resolver_for(connection, selector)
|
389
|
+
resolver = selector.find_resolver(connection.options)
|
390
|
+
|
391
|
+
unless resolver
|
392
|
+
resolver = @pool.checkout_resolver(connection.options)
|
393
|
+
resolver.current_session = self
|
394
|
+
resolver.current_selector = selector
|
395
|
+
end
|
396
|
+
|
397
|
+
resolver
|
398
|
+
end
|
399
|
+
|
400
|
+
# coalesces +conn2+ into +conn1+. if +conn1+ was loaded from the connection pool
|
401
|
+
# (it is known via +from_pool+), then it adds its to the +selector+.
|
402
|
+
def coalesce_connections(conn1, conn2, selector, from_pool)
|
403
|
+
unless conn1.coalescable?(conn2)
|
404
|
+
select_connection(conn2, selector)
|
405
|
+
@pool.checkin_connection(conn1) if from_pool
|
406
|
+
return false
|
407
|
+
end
|
408
|
+
|
409
|
+
conn2.coalesced_connection = conn1
|
410
|
+
select_connection(conn1, selector) if from_pool
|
411
|
+
deselect_connection(conn2, selector)
|
412
|
+
true
|
413
|
+
end
|
414
|
+
|
415
|
+
def get_current_selector
|
416
|
+
selector_store[self] || (yield if block_given?)
|
417
|
+
end
|
418
|
+
|
419
|
+
def set_current_selector(selector)
|
420
|
+
if selector
|
421
|
+
selector_store[self] = selector
|
422
|
+
else
|
423
|
+
selector_store.delete(self)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def selector_store
|
428
|
+
th_current = Thread.current
|
429
|
+
th_current.thread_variable_get(:httpx_persistent_selector_store) || begin
|
430
|
+
{}.compare_by_identity.tap do |store|
|
431
|
+
th_current.thread_variable_set(:httpx_persistent_selector_store, store)
|
299
432
|
end
|
300
433
|
end
|
301
434
|
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,48 +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
|
-
|
16
|
-
def_delegator :@raw, :==
|
17
|
-
|
11
|
+
class Encoder < SimpleDelegator
|
18
12
|
def initialize(body)
|
19
|
-
|
13
|
+
body = body.open(File::RDONLY, encoding: Encoding::BINARY) if Object.const_defined?(:Pathname) && body.is_a?(Pathname)
|
14
|
+
@body = body
|
15
|
+
super(body)
|
20
16
|
end
|
21
17
|
|
22
18
|
def bytesize
|
23
|
-
if @
|
24
|
-
@
|
25
|
-
elsif @
|
26
|
-
@
|
27
|
-
elsif @
|
28
|
-
@
|
29
|
-
elsif @
|
30
|
-
@
|
31
|
-
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)
|
32
28
|
Float::INFINITY
|
33
29
|
else
|
34
|
-
raise Error, "cannot determine size of body: #{@
|
30
|
+
raise Error, "cannot determine size of body: #{@body.inspect}"
|
35
31
|
end
|
36
32
|
end
|
37
33
|
|
38
34
|
def content_type
|
39
35
|
"application/octet-stream"
|
40
36
|
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def respond_to_missing?(meth, *args)
|
45
|
-
@raw.respond_to?(meth, *args) || super
|
46
|
-
end
|
47
|
-
|
48
|
-
def method_missing(meth, *args, &block)
|
49
|
-
return super unless @raw.respond_to?(meth)
|
50
|
-
|
51
|
-
@raw.__send__(meth, *args, &block)
|
52
|
-
end
|
53
37
|
end
|
54
38
|
|
55
39
|
def encode(body)
|
@@ -6,7 +6,19 @@ module HTTPX::Transcoder
|
|
6
6
|
module JSON
|
7
7
|
module_function
|
8
8
|
|
9
|
-
JSON_REGEX = %r{
|
9
|
+
JSON_REGEX = %r{
|
10
|
+
\b
|
11
|
+
application/
|
12
|
+
# optional vendor specific type
|
13
|
+
(?:
|
14
|
+
# token as per https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
15
|
+
[!#$%&'*+\-.^_`|~0-9a-z]+
|
16
|
+
# literal plus sign
|
17
|
+
\+
|
18
|
+
)?
|
19
|
+
json
|
20
|
+
\b
|
21
|
+
}ix.freeze
|
10
22
|
|
11
23
|
class Encoder
|
12
24
|
extend Forwardable
|
@@ -45,7 +57,7 @@ module HTTPX::Transcoder
|
|
45
57
|
def json_dump(*args); MultiJson.dump(*args); end
|
46
58
|
elsif defined?(Oj)
|
47
59
|
def json_load(response, *args); Oj.load(response.to_s, *args); end
|
48
|
-
def json_dump(
|
60
|
+
def json_dump(obj, options = {}); Oj.dump(obj, { mode: :compat }.merge(options)); end
|
49
61
|
elsif defined?(Yajl)
|
50
62
|
def json_load(response, *args); Yajl::Parser.new(*args).parse(response.to_s); end
|
51
63
|
def json_dump(*args); Yajl::Encoder.encode(*args); end
|
@@ -19,7 +19,7 @@ module HTTPX
|
|
19
19
|
value = value[:body]
|
20
20
|
end
|
21
21
|
|
22
|
-
value = value.open(File::RDONLY) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
|
22
|
+
value = value.open(File::RDONLY, encoding: Encoding::BINARY) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
|
23
23
|
|
24
24
|
if value.respond_to?(:path) && value.respond_to?(:read)
|
25
25
|
# either a File, a Tempfile, or something else which has to quack like a file
|
@@ -1,13 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
3
|
require_relative "body_reader"
|
5
4
|
|
6
5
|
module HTTPX
|
7
6
|
module Transcoder
|
8
7
|
class Deflater
|
9
|
-
extend Forwardable
|
10
|
-
|
11
8
|
attr_reader :content_type
|
12
9
|
|
13
10
|
def initialize(body)
|
@@ -51,6 +48,12 @@ module HTTPX
|
|
51
48
|
@closed = true
|
52
49
|
end
|
53
50
|
|
51
|
+
def rewind
|
52
|
+
return unless @buffer
|
53
|
+
|
54
|
+
@buffer.rewind
|
55
|
+
end
|
56
|
+
|
54
57
|
private
|
55
58
|
|
56
59
|
# rubocop:disable Naming/MemoizedInstanceVariableName
|
@@ -62,7 +65,7 @@ module HTTPX
|
|
62
65
|
)
|
63
66
|
::IO.copy_stream(self, buffer)
|
64
67
|
|
65
|
-
buffer.rewind
|
68
|
+
buffer.rewind if buffer.respond_to?(:rewind)
|
66
69
|
|
67
70
|
@buffer = buffer
|
68
71
|
end
|
data/lib/httpx/transcoder.rb
CHANGED