httpx 0.7.0 → 0.8.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/lib/httpx/altsvc.rb +18 -2
- data/lib/httpx/chainable.rb +5 -3
- data/lib/httpx/connection.rb +168 -66
- data/lib/httpx/connection/http1.rb +28 -10
- data/lib/httpx/connection/http2.rb +55 -32
- data/lib/httpx/extensions.rb +1 -1
- data/lib/httpx/io/ssl.rb +11 -3
- data/lib/httpx/io/tcp.rb +12 -2
- data/lib/httpx/loggable.rb +6 -6
- data/lib/httpx/options.rb +17 -14
- data/lib/httpx/plugins/compression.rb +5 -1
- data/lib/httpx/plugins/compression/gzip.rb +22 -12
- data/lib/httpx/plugins/digest_authentication.rb +2 -0
- data/lib/httpx/plugins/proxy.rb +16 -12
- data/lib/httpx/plugins/proxy/http.rb +7 -2
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +5 -1
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/retries.rb +11 -5
- data/lib/httpx/pool.rb +7 -12
- data/lib/httpx/registry.rb +2 -1
- data/lib/httpx/request.rb +7 -0
- data/lib/httpx/resolver/https.rb +15 -3
- data/lib/httpx/resolver/native.rb +22 -32
- data/lib/httpx/resolver/options.rb +2 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/response.rb +15 -1
- data/lib/httpx/selector.rb +96 -95
- data/lib/httpx/session.rb +1 -1
- data/lib/httpx/timeout.rb +7 -1
- data/lib/httpx/version.rb +1 -1
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ac832ee93155b950d27dde3d7295fae2b817a8b2c55c881ec07f73e97736c59
|
4
|
+
data.tar.gz: 28251067c648230eed4435b9df7cf75bfc1c552162f37928a8ee6c5b485d6c3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7b9f1419af3bb823ff58de80dca6a933bf25a756e27ff35f76c5ad7948ad1aab1c1f63f8eaf20bc9fc05bb0b2ed4932fd5ff1e33bcd7a8cda061bee2c54e4ac
|
7
|
+
data.tar.gz: f410653e0e1646f36eeaf90746bfae17d8c344abdf44eb92df2a992897d24843d16e370ec54f6441bedfe779981d17e4b9c5316f73115adc77e0639cb8dba53f
|
data/lib/httpx/altsvc.rb
CHANGED
@@ -42,7 +42,23 @@ module HTTPX
|
|
42
42
|
|
43
43
|
origin = request.origin
|
44
44
|
host = request.uri.host
|
45
|
-
|
45
|
+
|
46
|
+
altsvc = response.headers["alt-svc"]
|
47
|
+
|
48
|
+
# https://tools.ietf.org/html/rfc7838#section-3
|
49
|
+
# A field value containing the special value "clear" indicates that the
|
50
|
+
# origin requests all alternatives for that origin to be invalidated
|
51
|
+
# (including those specified in the same response, in case of an
|
52
|
+
# invalid reply containing both "clear" and alternative services).
|
53
|
+
if altsvc == "clear"
|
54
|
+
@altsvc_mutex.synchronize do
|
55
|
+
@altsvcs[origin].clear
|
56
|
+
end
|
57
|
+
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
parse(altsvc) do |alt_origin, alt_params|
|
46
62
|
alt_origin.host ||= host
|
47
63
|
yield(alt_origin, origin, alt_params)
|
48
64
|
end
|
@@ -73,7 +89,7 @@ module HTTPX
|
|
73
89
|
alt_proto, alt_origin = alt_origin.split("=")
|
74
90
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
75
91
|
if alt_origin.start_with?(":")
|
76
|
-
alt_origin = "dummy#{alt_origin}"
|
92
|
+
alt_origin = "#{alt_proto}://dummy#{alt_origin}"
|
77
93
|
uri = URI.parse(alt_origin)
|
78
94
|
uri.host = nil
|
79
95
|
uri
|
data/lib/httpx/chainable.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
module HTTPX
|
4
4
|
module Chainable
|
5
5
|
%i[head get post put delete trace options connect patch].each do |meth|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
7
|
+
def #{meth}(*uri, **options)
|
8
|
+
request(:#{meth}, uri, **options)
|
9
|
+
end
|
10
|
+
MOD
|
9
11
|
end
|
10
12
|
|
11
13
|
def request(verb, uri, **options)
|
data/lib/httpx/connection.rb
CHANGED
@@ -67,6 +67,10 @@ module HTTPX
|
|
67
67
|
else
|
68
68
|
transition(:idle)
|
69
69
|
end
|
70
|
+
|
71
|
+
@inflight = 0
|
72
|
+
@keep_alive_timeout = options.timeout.keep_alive_timeout
|
73
|
+
@keep_alive_timer = nil
|
70
74
|
end
|
71
75
|
|
72
76
|
# this is a semi-private method, to be used by the resolver
|
@@ -84,6 +88,8 @@ module HTTPX
|
|
84
88
|
|
85
89
|
return false if exhausted?
|
86
90
|
|
91
|
+
return false if @keep_alive_timer && @keep_alive_timer.fires_in.negative?
|
92
|
+
|
87
93
|
(
|
88
94
|
(
|
89
95
|
@origins.include?(uri.origin) &&
|
@@ -101,6 +107,8 @@ module HTTPX
|
|
101
107
|
|
102
108
|
return false if exhausted?
|
103
109
|
|
110
|
+
return false if @keep_alive_timer && @keep_alive_timer.fires_in.negative?
|
111
|
+
|
104
112
|
!(@io.addresses & connection.addresses).empty? && @options == connection.options
|
105
113
|
end
|
106
114
|
|
@@ -140,7 +148,10 @@ module HTTPX
|
|
140
148
|
|
141
149
|
def purge_pending
|
142
150
|
pendings = []
|
143
|
-
|
151
|
+
if @parser
|
152
|
+
@inflight -= @parser.pending.size
|
153
|
+
pendings << @parser.pending
|
154
|
+
end
|
144
155
|
pendings << @pending
|
145
156
|
pendings.each do |pending|
|
146
157
|
pending.reject! do |request|
|
@@ -168,22 +179,45 @@ module HTTPX
|
|
168
179
|
end
|
169
180
|
|
170
181
|
def interests
|
171
|
-
|
182
|
+
# connecting
|
183
|
+
if connecting?
|
184
|
+
connect
|
172
185
|
|
173
|
-
|
186
|
+
return @io.interests if connecting?
|
187
|
+
end
|
188
|
+
|
189
|
+
# if the write buffer is full, we drain it
|
190
|
+
return :w if @write_buffer.full?
|
191
|
+
|
192
|
+
return @parser.interests if @parser
|
193
|
+
|
194
|
+
nil
|
174
195
|
end
|
175
196
|
|
176
197
|
def to_io
|
198
|
+
@io.to_io
|
199
|
+
end
|
200
|
+
|
201
|
+
def call
|
177
202
|
case @state
|
178
|
-
when :
|
179
|
-
|
203
|
+
when :closed
|
204
|
+
return
|
205
|
+
when :closing
|
206
|
+
consume
|
207
|
+
transition(:closed)
|
208
|
+
emit(:close)
|
209
|
+
when :open
|
210
|
+
consume
|
180
211
|
end
|
181
|
-
|
212
|
+
nil
|
182
213
|
end
|
183
214
|
|
184
215
|
def close
|
185
216
|
@parser.close if @parser
|
186
|
-
|
217
|
+
return unless @keep_alive_timer
|
218
|
+
|
219
|
+
@keep_alive_timer.cancel
|
220
|
+
remove_instance_variable(:@keep_alive_timer)
|
187
221
|
end
|
188
222
|
|
189
223
|
def reset
|
@@ -195,26 +229,14 @@ module HTTPX
|
|
195
229
|
def send(request)
|
196
230
|
if @parser && !@write_buffer.full?
|
197
231
|
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
232
|
+
@inflight += 1
|
233
|
+
@keep_alive_timer.pause if @keep_alive_timer
|
198
234
|
parser.send(request)
|
199
235
|
else
|
200
236
|
@pending << request
|
201
237
|
end
|
202
238
|
end
|
203
239
|
|
204
|
-
def call
|
205
|
-
case @state
|
206
|
-
when :closed
|
207
|
-
return
|
208
|
-
when :closing
|
209
|
-
dwrite
|
210
|
-
transition(:closed)
|
211
|
-
emit(:close)
|
212
|
-
when :open
|
213
|
-
consume
|
214
|
-
end
|
215
|
-
nil
|
216
|
-
end
|
217
|
-
|
218
240
|
def timeout
|
219
241
|
return @timeout if defined?(@timeout)
|
220
242
|
|
@@ -225,54 +247,94 @@ module HTTPX
|
|
225
247
|
|
226
248
|
private
|
227
249
|
|
250
|
+
def connect
|
251
|
+
transition(:open)
|
252
|
+
end
|
253
|
+
|
228
254
|
def exhausted?
|
229
255
|
@parser && parser.exhausted?
|
230
256
|
end
|
231
257
|
|
232
258
|
def consume
|
233
259
|
catch(:called) do
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
260
|
+
loop do
|
261
|
+
parser.consume
|
262
|
+
|
263
|
+
# we exit if there's no more data to process
|
264
|
+
if @pending.size.zero? && @inflight.zero?
|
265
|
+
log(level: 3) { "NO MORE REQUESTS..." }
|
266
|
+
return
|
267
|
+
end
|
268
|
+
|
269
|
+
@timeout = @current_timeout
|
270
|
+
|
271
|
+
read_drained = false
|
272
|
+
write_drained = nil
|
273
|
+
|
274
|
+
# dread
|
275
|
+
loop do
|
276
|
+
siz = @io.read(@window_size, @read_buffer)
|
277
|
+
unless siz
|
278
|
+
ex = EOFError.new("descriptor closed")
|
279
|
+
ex.set_backtrace(caller)
|
280
|
+
on_error(ex)
|
281
|
+
return
|
282
|
+
end
|
283
|
+
|
284
|
+
log { "READ: #{siz} bytes..." }
|
285
|
+
|
286
|
+
if siz.zero?
|
287
|
+
read_drained = @read_buffer.empty?
|
288
|
+
break
|
289
|
+
end
|
290
|
+
|
291
|
+
parser << @read_buffer.to_s
|
292
|
+
|
293
|
+
break if @state == :closing || @state == :closed
|
294
|
+
|
295
|
+
# for HTTP/2, we just want to write goaway frame
|
296
|
+
end unless @state == :closing
|
297
|
+
|
298
|
+
# dwrite
|
299
|
+
loop do
|
300
|
+
if @write_buffer.empty?
|
301
|
+
# we only mark as drained on the first loop
|
302
|
+
write_drained = write_drained.nil? && @inflight.positive?
|
303
|
+
break
|
304
|
+
end
|
305
|
+
|
306
|
+
siz = @io.write(@write_buffer)
|
307
|
+
unless siz
|
308
|
+
ex = EOFError.new("descriptor closed")
|
309
|
+
ex.set_backtrace(caller)
|
310
|
+
on_error(ex)
|
311
|
+
return
|
312
|
+
end
|
313
|
+
log { "WRITE: #{siz} bytes..." }
|
314
|
+
|
315
|
+
if siz.zero?
|
316
|
+
write_drained = !@write_buffer.empty?
|
317
|
+
break
|
318
|
+
end
|
319
|
+
|
320
|
+
break if @state == :closing || @state == :closed
|
321
|
+
|
322
|
+
write_drained = false
|
323
|
+
end
|
324
|
+
|
325
|
+
# return if socket is drained
|
326
|
+
if read_drained && write_drained
|
327
|
+
log(level: 3) { "WAITING FOR EVENTS..." }
|
328
|
+
return
|
329
|
+
end
|
267
330
|
end
|
268
|
-
log { "WRITE: #{siz} bytes..." }
|
269
|
-
return if siz.zero?
|
270
|
-
return if @state == :closing || @state == :closed
|
271
331
|
end
|
272
332
|
end
|
273
333
|
|
274
334
|
def send_pending
|
275
335
|
while !@write_buffer.full? && (request = @pending.shift)
|
336
|
+
@inflight += 1
|
337
|
+
@keep_alive_timer.pause if @keep_alive_timer
|
276
338
|
parser.send(request)
|
277
339
|
end
|
278
340
|
end
|
@@ -292,6 +354,7 @@ module HTTPX
|
|
292
354
|
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
293
355
|
emit(:altsvc, alt_origin, origin, alt_params)
|
294
356
|
end
|
357
|
+
handle_response
|
295
358
|
request.emit(:response, response)
|
296
359
|
end
|
297
360
|
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
@@ -307,14 +370,21 @@ module HTTPX
|
|
307
370
|
parser.on(:origin) do |origin|
|
308
371
|
@origins << origin
|
309
372
|
end
|
310
|
-
parser.on(:close) do
|
373
|
+
parser.on(:close) do |force|
|
311
374
|
transition(:closing)
|
375
|
+
if force
|
376
|
+
transition(:closed)
|
377
|
+
emit(:close)
|
378
|
+
end
|
312
379
|
end
|
313
380
|
parser.on(:reset) do
|
314
|
-
|
315
|
-
|
381
|
+
if parser.empty?
|
382
|
+
reset
|
383
|
+
else
|
384
|
+
transition(:closing)
|
316
385
|
transition(:closed)
|
317
386
|
emit(:reset)
|
387
|
+
@parser.reset if @parser
|
318
388
|
transition(:idle)
|
319
389
|
transition(:open)
|
320
390
|
end
|
@@ -335,6 +405,9 @@ module HTTPX
|
|
335
405
|
|
336
406
|
def transition(nextstate)
|
337
407
|
case nextstate
|
408
|
+
when :idle
|
409
|
+
@timeout = @current_timeout = @options.timeout.connect_timeout
|
410
|
+
|
338
411
|
when :open
|
339
412
|
return if @state == :closed
|
340
413
|
|
@@ -344,6 +417,8 @@ module HTTPX
|
|
344
417
|
return unless @io.connected?
|
345
418
|
|
346
419
|
send_pending
|
420
|
+
|
421
|
+
@timeout = @current_timeout = @options.timeout.operation_timeout
|
347
422
|
emit(:open)
|
348
423
|
when :closing
|
349
424
|
return unless @state == :open
|
@@ -358,6 +433,11 @@ module HTTPX
|
|
358
433
|
|
359
434
|
@io.close
|
360
435
|
@read_buffer.clear
|
436
|
+
if @keep_alive_timer
|
437
|
+
@keep_alive_timer.cancel
|
438
|
+
remove_instance_variable(:@keep_alive_timer)
|
439
|
+
end
|
440
|
+
|
361
441
|
remove_instance_variable(:@timeout) if defined?(@timeout)
|
362
442
|
when :already_open
|
363
443
|
nextstate = :open
|
@@ -379,21 +459,43 @@ module HTTPX
|
|
379
459
|
emit(:close)
|
380
460
|
end
|
381
461
|
|
382
|
-
def
|
383
|
-
|
384
|
-
|
462
|
+
def handle_response
|
463
|
+
@inflight -= 1
|
464
|
+
return unless @inflight.zero?
|
465
|
+
|
466
|
+
if @keep_alive_timer
|
467
|
+
@keep_alive_timer.resume
|
468
|
+
@keep_alive_timer.reset
|
469
|
+
else
|
470
|
+
@keep_alive_timer = @timers.after(@keep_alive_timeout) do
|
471
|
+
unless @inflight.zero?
|
472
|
+
log { "(#{object_id})) keep alive timeout expired, closing..." }
|
473
|
+
reset
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
385
477
|
end
|
386
478
|
|
387
|
-
def
|
479
|
+
def on_error(error)
|
388
480
|
if error.instance_of?(TimeoutError)
|
389
481
|
if @timeout
|
390
482
|
@timeout -= error.timeout
|
391
483
|
return unless @timeout <= 0
|
392
484
|
end
|
393
485
|
|
394
|
-
|
486
|
+
if @total_timeout && @total_timeout.fires_in.negative?
|
487
|
+
ex = TotalTimeoutError.new(@total_timeout.interval, "Timed out after #{@total_timeout.interval} seconds")
|
488
|
+
ex.set_backtrace(error.backtrace)
|
489
|
+
error = ex
|
490
|
+
elsif connecting?
|
491
|
+
error = error.to_connection_error
|
492
|
+
end
|
395
493
|
end
|
494
|
+
handle_error(error)
|
495
|
+
reset
|
496
|
+
end
|
396
497
|
|
498
|
+
def handle_error(error)
|
397
499
|
parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
|
398
500
|
while (request = @pending.shift)
|
399
501
|
request.emit(:response, ErrorResponse.new(request, error, @options))
|
@@ -408,8 +510,8 @@ module HTTPX
|
|
408
510
|
@total_timeout ||= @timers.after(total) do
|
409
511
|
ex = TotalTimeoutError.new(total, "Timed out after #{total} seconds")
|
410
512
|
ex.set_backtrace(caller)
|
411
|
-
@parser.close if @parser
|
412
513
|
on_error(ex)
|
514
|
+
@parser.close if @parser
|
413
515
|
end
|
414
516
|
end
|
415
517
|
end
|
@@ -23,6 +23,19 @@ module HTTPX
|
|
23
23
|
@requests = []
|
24
24
|
end
|
25
25
|
|
26
|
+
def interests
|
27
|
+
# this means we're processing incoming response already
|
28
|
+
return :r if @request
|
29
|
+
|
30
|
+
return if @requests.empty?
|
31
|
+
|
32
|
+
request = @requests.first
|
33
|
+
|
34
|
+
return :w if request.interests == :w || !@buffer.empty?
|
35
|
+
|
36
|
+
:r
|
37
|
+
end
|
38
|
+
|
26
39
|
def reset
|
27
40
|
@max_requests = @options.max_requests || MAX_REQUESTS
|
28
41
|
@parser.reset!
|
@@ -30,7 +43,7 @@ module HTTPX
|
|
30
43
|
|
31
44
|
def close
|
32
45
|
reset
|
33
|
-
emit(:close)
|
46
|
+
emit(:close, true)
|
34
47
|
end
|
35
48
|
|
36
49
|
def exhausted?
|
@@ -53,16 +66,18 @@ module HTTPX
|
|
53
66
|
return
|
54
67
|
end
|
55
68
|
|
56
|
-
|
57
|
-
@requests << request
|
58
|
-
@pipelining = true if @requests.size > 1
|
59
|
-
end
|
69
|
+
return if @requests.include?(request)
|
60
70
|
|
61
|
-
|
71
|
+
@requests << request
|
72
|
+
@pipelining = true if @requests.size > 1
|
62
73
|
end
|
63
74
|
|
64
75
|
def consume
|
65
|
-
@requests.
|
76
|
+
requests_limit = [@max_concurrent_requests, @max_requests, @requests.size].min
|
77
|
+
@requests.each_with_index do |request, idx|
|
78
|
+
break if idx >= requests_limit
|
79
|
+
next if request.state == :done
|
80
|
+
|
66
81
|
handle(request)
|
67
82
|
end
|
68
83
|
end
|
@@ -121,7 +136,7 @@ module HTTPX
|
|
121
136
|
|
122
137
|
def dispatch
|
123
138
|
if @request.expects?
|
124
|
-
reset
|
139
|
+
@parser.reset!
|
125
140
|
return handle(@request)
|
126
141
|
end
|
127
142
|
|
@@ -136,10 +151,10 @@ module HTTPX
|
|
136
151
|
throw(:called)
|
137
152
|
end
|
138
153
|
|
139
|
-
reset
|
154
|
+
@parser.reset!
|
140
155
|
@max_requests -= 1
|
141
|
-
send(@pending.shift) unless @pending.empty?
|
142
156
|
manage_connection(response)
|
157
|
+
send(@pending.shift) unless @pending.empty?
|
143
158
|
end
|
144
159
|
|
145
160
|
def handle_error(ex)
|
@@ -149,6 +164,9 @@ module HTTPX
|
|
149
164
|
@requests.each do |request|
|
150
165
|
emit(:error, request, ex)
|
151
166
|
end
|
167
|
+
@pending.each do |request|
|
168
|
+
emit(:error, request, ex)
|
169
|
+
end
|
152
170
|
end
|
153
171
|
end
|
154
172
|
|