httpx 1.3.4 → 1.4.0
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/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +11 -5
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +11 -7
- data/lib/httpx/connection.rb +128 -16
- 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/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 +16 -12
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +12 -19
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +36 -13
- data/lib/httpx/resolver/resolver.rb +49 -11
- data/lib/httpx/resolver/system.rb +29 -11
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +164 -95
- data/lib/httpx/session.rb +296 -139
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- 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 +19 -20
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +14 -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 -1
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +0 -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 +45 -18
- data/sig/transcoder/body.rbs +1 -1
- 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 +2 -2
- data/sig/transcoder/utils/deflater.rbs +2 -2
- metadata +10 -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,56 @@ 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
|
+
connection.current_session = self
|
73
|
+
connection.current_selector = selector
|
74
|
+
select_connection(connection, selector)
|
75
|
+
end
|
76
|
+
begin
|
77
|
+
@closing = true
|
78
|
+
selector.terminate
|
79
|
+
ensure
|
80
|
+
@closing = false
|
81
|
+
end
|
46
82
|
end
|
47
83
|
|
48
84
|
# performs one, or multple requests; it accepts:
|
@@ -89,113 +125,153 @@ module HTTPX
|
|
89
125
|
request
|
90
126
|
end
|
91
127
|
|
92
|
-
|
93
|
-
|
94
|
-
# returns the HTTPX::Pool object which manages the networking required to
|
95
|
-
# perform requests.
|
96
|
-
def pool
|
97
|
-
Thread.current[:httpx_connection_pool] ||= Pool.new
|
128
|
+
def select_connection(connection, selector)
|
129
|
+
selector.register(connection)
|
98
130
|
end
|
99
131
|
|
100
|
-
|
101
|
-
def on_response(request, response)
|
102
|
-
@responses[request] = response
|
103
|
-
end
|
132
|
+
alias_method :select_resolver, :select_connection
|
104
133
|
|
105
|
-
|
106
|
-
|
107
|
-
log(level: 2) { "#{stream.id}: refusing stream!" }
|
108
|
-
stream.refuse
|
109
|
-
end
|
134
|
+
def deselect_connection(connection, selector, cloned = false)
|
135
|
+
selector.deregister(connection)
|
110
136
|
|
111
|
-
|
112
|
-
|
113
|
-
@responses.delete(request)
|
114
|
-
end
|
137
|
+
# when connections coalesce
|
138
|
+
return if connection.state == :idle
|
115
139
|
|
116
|
-
|
117
|
-
def find_connection(request, connections, options)
|
118
|
-
uri = request.uri
|
140
|
+
return if cloned
|
119
141
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
set_connection_callbacks(connection, connections, options)
|
124
|
-
end
|
125
|
-
connection
|
142
|
+
return if @closing && connection.state == :closed
|
143
|
+
|
144
|
+
@pool.checkin_connection(connection)
|
126
145
|
end
|
127
146
|
|
128
|
-
|
129
|
-
|
130
|
-
error = catch(:resolve_error) do
|
131
|
-
connection = find_connection(request, connections, options)
|
132
|
-
connection.send(request)
|
133
|
-
end
|
134
|
-
return unless error.is_a?(Error)
|
147
|
+
def deselect_resolver(resolver, selector)
|
148
|
+
selector.deregister(resolver)
|
135
149
|
|
136
|
-
|
150
|
+
return if @closing && resolver.closed?
|
151
|
+
|
152
|
+
@pool.checkin_resolver(resolver)
|
137
153
|
end
|
138
154
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
connection.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
155
|
+
def try_clone_connection(connection, selector, family)
|
156
|
+
connection.family ||= family
|
157
|
+
|
158
|
+
return connection if connection.family == family
|
159
|
+
|
160
|
+
new_connection = connection.class.new(connection.origin, connection.options)
|
161
|
+
|
162
|
+
new_connection.family = family
|
163
|
+
new_connection.current_session = self
|
164
|
+
new_connection.current_selector = selector
|
165
|
+
|
166
|
+
connection.once(:tcp_open) { new_connection.force_reset(true) }
|
167
|
+
connection.once(:connect_error) do |err|
|
168
|
+
if new_connection.connecting?
|
169
|
+
new_connection.merge(connection)
|
170
|
+
connection.emit(:cloned, new_connection)
|
171
|
+
connection.force_reset(true)
|
172
|
+
else
|
173
|
+
connection.__send__(:handle_error, err)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
new_connection.once(:tcp_open) do |new_conn|
|
178
|
+
if new_conn != connection
|
179
|
+
new_conn.merge(connection)
|
180
|
+
connection.force_reset(true)
|
147
181
|
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
182
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
183
|
+
new_connection.once(:connect_error) do |err|
|
184
|
+
if connection.connecting?
|
185
|
+
# main connection has the requests
|
186
|
+
connection.merge(new_connection)
|
187
|
+
new_connection.emit(:cloned, connection)
|
188
|
+
new_connection.force_reset(true)
|
189
|
+
else
|
190
|
+
new_connection.__send__(:handle_error, err)
|
191
|
+
end
|
156
192
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end unless cloned
|
193
|
+
|
194
|
+
do_init_connection(new_connection, selector)
|
195
|
+
new_connection
|
161
196
|
end
|
162
197
|
|
163
|
-
# returns
|
164
|
-
def
|
165
|
-
|
166
|
-
|
198
|
+
# returns the HTTPX::Connection through which the +request+ should be sent through.
|
199
|
+
def find_connection(request_uri, selector, options)
|
200
|
+
if (connection = selector.find_connection(request_uri, options))
|
201
|
+
return connection
|
202
|
+
end
|
167
203
|
|
168
|
-
|
204
|
+
connection = @pool.checkout_connection(request_uri, options)
|
205
|
+
|
206
|
+
connection.current_session = self
|
207
|
+
connection.current_selector = selector
|
208
|
+
|
209
|
+
case connection.state
|
210
|
+
when :idle
|
211
|
+
do_init_connection(connection, selector)
|
212
|
+
when :open
|
213
|
+
select_connection(connection, selector) if options.io
|
214
|
+
when :closed
|
215
|
+
connection.idling
|
216
|
+
select_connection(connection, selector)
|
217
|
+
when :closing
|
218
|
+
connection.once(:close) do
|
219
|
+
connection.idling
|
220
|
+
select_connection(connection, selector)
|
221
|
+
end
|
222
|
+
end
|
169
223
|
|
170
|
-
|
171
|
-
|
224
|
+
connection
|
225
|
+
end
|
172
226
|
|
173
|
-
|
227
|
+
private
|
174
228
|
|
175
|
-
|
229
|
+
def deactivate(selector)
|
230
|
+
selector.each_connection do |connection|
|
231
|
+
connection.deactivate
|
232
|
+
deselect_connection(connection, selector) if connection.state == :inactive
|
233
|
+
end
|
234
|
+
end
|
176
235
|
|
177
|
-
|
178
|
-
|
236
|
+
# callback executed when a response for a given request has been received.
|
237
|
+
def on_response(request, response)
|
238
|
+
@responses[request] = response
|
239
|
+
end
|
179
240
|
|
180
|
-
|
241
|
+
# callback executed when an HTTP/2 promise frame has been received.
|
242
|
+
def on_promise(_, stream)
|
243
|
+
log(level: 2) { "#{stream.id}: refusing stream!" }
|
244
|
+
stream.refuse
|
245
|
+
end
|
181
246
|
|
182
|
-
|
247
|
+
# returns the corresponding HTTP::Response to the given +request+ if it has been received.
|
248
|
+
def fetch_response(request, _selector, _options)
|
249
|
+
@responses.delete(request)
|
250
|
+
end
|
183
251
|
|
184
|
-
|
252
|
+
# sends the +request+ to the corresponding HTTPX::Connection
|
253
|
+
def send_request(request, selector, options = request.options)
|
254
|
+
error = begin
|
255
|
+
catch(:resolve_error) do
|
256
|
+
connection = find_connection(request.uri, selector, options)
|
257
|
+
connection.send(request)
|
258
|
+
end
|
259
|
+
rescue StandardError => e
|
260
|
+
e
|
261
|
+
end
|
262
|
+
return unless error && error.is_a?(Exception)
|
185
263
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
nil
|
264
|
+
if error.is_a?(Error)
|
265
|
+
request.emit(:response, ErrorResponse.new(request, error))
|
266
|
+
else
|
267
|
+
raise error if selector.empty?
|
268
|
+
end
|
192
269
|
end
|
193
270
|
|
194
271
|
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
|
195
272
|
def build_requests(*args, params)
|
196
273
|
requests = if args.size == 1
|
197
274
|
reqs = args.first
|
198
|
-
# TODO: find a way to make requests share same options object
|
199
275
|
reqs.map do |verb, uri, ps = EMPTY_HASH|
|
200
276
|
request_params = params
|
201
277
|
request_params = request_params.merge(ps) unless ps.empty?
|
@@ -204,7 +280,6 @@ module HTTPX
|
|
204
280
|
else
|
205
281
|
verb, uris = args
|
206
282
|
if uris.respond_to?(:each)
|
207
|
-
# TODO: find a way to make requests share same options object
|
208
283
|
uris.enum_for(:each).map do |uri, ps = EMPTY_HASH|
|
209
284
|
request_params = params
|
210
285
|
request_params = request_params.merge(ps) unless ps.empty?
|
@@ -224,78 +299,160 @@ module HTTPX
|
|
224
299
|
request.on(:promise, &method(:on_promise))
|
225
300
|
end
|
226
301
|
|
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
|
302
|
+
def do_init_connection(connection, selector)
|
303
|
+
resolve_connection(connection, selector) unless connection.family
|
241
304
|
end
|
242
305
|
|
243
306
|
# sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
|
244
307
|
def send_requests(*requests)
|
245
|
-
|
246
|
-
|
308
|
+
selector = get_current_selector { Selector.new }
|
309
|
+
begin
|
310
|
+
_send_requests(requests, selector)
|
311
|
+
receive_requests(requests, selector)
|
312
|
+
ensure
|
313
|
+
unless @wrapped
|
314
|
+
if @persistent
|
315
|
+
deactivate(selector)
|
316
|
+
else
|
317
|
+
close(selector)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
247
321
|
end
|
248
322
|
|
249
323
|
# sends an array of HTTPX::Request objects
|
250
|
-
def _send_requests(requests)
|
251
|
-
connections = []
|
252
|
-
|
324
|
+
def _send_requests(requests, selector)
|
253
325
|
requests.each do |request|
|
254
|
-
send_request(request,
|
326
|
+
send_request(request, selector)
|
255
327
|
end
|
256
|
-
|
257
|
-
connections
|
258
328
|
end
|
259
329
|
|
260
330
|
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
|
261
|
-
def receive_requests(requests,
|
331
|
+
def receive_requests(requests, selector)
|
262
332
|
# @type var responses: Array[response]
|
263
333
|
responses = []
|
264
334
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
335
|
+
# guarantee ordered responses
|
336
|
+
loop do
|
337
|
+
request = requests.first
|
338
|
+
|
339
|
+
return responses unless request
|
269
340
|
|
270
|
-
|
341
|
+
catch(:coalesced) { selector.next_tick } until (response = fetch_response(request, selector, request.options))
|
342
|
+
request.emit(:complete, response)
|
271
343
|
|
272
|
-
|
273
|
-
|
344
|
+
responses << response
|
345
|
+
requests.shift
|
274
346
|
|
347
|
+
break if requests.empty?
|
348
|
+
|
349
|
+
next unless selector.empty?
|
350
|
+
|
351
|
+
# in some cases, the pool of connections might have been drained because there was some
|
352
|
+
# handshake error, and the error responses have already been emitted, but there was no
|
353
|
+
# opportunity to traverse the requests, hence we're returning only a fraction of the errors
|
354
|
+
# we were supposed to. This effectively fetches the existing responses and return them.
|
355
|
+
while (request = requests.shift)
|
356
|
+
response = fetch_response(request, selector, request.options)
|
357
|
+
request.emit(:complete, response) if response
|
275
358
|
responses << response
|
276
|
-
|
359
|
+
end
|
360
|
+
break
|
361
|
+
end
|
362
|
+
responses
|
363
|
+
end
|
277
364
|
|
278
|
-
|
365
|
+
def resolve_connection(connection, selector)
|
366
|
+
if connection.addresses || connection.open?
|
367
|
+
#
|
368
|
+
# there are two cases in which we want to activate initialization of
|
369
|
+
# connection immediately:
|
370
|
+
#
|
371
|
+
# 1. when the connection already has addresses, i.e. it doesn't need to
|
372
|
+
# resolve a name (not the same as name being an IP, yet)
|
373
|
+
# 2. when the connection is initialized with an external already open IO.
|
374
|
+
#
|
375
|
+
connection.once(:connect_error, &connection.method(:handle_error))
|
376
|
+
on_resolver_connection(connection, selector)
|
377
|
+
return
|
378
|
+
end
|
279
379
|
|
280
|
-
|
380
|
+
resolver = find_resolver_for(connection, selector)
|
281
381
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
382
|
+
resolver.early_resolve(connection) || resolver.lazy_resolve(connection)
|
383
|
+
end
|
384
|
+
|
385
|
+
def on_resolver_connection(connection, selector)
|
386
|
+
from_pool = false
|
387
|
+
found_connection = selector.find_mergeable_connection(connection) || begin
|
388
|
+
from_pool = true
|
389
|
+
@pool.checkout_mergeable_connection(connection)
|
390
|
+
end
|
391
|
+
|
392
|
+
return select_connection(connection, selector) unless found_connection
|
393
|
+
|
394
|
+
if found_connection.open?
|
395
|
+
coalesce_connections(found_connection, connection, selector, from_pool)
|
396
|
+
else
|
397
|
+
found_connection.once(:open) do
|
398
|
+
coalesce_connections(found_connection, connection, selector, from_pool)
|
292
399
|
end
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def on_resolver_close(resolver, selector)
|
404
|
+
return if resolver.closed?
|
405
|
+
|
406
|
+
deselect_resolver(resolver, selector)
|
407
|
+
resolver.close unless resolver.closed?
|
408
|
+
end
|
409
|
+
|
410
|
+
def find_resolver_for(connection, selector)
|
411
|
+
resolver = selector.find_resolver(connection.options)
|
412
|
+
|
413
|
+
unless resolver
|
414
|
+
resolver = @pool.checkout_resolver(connection.options)
|
415
|
+
resolver.current_session = self
|
416
|
+
resolver.current_selector = selector
|
417
|
+
end
|
418
|
+
|
419
|
+
resolver
|
420
|
+
end
|
421
|
+
|
422
|
+
# coalesces +conn2+ into +conn1+. if +conn1+ was loaded from the connection pool
|
423
|
+
# (it is known via +from_pool+), then it adds its to the +selector+.
|
424
|
+
def coalesce_connections(conn1, conn2, selector, from_pool)
|
425
|
+
unless conn1.coalescable?(conn2)
|
426
|
+
select_connection(conn2, selector)
|
427
|
+
@pool.checkin_connection(conn1) if from_pool
|
428
|
+
return false
|
429
|
+
end
|
430
|
+
|
431
|
+
conn2.emit(:tcp_open, conn1)
|
432
|
+
conn1.merge(conn2)
|
433
|
+
conn2.coalesced_connection = conn1
|
434
|
+
select_connection(conn1, selector) if from_pool
|
435
|
+
deselect_connection(conn2, selector)
|
436
|
+
true
|
437
|
+
end
|
438
|
+
|
439
|
+
def get_current_selector
|
440
|
+
selector_store[self] || (yield if block_given?)
|
441
|
+
end
|
442
|
+
|
443
|
+
def set_current_selector(selector)
|
444
|
+
if selector
|
445
|
+
selector_store[self] = selector
|
446
|
+
else
|
447
|
+
selector_store.delete(self)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def selector_store
|
452
|
+
th_current = Thread.current
|
453
|
+
th_current.thread_variable_get(:httpx_persistent_selector_store) || begin
|
454
|
+
{}.compare_by_identity.tap do |store|
|
455
|
+
th_current.thread_variable_set(:httpx_persistent_selector_store, store)
|
299
456
|
end
|
300
457
|
end
|
301
458
|
end
|
@@ -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
|
@@ -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
data/lib/httpx/version.rb
CHANGED