httpx 1.4.0 → 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_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 +55 -83
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +11 -1
- data/lib/httpx/callbacks.rb +2 -2
- data/lib/httpx/connection/http2.rb +33 -18
- data/lib/httpx/connection.rb +115 -55
- data/lib/httpx/errors.rb +3 -4
- data/lib/httpx/io/ssl.rb +6 -3
- data/lib/httpx/loggable.rb +13 -6
- data/lib/httpx/plugins/callbacks.rb +1 -0
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/expect.rb +1 -1
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/internal_telemetry.rb +21 -1
- data/lib/httpx/plugins/retries.rb +2 -2
- data/lib/httpx/plugins/stream.rb +42 -18
- data/lib/httpx/request/body.rb +9 -14
- data/lib/httpx/request.rb +37 -3
- data/lib/httpx/resolver/https.rb +4 -2
- data/lib/httpx/resolver/native.rb +111 -55
- data/lib/httpx/resolver/resolver.rb +18 -11
- data/lib/httpx/resolver/system.rb +3 -5
- data/lib/httpx/response.rb +9 -4
- data/lib/httpx/selector.rb +33 -23
- data/lib/httpx/session.rb +20 -49
- data/lib/httpx/timers.rb +16 -1
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/multipart/encoder.rb +2 -1
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -1
- data/sig/callbacks.rbs +2 -2
- data/sig/connection/http2.rbs +4 -0
- data/sig/connection.rbs +19 -5
- data/sig/errors.rbs +3 -3
- data/sig/loggable.rbs +2 -2
- data/sig/plugins/stream.rbs +3 -0
- data/sig/pool.rbs +2 -0
- data/sig/request/body.rbs +0 -8
- data/sig/request.rbs +12 -0
- data/sig/resolver/native.rbs +6 -1
- data/sig/response.rbs +8 -3
- data/sig/selector.rbs +1 -0
- data/sig/session.rbs +2 -0
- data/sig/timers.rbs +15 -4
- data/sig/transcoder/body.rbs +1 -3
- data/sig/transcoder/json.rbs +1 -1
- data/sig/transcoder/multipart.rbs +1 -1
- data/sig/transcoder/utils/body_reader.rbs +1 -1
- data/sig/transcoder/utils/deflater.rbs +1 -2
- metadata +11 -9
- data/lib/httpx/session2.rb +0 -23
- data/lib/httpx/transcoder/utils/inflater.rb +0 -21
- data/sig/transcoder/utils/inflater.rbs +0 -12
data/lib/httpx/request.rb
CHANGED
@@ -11,6 +11,8 @@ module HTTPX
|
|
11
11
|
include Callbacks
|
12
12
|
using URIExtensions
|
13
13
|
|
14
|
+
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
15
|
+
|
14
16
|
# default value used for "user-agent" header, when not overridden.
|
15
17
|
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
16
18
|
|
@@ -43,9 +45,14 @@ module HTTPX
|
|
43
45
|
|
44
46
|
attr_writer :persistent
|
45
47
|
|
48
|
+
attr_reader :active_timeouts
|
49
|
+
|
46
50
|
# will be +true+ when request body has been completely flushed.
|
47
51
|
def_delegator :@body, :empty?
|
48
52
|
|
53
|
+
# closes the body
|
54
|
+
def_delegator :@body, :close
|
55
|
+
|
49
56
|
# initializes the instance with the given +verb+ (an upppercase String, ex. 'GEt'),
|
50
57
|
# an absolute or relative +uri+ (either as String or URI::HTTP object), the
|
51
58
|
# request +options+ (instance of HTTPX::Options) and an optional Hash of +params+.
|
@@ -92,23 +99,37 @@ module HTTPX
|
|
92
99
|
@uri = origin.merge("#{base_path}#{@uri}")
|
93
100
|
end
|
94
101
|
|
102
|
+
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
103
|
+
|
95
104
|
@state = :idle
|
96
105
|
@response = nil
|
97
106
|
@peer_address = nil
|
107
|
+
@ping = false
|
98
108
|
@persistent = @options.persistent
|
109
|
+
@active_timeouts = []
|
110
|
+
end
|
111
|
+
|
112
|
+
# whether request has been buffered with a ping
|
113
|
+
def ping?
|
114
|
+
@ping
|
115
|
+
end
|
116
|
+
|
117
|
+
# marks the request as having been buffered with a ping
|
118
|
+
def ping!
|
119
|
+
@ping = true
|
99
120
|
end
|
100
121
|
|
101
|
-
# the read timeout defined for this
|
122
|
+
# the read timeout defined for this request.
|
102
123
|
def read_timeout
|
103
124
|
@options.timeout[:read_timeout]
|
104
125
|
end
|
105
126
|
|
106
|
-
# the write timeout defined for this
|
127
|
+
# the write timeout defined for this request.
|
107
128
|
def write_timeout
|
108
129
|
@options.timeout[:write_timeout]
|
109
130
|
end
|
110
131
|
|
111
|
-
# the request timeout defined for this
|
132
|
+
# the request timeout defined for this request.
|
112
133
|
def request_timeout
|
113
134
|
@options.timeout[:request_timeout]
|
114
135
|
end
|
@@ -239,10 +260,13 @@ module HTTPX
|
|
239
260
|
case nextstate
|
240
261
|
when :idle
|
241
262
|
@body.rewind
|
263
|
+
@ping = false
|
242
264
|
@response = nil
|
243
265
|
@drainer = nil
|
266
|
+
@active_timeouts.clear
|
244
267
|
when :headers
|
245
268
|
return unless @state == :idle
|
269
|
+
|
246
270
|
when :body
|
247
271
|
return unless @state == :headers ||
|
248
272
|
@state == :expect
|
@@ -263,6 +287,7 @@ module HTTPX
|
|
263
287
|
return unless @state == :body
|
264
288
|
when :done
|
265
289
|
return if @state == :expect
|
290
|
+
|
266
291
|
end
|
267
292
|
@state = nextstate
|
268
293
|
emit(@state, self)
|
@@ -273,6 +298,15 @@ module HTTPX
|
|
273
298
|
def expects?
|
274
299
|
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
|
275
300
|
end
|
301
|
+
|
302
|
+
def set_timeout_callback(event, &callback)
|
303
|
+
clb = once(event, &callback)
|
304
|
+
|
305
|
+
# reset timeout callbacks when requests get rerouted to a different connection
|
306
|
+
once(:idle) do
|
307
|
+
callbacks(event).delete(clb)
|
308
|
+
end
|
309
|
+
end
|
276
310
|
end
|
277
311
|
end
|
278
312
|
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -82,7 +82,9 @@ module HTTPX
|
|
82
82
|
|
83
83
|
if hostname.nil?
|
84
84
|
hostname = connection.peer.host
|
85
|
-
log
|
85
|
+
log do
|
86
|
+
"resolver #{FAMILY_TYPES[@record_type]}: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
87
|
+
end if connection.peer.non_ascii_hostname
|
86
88
|
|
87
89
|
hostname = @resolver.generate_candidates(hostname).each do |name|
|
88
90
|
@queries[name.to_s] = connection
|
@@ -90,7 +92,7 @@ module HTTPX
|
|
90
92
|
else
|
91
93
|
@queries[hostname] = connection
|
92
94
|
end
|
93
|
-
log { "resolver
|
95
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
94
96
|
|
95
97
|
begin
|
96
98
|
request = build_request(hostname)
|
@@ -35,6 +35,7 @@ module HTTPX
|
|
35
35
|
@_timeouts = Array(@resolver_options[:timeouts])
|
36
36
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
37
37
|
@connections = []
|
38
|
+
@name = nil
|
38
39
|
@queries = {}
|
39
40
|
@read_buffer = "".b
|
40
41
|
@write_buffer = Buffer.new(@resolver_options[:packet_size])
|
@@ -58,19 +59,6 @@ module HTTPX
|
|
58
59
|
when :open
|
59
60
|
consume
|
60
61
|
end
|
61
|
-
nil
|
62
|
-
rescue Errno::EHOSTUNREACH => e
|
63
|
-
@ns_index += 1
|
64
|
-
nameserver = @nameserver
|
65
|
-
if nameserver && @ns_index < nameserver.size
|
66
|
-
log { "resolver: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
|
67
|
-
transition(:idle)
|
68
|
-
@timeouts.clear
|
69
|
-
else
|
70
|
-
handle_error(e)
|
71
|
-
end
|
72
|
-
rescue NativeResolveError => e
|
73
|
-
handle_error(e)
|
74
62
|
end
|
75
63
|
|
76
64
|
def interests
|
@@ -105,9 +93,7 @@ module HTTPX
|
|
105
93
|
@timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
|
106
94
|
end
|
107
95
|
|
108
|
-
def handle_socket_timeout(interval)
|
109
|
-
do_retry(interval)
|
110
|
-
end
|
96
|
+
def handle_socket_timeout(interval); end
|
111
97
|
|
112
98
|
private
|
113
99
|
|
@@ -120,54 +106,94 @@ module HTTPX
|
|
120
106
|
end
|
121
107
|
|
122
108
|
def consume
|
123
|
-
|
124
|
-
|
125
|
-
|
109
|
+
loop do
|
110
|
+
dread if calculate_interests == :r
|
111
|
+
|
112
|
+
break unless calculate_interests == :w
|
113
|
+
|
114
|
+
# do_retry
|
115
|
+
dwrite
|
116
|
+
|
117
|
+
break unless calculate_interests == :r
|
118
|
+
end
|
119
|
+
rescue Errno::EHOSTUNREACH => e
|
120
|
+
@ns_index += 1
|
121
|
+
nameserver = @nameserver
|
122
|
+
if nameserver && @ns_index < nameserver.size
|
123
|
+
log do
|
124
|
+
"resolver #{FAMILY_TYPES[@record_type]}: " \
|
125
|
+
"failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})"
|
126
|
+
end
|
127
|
+
transition(:idle)
|
128
|
+
@timeouts.clear
|
129
|
+
retry
|
130
|
+
else
|
131
|
+
handle_error(e)
|
132
|
+
emit(:close, self)
|
133
|
+
end
|
134
|
+
rescue NativeResolveError => e
|
135
|
+
handle_error(e)
|
136
|
+
close_or_resolve
|
137
|
+
retry unless closed?
|
126
138
|
end
|
127
139
|
|
128
|
-
def
|
129
|
-
|
140
|
+
def schedule_retry
|
141
|
+
h = @name
|
130
142
|
|
131
|
-
|
143
|
+
return unless h
|
132
144
|
|
133
|
-
|
145
|
+
connection = @queries[h]
|
134
146
|
|
135
|
-
|
147
|
+
timeouts = @timeouts[h]
|
148
|
+
timeout = timeouts.shift
|
136
149
|
|
137
|
-
|
138
|
-
|
139
|
-
timeout = (@timeouts[host][0] -= loop_time)
|
150
|
+
@timer = @current_selector.after(timeout) do
|
151
|
+
next unless @connections.include?(connection)
|
140
152
|
|
141
|
-
|
153
|
+
do_retry(h, connection, timeout)
|
154
|
+
end
|
155
|
+
end
|
142
156
|
|
143
|
-
|
157
|
+
def do_retry(h, connection, interval)
|
158
|
+
timeouts = @timeouts[h]
|
144
159
|
|
145
|
-
if
|
146
|
-
log
|
160
|
+
if !timeouts.empty?
|
161
|
+
log do
|
162
|
+
"resolver #{FAMILY_TYPES[@record_type]}: timeout after #{interval}s, retry (with #{timeouts.first}s) #{h}..."
|
163
|
+
end
|
147
164
|
# must downgrade to tcp AND retry on same host as last
|
148
165
|
downgrade_socket
|
149
166
|
resolve(connection, h)
|
150
167
|
elsif @ns_index + 1 < @nameserver.size
|
151
168
|
# try on the next nameserver
|
152
169
|
@ns_index += 1
|
153
|
-
log
|
170
|
+
log do
|
171
|
+
"resolver #{FAMILY_TYPES[@record_type]}: failed resolving #{h} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)"
|
172
|
+
end
|
154
173
|
transition(:idle)
|
155
174
|
@timeouts.clear
|
156
175
|
resolve(connection, h)
|
157
176
|
else
|
158
177
|
|
159
|
-
@timeouts.delete(
|
178
|
+
@timeouts.delete(h)
|
160
179
|
reset_hostname(h, reset_candidates: false)
|
161
180
|
|
162
|
-
|
181
|
+
unless @queries.empty?
|
182
|
+
resolve(connection)
|
183
|
+
return
|
184
|
+
end
|
163
185
|
|
164
186
|
@connections.delete(connection)
|
187
|
+
|
188
|
+
host = connection.peer.host
|
189
|
+
|
165
190
|
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
166
191
|
# resolve timeout, including from the previous retries.
|
167
|
-
ex = ResolveTimeoutError.new(
|
192
|
+
ex = ResolveTimeoutError.new(interval, "Timed out while resolving #{host}")
|
168
193
|
ex.set_backtrace(ex ? ex.backtrace : caller)
|
169
194
|
emit_resolve_error(connection, host, ex)
|
170
|
-
|
195
|
+
|
196
|
+
close_or_resolve
|
171
197
|
end
|
172
198
|
end
|
173
199
|
|
@@ -216,7 +242,7 @@ module HTTPX
|
|
216
242
|
parse(@read_buffer)
|
217
243
|
end
|
218
244
|
|
219
|
-
return if @state == :closed
|
245
|
+
return if @state == :closed || !@write_buffer.empty?
|
220
246
|
end
|
221
247
|
end
|
222
248
|
|
@@ -234,11 +260,15 @@ module HTTPX
|
|
234
260
|
|
235
261
|
return unless siz.positive?
|
236
262
|
|
263
|
+
schedule_retry if @write_buffer.empty?
|
264
|
+
|
237
265
|
return if @state == :closed
|
238
266
|
end
|
239
267
|
end
|
240
268
|
|
241
269
|
def parse(buffer)
|
270
|
+
@timer.cancel
|
271
|
+
|
242
272
|
code, result = Resolver.decode_dns_answer(buffer)
|
243
273
|
|
244
274
|
case code
|
@@ -249,15 +279,17 @@ module HTTPX
|
|
249
279
|
hostname, connection = @queries.first
|
250
280
|
reset_hostname(hostname, reset_candidates: false)
|
251
281
|
|
252
|
-
|
282
|
+
other_candidate, _ = @queries.find { |_, conn| conn == connection }
|
283
|
+
|
284
|
+
if other_candidate
|
285
|
+
resolve(connection, other_candidate)
|
286
|
+
else
|
253
287
|
@connections.delete(connection)
|
254
288
|
ex = NativeResolveError.new(connection, connection.peer.host, "name or service not known")
|
255
289
|
ex.set_backtrace(ex ? ex.backtrace : caller)
|
256
290
|
emit_resolve_error(connection, connection.peer.host, ex)
|
257
|
-
|
291
|
+
close_or_resolve
|
258
292
|
end
|
259
|
-
|
260
|
-
resolve
|
261
293
|
when :message_truncated
|
262
294
|
# TODO: what to do if it's already tcp??
|
263
295
|
return if @socket_type == :tcp
|
@@ -312,8 +344,10 @@ module HTTPX
|
|
312
344
|
connection = @queries.delete(name)
|
313
345
|
end
|
314
346
|
|
315
|
-
|
316
|
-
|
347
|
+
alias_addresses, addresses = addresses.partition { |addr| addr.key?("alias") }
|
348
|
+
|
349
|
+
if addresses.empty? && !alias_addresses.empty? # CNAME
|
350
|
+
hostname_alias = alias_addresses.first["alias"]
|
317
351
|
# clean up intermediate queries
|
318
352
|
@timeouts.delete(name) unless connection.peer.host == name
|
319
353
|
|
@@ -326,7 +360,7 @@ module HTTPX
|
|
326
360
|
transition(:idle)
|
327
361
|
transition(:open)
|
328
362
|
end
|
329
|
-
log { "resolver: ALIAS #{hostname_alias} for #{name}" }
|
363
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: ALIAS #{hostname_alias} for #{name}" }
|
330
364
|
resolve(connection, hostname_alias)
|
331
365
|
return
|
332
366
|
end
|
@@ -338,12 +372,14 @@ module HTTPX
|
|
338
372
|
catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
|
339
373
|
end
|
340
374
|
end
|
341
|
-
|
342
|
-
|
343
|
-
resolve
|
375
|
+
close_or_resolve
|
344
376
|
end
|
345
377
|
|
346
|
-
def resolve(connection =
|
378
|
+
def resolve(connection = nil, hostname = nil)
|
379
|
+
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
380
|
+
|
381
|
+
connection ||= @connections.find { |c| !@queries.value?(c) }
|
382
|
+
|
347
383
|
raise Error, "no URI to resolve" unless connection
|
348
384
|
|
349
385
|
return unless @write_buffer.empty?
|
@@ -352,7 +388,10 @@ module HTTPX
|
|
352
388
|
|
353
389
|
if hostname.nil?
|
354
390
|
hostname = connection.peer.host
|
355
|
-
log
|
391
|
+
log do
|
392
|
+
"resolver #{FAMILY_TYPES[@record_type]}: " \
|
393
|
+
"resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
394
|
+
end if connection.peer.non_ascii_hostname
|
356
395
|
|
357
396
|
hostname = generate_candidates(hostname).each do |name|
|
358
397
|
@queries[name] = connection
|
@@ -360,14 +399,17 @@ module HTTPX
|
|
360
399
|
else
|
361
400
|
@queries[hostname] = connection
|
362
401
|
end
|
363
|
-
|
402
|
+
|
403
|
+
@name = hostname
|
404
|
+
|
405
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
364
406
|
begin
|
365
407
|
@write_buffer << encode_dns_query(hostname)
|
366
408
|
rescue Resolv::DNS::EncodeError => e
|
367
409
|
reset_hostname(hostname, connection: connection)
|
368
410
|
@connections.delete(connection)
|
369
411
|
emit_resolve_error(connection, hostname, e)
|
370
|
-
|
412
|
+
close_or_resolve
|
371
413
|
end
|
372
414
|
end
|
373
415
|
|
@@ -397,10 +439,10 @@ module HTTPX
|
|
397
439
|
|
398
440
|
case @socket_type
|
399
441
|
when :udp
|
400
|
-
log { "resolver: server: udp://#{ip}:#{port}..." }
|
442
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: server: udp://#{ip}:#{port}..." }
|
401
443
|
UDP.new(ip, port, @options)
|
402
444
|
when :tcp
|
403
|
-
log { "resolver: server: tcp://#{ip}:#{port}..." }
|
445
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: server: tcp://#{ip}:#{port}..." }
|
404
446
|
origin = URI("tcp://#{ip}:#{port}")
|
405
447
|
TCP.new(origin, [ip], @options)
|
406
448
|
end
|
@@ -448,6 +490,7 @@ module HTTPX
|
|
448
490
|
# these errors may happen during TCP handshake
|
449
491
|
# treat them as resolve errors.
|
450
492
|
handle_error(e)
|
493
|
+
emit(:close, self)
|
451
494
|
end
|
452
495
|
|
453
496
|
def handle_error(error)
|
@@ -462,13 +505,15 @@ module HTTPX
|
|
462
505
|
@connections.delete(connection)
|
463
506
|
emit_resolve_error(connection, host, error)
|
464
507
|
end
|
508
|
+
|
509
|
+
while (connection = @connections.shift)
|
510
|
+
emit_resolve_error(connection, host, error)
|
511
|
+
end
|
465
512
|
end
|
466
|
-
emit(:close, self) if @connections.empty?
|
467
513
|
end
|
468
514
|
|
469
515
|
def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
|
470
516
|
@timeouts.delete(hostname)
|
471
|
-
@timeouts.delete(hostname)
|
472
517
|
|
473
518
|
return unless connection && reset_candidates
|
474
519
|
|
@@ -478,5 +523,16 @@ module HTTPX
|
|
478
523
|
# reset timeouts
|
479
524
|
@timeouts.delete_if { |h, _| candidates.include?(h) }
|
480
525
|
end
|
526
|
+
|
527
|
+
def close_or_resolve
|
528
|
+
# drop already closed connections
|
529
|
+
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
530
|
+
|
531
|
+
if (@connections - @queries.values).empty?
|
532
|
+
emit(:close, self)
|
533
|
+
else
|
534
|
+
resolve
|
535
|
+
end
|
536
|
+
end
|
481
537
|
end
|
482
538
|
end
|
@@ -72,17 +72,22 @@ module HTTPX
|
|
72
72
|
# double emission check, but allow early resolution to work
|
73
73
|
return if !early_resolve && connection.addresses && !addresses.intersect?(connection.addresses)
|
74
74
|
|
75
|
-
log
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
log do
|
76
|
+
"resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
|
77
|
+
"answer #{connection.peer.host}: #{addresses.inspect} (early resolve: #{early_resolve})"
|
78
|
+
end
|
79
|
+
|
80
|
+
if !early_resolve && # do not apply resolution delay for non-dns name resolution
|
81
|
+
@current_selector && # just in case...
|
82
|
+
family == Socket::AF_INET && # resolution delay only applies to IPv4
|
83
|
+
!connection.io && # connection already has addresses and initiated/ended handshake
|
84
|
+
connection.options.ip_families.size > 1 && # no need to delay if not supporting dual stack IP
|
85
|
+
addresses.first.to_s != connection.peer.host.to_s # connection URL host is already the IP (early resolve included perhaps?)
|
86
|
+
log { "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: applying resolution delay..." }
|
87
|
+
|
82
88
|
@current_selector.after(0.05) do
|
83
|
-
|
84
|
-
|
85
|
-
(connection.addresses && addresses.intersect?(connection.addresses))
|
89
|
+
# double emission check
|
90
|
+
unless connection.addresses && addresses.intersect?(connection.addresses)
|
86
91
|
emit_resolved_connection(connection, addresses, early_resolve)
|
87
92
|
end
|
88
93
|
end
|
@@ -97,6 +102,8 @@ module HTTPX
|
|
97
102
|
begin
|
98
103
|
connection.addresses = addresses
|
99
104
|
|
105
|
+
return if connection.state == :closed
|
106
|
+
|
100
107
|
emit(:resolve, connection)
|
101
108
|
rescue StandardError => e
|
102
109
|
if early_resolve
|
@@ -146,7 +153,7 @@ module HTTPX
|
|
146
153
|
end
|
147
154
|
|
148
155
|
def emit_connection_error(connection, error)
|
149
|
-
return connection.
|
156
|
+
return connection.handle_connect_error(error) if connection.connecting?
|
150
157
|
|
151
158
|
connection.emit(:error, error)
|
152
159
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
3
|
require "resolv"
|
5
4
|
|
6
5
|
module HTTPX
|
7
6
|
class Resolver::System < Resolver::Resolver
|
8
7
|
using URIExtensions
|
9
|
-
extend Forwardable
|
10
8
|
|
11
9
|
RESOLV_ERRORS = [Resolv::ResolvError,
|
12
10
|
Resolv::DNS::Requester::RequestError,
|
@@ -24,8 +22,6 @@ module HTTPX
|
|
24
22
|
|
25
23
|
attr_reader :state
|
26
24
|
|
27
|
-
def_delegator :@connections, :empty?
|
28
|
-
|
29
25
|
def initialize(options)
|
30
26
|
super(nil, options)
|
31
27
|
@resolver_options = @options.resolver_options
|
@@ -162,7 +158,9 @@ module HTTPX
|
|
162
158
|
|
163
159
|
hostname = connection.peer.host
|
164
160
|
scheme = connection.origin.scheme
|
165
|
-
log
|
161
|
+
log do
|
162
|
+
"resolver: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
163
|
+
end if connection.peer.non_ascii_hostname
|
166
164
|
|
167
165
|
transition(:open)
|
168
166
|
|
data/lib/httpx/response.rb
CHANGED
@@ -52,9 +52,6 @@ module HTTPX
|
|
52
52
|
# copies the response body to a different location.
|
53
53
|
def_delegator :@body, :copy_to
|
54
54
|
|
55
|
-
# closes the body.
|
56
|
-
def_delegator :@body, :close
|
57
|
-
|
58
55
|
# the corresponding request uri.
|
59
56
|
def_delegator :@request, :uri
|
60
57
|
|
@@ -74,6 +71,12 @@ module HTTPX
|
|
74
71
|
@content_type = nil
|
75
72
|
end
|
76
73
|
|
74
|
+
# closes the respective +@request+ and +@body+.
|
75
|
+
def close
|
76
|
+
@request.close
|
77
|
+
@body.close
|
78
|
+
end
|
79
|
+
|
77
80
|
# merges headers defined in +h+ into the response headers.
|
78
81
|
def merge_headers(h)
|
79
82
|
@headers = @headers.merge(h)
|
@@ -264,7 +267,7 @@ module HTTPX
|
|
264
267
|
|
265
268
|
# closes the error resources.
|
266
269
|
def close
|
267
|
-
@response.close if @response
|
270
|
+
@response.close if @response
|
268
271
|
end
|
269
272
|
|
270
273
|
# always true for error responses.
|
@@ -279,6 +282,8 @@ module HTTPX
|
|
279
282
|
|
280
283
|
# buffers lost chunks to error response
|
281
284
|
def <<(data)
|
285
|
+
return unless @response
|
286
|
+
|
282
287
|
@response << data
|
283
288
|
end
|
284
289
|
end
|
data/lib/httpx/selector.rb
CHANGED
@@ -19,6 +19,7 @@ module HTTPX
|
|
19
19
|
def initialize
|
20
20
|
@timers = Timers.new
|
21
21
|
@selectables = []
|
22
|
+
@is_timer_interval = false
|
22
23
|
end
|
23
24
|
|
24
25
|
def each(&blk)
|
@@ -43,7 +44,11 @@ module HTTPX
|
|
43
44
|
rescue StandardError => e
|
44
45
|
emit_error(e)
|
45
46
|
rescue Exception # rubocop:disable Lint/RescueException
|
46
|
-
each_connection
|
47
|
+
each_connection do |conn|
|
48
|
+
conn.force_reset
|
49
|
+
conn.disconnect
|
50
|
+
end
|
51
|
+
|
47
52
|
raise
|
48
53
|
end
|
49
54
|
|
@@ -92,10 +97,6 @@ module HTTPX
|
|
92
97
|
end
|
93
98
|
end
|
94
99
|
|
95
|
-
def empty?
|
96
|
-
@selectables.empty?
|
97
|
-
end
|
98
|
-
|
99
100
|
# deregisters +io+ from selectables.
|
100
101
|
def deregister(io)
|
101
102
|
@selectables.delete(io)
|
@@ -129,24 +130,22 @@ module HTTPX
|
|
129
130
|
# first, we group IOs based on interest type. On call to #interests however,
|
130
131
|
# things might already happen, and new IOs might be registered, so we might
|
131
132
|
# have to start all over again. We do this until we group all selectables
|
132
|
-
|
133
|
-
|
134
|
-
interests = io.interests
|
133
|
+
@selectables.delete_if do |io|
|
134
|
+
interests = io.interests
|
135
135
|
|
136
|
-
|
137
|
-
|
136
|
+
(r ||= []) << io if READABLE.include?(interests)
|
137
|
+
(w ||= []) << io if WRITABLE.include?(interests)
|
138
138
|
|
139
|
-
|
140
|
-
|
139
|
+
io.state == :closed
|
140
|
+
end
|
141
141
|
|
142
|
-
|
142
|
+
# TODO: what to do if there are no selectables?
|
143
143
|
|
144
|
-
|
144
|
+
readers, writers = IO.select(r, w, nil, interval)
|
145
145
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
146
|
+
if readers.nil? && writers.nil? && interval
|
147
|
+
[*r, *w].each { |io| io.handle_socket_timeout(interval) }
|
148
|
+
return
|
150
149
|
end
|
151
150
|
|
152
151
|
if writers
|
@@ -178,7 +177,7 @@ module HTTPX
|
|
178
177
|
end
|
179
178
|
|
180
179
|
unless result || interval.nil?
|
181
|
-
io.handle_socket_timeout(interval)
|
180
|
+
io.handle_socket_timeout(interval) unless @is_timer_interval
|
182
181
|
return
|
183
182
|
end
|
184
183
|
# raise TimeoutError.new(interval, "timed out while waiting on select")
|
@@ -190,10 +189,21 @@ module HTTPX
|
|
190
189
|
end
|
191
190
|
|
192
191
|
def next_timeout
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
192
|
+
@is_timer_interval = false
|
193
|
+
|
194
|
+
timer_interval = @timers.wait_interval
|
195
|
+
|
196
|
+
connection_interval = @selectables.filter_map(&:timeout).min
|
197
|
+
|
198
|
+
return connection_interval unless timer_interval
|
199
|
+
|
200
|
+
if connection_interval.nil? || timer_interval <= connection_interval
|
201
|
+
@is_timer_interval = true
|
202
|
+
|
203
|
+
return timer_interval
|
204
|
+
end
|
205
|
+
|
206
|
+
connection_interval
|
197
207
|
end
|
198
208
|
|
199
209
|
def emit_error(e)
|