httpx 1.4.1 → 1.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8a0ae955506767cc7b2f0a8134dd920bc937f88aeba2f72f691489e4d2199ab
4
- data.tar.gz: 96503529f27fddf76b41250f3a7a0686c93d9d6c1091255275ed1d5b2beada11
3
+ metadata.gz: 36f5b3d4da61a1a6c86602205a6eda217f51b40411865589587a09553eb263cb
4
+ data.tar.gz: 9b705a6b8bc7ebf1ec2e308ae6b49bfcc3ede818bbca0dcf4b2fb82dde1cace6
5
5
  SHA512:
6
- metadata.gz: d16170323b9f5d016f496e848be6b6fb50046402ca644c2a016d05fe1af4a5b045a400b31530c8fa38f85a61351125062edce01bd04c975bc45c3205545aef71
7
- data.tar.gz: a800e8814449fcf2d3149aadc5f335f458f7dc7fa537d7c676d8460b6bfcfd1a9cc0a96f3350b9492846fea563fca15605f228ef539b39ef36985df05431d193
6
+ metadata.gz: 72759cee17931e45580673c119fffd7339afdfe149f6a4a14afa410d11c47b0d84146a70660b5ca8ff63b1d0b6c1d16a85008416348cd48cddd3f9a9c94f3c10
7
+ data.tar.gz: 1233281adc13e03e5b754ef6d5d830e84335f5f8b3cc90dea33e27c9f5751f2e3f7d20e1d6064b9273678612a3e4505d8d54b3038b85855bc25a72c6d73d914c
data/README.md CHANGED
@@ -157,7 +157,6 @@ All Rubies greater or equal to 2.7, and always latest JRuby and Truffleruby.
157
157
 
158
158
  * Discuss your contribution in an issue
159
159
  * Fork it
160
- * Make your changes, add some tests
161
- * Ensure all tests pass (`docker-compose -f docker-compose.yml -f docker-compose-ruby-{RUBY_VERSION}.yml run httpx bundle exec rake test`)
160
+ * Make your changes, add some tests (follow the instructions from [here](test/README.md))
162
161
  * Open a Merge Request (that's Pull Request in Github-ish)
163
162
  * Wait for feedback
@@ -0,0 +1,20 @@
1
+ # 1.4.2
2
+
3
+ ## Bugfixes
4
+
5
+ * faraday: use default reason when none is matched by Net::HTTP::STATUS_CODES
6
+ * native resolver: keep sending DNS queries if the socket is available, to avoid busy loops on select
7
+ * native resolver fixes for Happy Eyeballs v2
8
+ * do not apply resolution delay if the IPv4 IP was not resolved via DNS
9
+ * ignore ALIAS if DNS response carries IP answers
10
+ * do not try to query for names already awaiting answer from the resolver
11
+ * make sure all types of errors are propagated to connections
12
+ * make sure next candidate is picked up if receiving NX_DOMAIN_NOT_FOUND error from resolver
13
+ * raise error happening before any request is flushed to respective connections (avoids loop on non-actionable selector termination).
14
+ * fix "NoMethodError: undefined method `after' for nil:NilClass", happening for requests flushed into persistent connections which errored, and were retried in a different connection before triggering the timeout callbacks from the previously-closed connection.
15
+
16
+
17
+ ## Chore
18
+
19
+ * Refactor of timers to allow for explicit and more performant single timer interval cancellation.
20
+ * default log message restructured to include info about process, thread and caller.
@@ -0,0 +1,11 @@
1
+ # 1.4.3
2
+
3
+ ## Bugfixes
4
+
5
+ * `webmock` adapter: reassign headers to signature after callbacks are called (these may change the headers before virtual send).
6
+ * do not close request (and its body) right after sending, instead only on response close
7
+ * prevents retries from failing under the `:retries` plugin
8
+ * fixes issue when using `faraday-multipart` request bodies
9
+ * retry request with HTTP/1 when receiving an HTTP/2 GOAWAY frame with `HTTP_1_1_REQUIRED` error code.
10
+ * fix wrong method call on HTTP/2 PING frame with unrecognized code.
11
+ * fix EOFError issues on connection termination for long running connections which may have already been terminated by peer and were wrongly trying to complete the HTTP/2 termination handshake.
@@ -149,7 +149,7 @@ module Faraday
149
149
 
150
150
  module ResponseMethods
151
151
  def reason
152
- Net::HTTP::STATUS_CODES.fetch(@status)
152
+ Net::HTTP::STATUS_CODES.fetch(@status, "Non-Standard status code")
153
153
  end
154
154
  end
155
155
  end
@@ -122,6 +122,8 @@ module WebMock
122
122
  request.transition(:done)
123
123
  request.response = response
124
124
  request.emit(:response, response)
125
+ request_signature.headers = request.headers.to_h
126
+
125
127
  response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
126
128
  elsif WebMock.net_connect_allowed?(request_signature.uri)
127
129
  if WebMock::CallbackRegistry.any_callbacks?
@@ -4,7 +4,7 @@ module HTTPX
4
4
  module Callbacks
5
5
  def on(type, &action)
6
6
  callbacks(type) << action
7
- self
7
+ action
8
8
  end
9
9
 
10
10
  def once(type, &block)
@@ -12,10 +12,10 @@ module HTTPX
12
12
  block.call(*args, &callback)
13
13
  :delete
14
14
  end
15
- self
16
15
  end
17
16
 
18
17
  def emit(type, *args)
18
+ log { "emit #{type.inspect} callbacks" } if respond_to?(:log)
19
19
  callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
20
20
  end
21
21
 
@@ -16,6 +16,12 @@ module HTTPX
16
16
  end
17
17
  end
18
18
 
19
+ class PingError < Error
20
+ def initialize
21
+ super(0, :ping_error)
22
+ end
23
+ end
24
+
19
25
  class GoawayError < Error
20
26
  def initialize
21
27
  super(0, :no_error)
@@ -125,7 +131,7 @@ module HTTPX
125
131
  end
126
132
 
127
133
  def handle_error(ex, request = nil)
128
- if ex.instance_of?(TimeoutError) && !@handshake_completed && @connection.state != :closed
134
+ if ex.is_a?(OperationTimeoutError) && !@handshake_completed && @connection.state != :closed
129
135
  @connection.goaway(:settings_timeout, "closing due to settings timeout")
130
136
  emit(:close_handshake)
131
137
  settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
@@ -311,17 +317,20 @@ module HTTPX
311
317
  @streams.delete(request)
312
318
 
313
319
  if error
314
- ex = Error.new(stream.id, error)
315
- ex.set_backtrace(caller)
316
- response = ErrorResponse.new(request, ex)
317
- request.response = response
318
- emit(:response, request, response)
320
+ case error
321
+ when :http_1_1_required
322
+ emit(:error, request, error)
323
+ else
324
+ ex = Error.new(stream.id, error)
325
+ ex.set_backtrace(caller)
326
+ response = ErrorResponse.new(request, ex)
327
+ request.response = response
328
+ emit(:response, request, response)
329
+ end
319
330
  else
320
331
  response = request.response
321
332
  if response && response.is_a?(Response) && response.status == 421
322
- ex = MisdirectedRequestError.new(response)
323
- ex.set_backtrace(caller)
324
- emit(:error, request, ex)
333
+ emit(:error, request, :http_1_1_required)
325
334
  else
326
335
  emit(:response, request, response)
327
336
  end
@@ -352,7 +361,12 @@ module HTTPX
352
361
  is_connection_closed = @connection.state == :closed
353
362
  if error
354
363
  @buffer.clear if is_connection_closed
355
- if error == :no_error
364
+ case error
365
+ when :http_1_1_required
366
+ while (request = @pending.shift)
367
+ emit(:error, request, error)
368
+ end
369
+ when :no_error
356
370
  ex = GoawayError.new
357
371
  @pending.unshift(*@streams.keys)
358
372
  @drains.clear
@@ -360,8 +374,11 @@ module HTTPX
360
374
  else
361
375
  ex = Error.new(0, error)
362
376
  end
363
- ex.set_backtrace(caller)
364
- handle_error(ex)
377
+
378
+ if ex
379
+ ex.set_backtrace(caller)
380
+ handle_error(ex)
381
+ end
365
382
  end
366
383
  return unless is_connection_closed && @streams.empty?
367
384
 
@@ -403,11 +420,9 @@ module HTTPX
403
420
  end
404
421
 
405
422
  def on_pong(ping)
406
- if @pings.delete(ping.to_s)
407
- emit(:pong)
408
- else
409
- close(:protocol_error, "ping payload did not match")
410
- end
423
+ raise PingError unless @pings.delete(ping.to_s)
424
+
425
+ emit(:pong)
411
426
  end
412
427
  end
413
428
  end
@@ -101,8 +101,6 @@ module HTTPX
101
101
  @inflight = 0
102
102
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
103
103
 
104
- @intervals = []
105
-
106
104
  self.addresses = @options.addresses if @options.addresses
107
105
  end
108
106
 
@@ -337,15 +335,7 @@ module HTTPX
337
335
  end
338
336
 
339
337
  def handle_socket_timeout(interval)
340
- @intervals.delete_if(&:elapsed?)
341
-
342
- unless @intervals.empty?
343
- # remove the intervals which will elapse
344
-
345
- return
346
- end
347
-
348
- error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
338
+ error = OperationTimeoutError.new(interval, "timed out while waiting on select")
349
339
  error.set_backtrace(caller)
350
340
  on_error(error)
351
341
  end
@@ -379,18 +369,20 @@ module HTTPX
379
369
  force_reset(true)
380
370
  end
381
371
 
382
- private
383
-
384
- def connect
385
- transition(:open)
386
- end
387
-
388
372
  def disconnect
373
+ return unless @current_session && @current_selector
374
+
389
375
  emit(:close)
390
376
  @current_session = nil
391
377
  @current_selector = nil
392
378
  end
393
379
 
380
+ private
381
+
382
+ def connect
383
+ transition(:open)
384
+ end
385
+
394
386
  def consume
395
387
  return unless @io
396
388
 
@@ -431,6 +423,8 @@ module HTTPX
431
423
  siz = @io.read(@window_size, @read_buffer)
432
424
  log(level: 3, color: :cyan) { "IO READ: #{siz} bytes... (wsize: #{@window_size}, rbuffer: #{@read_buffer.bytesize})" }
433
425
  unless siz
426
+ @write_buffer.clear
427
+
434
428
  ex = EOFError.new("descriptor closed")
435
429
  ex.set_backtrace(caller)
436
430
  on_error(ex)
@@ -485,6 +479,8 @@ module HTTPX
485
479
  end
486
480
  log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
487
481
  unless siz
482
+ @write_buffer.clear
483
+
488
484
  ex = EOFError.new("descriptor closed")
489
485
  ex.set_backtrace(caller)
490
486
  on_error(ex)
@@ -616,9 +612,9 @@ module HTTPX
616
612
  parser.on(:timeout) do |tout|
617
613
  @timeout = tout
618
614
  end
619
- parser.on(:error) do |request, ex|
620
- case ex
621
- when MisdirectedRequestError
615
+ parser.on(:error) do |request, error|
616
+ case error
617
+ when :http_1_1_required
622
618
  current_session = @current_session
623
619
  current_selector = @current_selector
624
620
  parser.close
@@ -628,11 +624,15 @@ module HTTPX
628
624
  other_connection.merge(self)
629
625
  request.transition(:idle)
630
626
  other_connection.send(request)
631
- else
632
- response = ErrorResponse.new(request, ex)
633
- request.response = response
634
- request.emit(:response, response)
627
+ next
628
+ when OperationTimeoutError
629
+ # request level timeouts should take precedence
630
+ next unless request.active_timeouts.empty?
635
631
  end
632
+
633
+ response = ErrorResponse.new(request, error)
634
+ request.response = response
635
+ request.emit(:response, response)
636
636
  end
637
637
  end
638
638
 
@@ -654,12 +654,14 @@ module HTTPX
654
654
  error.set_backtrace(e.backtrace)
655
655
  handle_connect_error(error) if connecting?
656
656
  @state = :closed
657
+ purge_after_closed
657
658
  disconnect
658
659
  rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
659
660
  # connect errors, exit gracefully
660
661
  handle_error(e)
661
662
  handle_connect_error(e) if connecting?
662
663
  @state = :closed
664
+ purge_after_closed
663
665
  disconnect
664
666
  end
665
667
 
@@ -812,7 +814,7 @@ module HTTPX
812
814
  end
813
815
 
814
816
  def on_error(error, request = nil)
815
- if error.instance_of?(TimeoutError)
817
+ if error.is_a?(OperationTimeoutError)
816
818
 
817
819
  # inactive connections do not contribute to the select loop, therefore
818
820
  # they should not fail due to such errors.
@@ -857,7 +859,7 @@ module HTTPX
857
859
 
858
860
  return if read_timeout.nil? || read_timeout.infinite?
859
861
 
860
- set_request_timeout(request, read_timeout, :done, :response) do
862
+ set_request_timeout(:read_timeout, request, read_timeout, :done, :response) do
861
863
  read_timeout_callback(request, read_timeout)
862
864
  end
863
865
  end
@@ -867,7 +869,7 @@ module HTTPX
867
869
 
868
870
  return if write_timeout.nil? || write_timeout.infinite?
869
871
 
870
- set_request_timeout(request, write_timeout, :headers, %i[done response]) do
872
+ set_request_timeout(:write_timeout, request, write_timeout, :headers, %i[done response]) do
871
873
  write_timeout_callback(request, write_timeout)
872
874
  end
873
875
  end
@@ -877,7 +879,7 @@ module HTTPX
877
879
 
878
880
  return if request_timeout.nil? || request_timeout.infinite?
879
881
 
880
- set_request_timeout(request, request_timeout, :headers, :complete) do
882
+ set_request_timeout(:request_timeout, request, request_timeout, :headers, :complete) do
881
883
  read_timeout_callback(request, request_timeout, RequestTimeoutError)
882
884
  end
883
885
  end
@@ -902,21 +904,18 @@ module HTTPX
902
904
  on_error(error, request)
903
905
  end
904
906
 
905
- def set_request_timeout(request, timeout, start_event, finish_events, &callback)
906
- request.once(start_event) do
907
- interval = @current_selector.after(timeout, callback)
907
+ def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
908
+ request.set_timeout_callback(start_event) do
909
+ timer = @current_selector.after(timeout, callback)
910
+ request.active_timeouts << label
908
911
 
909
912
  Array(finish_events).each do |event|
910
913
  # clean up request timeouts if the connection errors out
911
- request.once(event) do
912
- if @intervals.include?(interval)
913
- interval.delete(callback)
914
- @intervals.delete(interval) if interval.no_callbacks?
915
- end
914
+ request.set_timeout_callback(event) do
915
+ timer.cancel
916
+ request.active_timeouts.delete(label)
916
917
  end
917
918
  end
918
-
919
- @intervals << interval
920
919
  end
921
920
  end
922
921
 
data/lib/httpx/errors.rb CHANGED
@@ -77,6 +77,9 @@ module HTTPX
77
77
  # Error raised when there was a timeout while resolving a domain to an IP.
78
78
  class ResolveTimeoutError < TimeoutError; end
79
79
 
80
+ # Error raise when there was a timeout waiting for readiness of the socket the request is related to.
81
+ class OperationTimeoutError < TimeoutError; end
82
+
80
83
  # Error raised when there was an error while resolving a domain to an IP.
81
84
  class ResolveError < Error; end
82
85
 
@@ -112,8 +115,4 @@ module HTTPX
112
115
  @response.status
113
116
  end
114
117
  end
115
-
116
- # error raised when a request was sent a server which can't reproduce a response, and
117
- # has therefore returned an HTTP response using the 421 status code.
118
- class MisdirectedRequestError < HTTPError; end
119
118
  end
@@ -15,20 +15,27 @@ module HTTPX
15
15
 
16
16
  USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
17
17
 
18
- def log(level: @options.debug_level, color: nil, &msg)
19
- return unless @options.debug_level >= level
18
+ def log(level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug, &msg)
19
+ return unless debug_level >= level
20
20
 
21
- debug_stream = @options.debug || ($stderr if USE_DEBUG_LOG)
21
+ debug_stream = debug || ($stderr if USE_DEBUG_LOG)
22
22
 
23
23
  return unless debug_stream
24
24
 
25
- message = (+"" << msg.call << "\n")
25
+ klass = self.class
26
+
27
+ until (class_name = klass.name)
28
+ klass = klass.superclass
29
+ end
30
+
31
+ message = +"(pid:#{Process.pid} tid:#{Thread.current.object_id}, self:#{class_name}##{object_id}) "
32
+ message << msg.call << "\n"
26
33
  message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
27
34
  debug_stream << message
28
35
  end
29
36
 
30
- def log_exception(ex, level: @options.debug_level, color: nil)
31
- log(level: level, color: color) { ex.full_message }
37
+ def log_exception(ex, level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug)
38
+ log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
32
39
  end
33
40
  end
34
41
  end
@@ -25,6 +25,7 @@ module HTTPX
25
25
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
26
26
  def on_#{meth}(&blk) # def on_connection_opened(&blk)
27
27
  on(:#{meth}, &blk) # on(:connection_opened, &blk)
28
+ self # self
28
29
  end # end
29
30
  MOD
30
31
  end
@@ -36,6 +36,7 @@ module HTTPX
36
36
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
37
37
  def on_#{meth}(&blk) # def on_circuit_open(&blk)
38
38
  on(:#{meth}, &blk) # on(:circuit_open, &blk)
39
+ self # self
39
40
  end # end
40
41
  MOD
41
42
  end
@@ -84,7 +84,7 @@ module HTTPX
84
84
 
85
85
  return if expect_timeout.nil? || expect_timeout.infinite?
86
86
 
87
- set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
87
+ set_request_timeout(:expect_timeout, request, expect_timeout, :expect, %i[body response]) do
88
88
  # expect timeout expired
89
89
  if request.state == :expect && !request.expects?
90
90
  Expect.no_expect_store << request.origin
@@ -13,6 +13,12 @@ module HTTPX
13
13
  # by the end user in $http_init_time, different diff metrics can be shown. The "point of time" is calculated
14
14
  # using the monotonic clock.
15
15
  module InternalTelemetry
16
+ DEBUG_LEVEL = 3
17
+
18
+ def self.extra_options(options)
19
+ options.merge(debug_level: 3)
20
+ end
21
+
16
22
  module TrackTimeMethods
17
23
  private
18
24
 
@@ -28,7 +34,19 @@ module HTTPX
28
34
  after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
29
35
  # $http_init_time = after_time
30
36
  elapsed = after_time - prev_time
31
- warn(+"\e[31m" << "[ELAPSED TIME]: #{label}: #{elapsed} (ms)" << "\e[0m")
37
+ # klass = self.class
38
+
39
+ # until (class_name = klass.name)
40
+ # klass = klass.superclass
41
+ # end
42
+ log(
43
+ level: DEBUG_LEVEL,
44
+ color: :red,
45
+ debug_level: @options ? @options.debug_level : DEBUG_LEVEL,
46
+ debug: nil
47
+ ) do
48
+ "[ELAPSED TIME]: #{label}: #{elapsed} (ms)" << "\e[0m"
49
+ end
32
50
  end
33
51
  end
34
52
 
@@ -88,6 +106,7 @@ module HTTPX
88
106
 
89
107
  module RequestMethods
90
108
  def self.included(klass)
109
+ klass.prepend Loggable
91
110
  klass.prepend TrackTimeMethods
92
111
  super
93
112
  end
@@ -114,6 +133,7 @@ module HTTPX
114
133
 
115
134
  module PoolMethods
116
135
  def self.included(klass)
136
+ klass.prepend Loggable
117
137
  klass.prepend TrackTimeMethods
118
138
  super
119
139
  end
@@ -167,7 +167,7 @@ module HTTPX
167
167
  unless response.headers.key?("accept-ranges") &&
168
168
  response.headers["accept-ranges"] == "bytes" && # there's nothing else supported though...
169
169
  (original_body = response.body)
170
- response.close if response.respond_to?(:close)
170
+ response.body.close
171
171
  return
172
172
  end
173
173
 
data/lib/httpx/request.rb CHANGED
@@ -45,9 +45,14 @@ module HTTPX
45
45
 
46
46
  attr_writer :persistent
47
47
 
48
+ attr_reader :active_timeouts
49
+
48
50
  # will be +true+ when request body has been completely flushed.
49
51
  def_delegator :@body, :empty?
50
52
 
53
+ # closes the body
54
+ def_delegator :@body, :close
55
+
51
56
  # initializes the instance with the given +verb+ (an upppercase String, ex. 'GEt'),
52
57
  # an absolute or relative +uri+ (either as String or URI::HTTP object), the
53
58
  # request +options+ (instance of HTTPX::Options) and an optional Hash of +params+.
@@ -100,6 +105,7 @@ module HTTPX
100
105
  @response = nil
101
106
  @peer_address = nil
102
107
  @persistent = @options.persistent
108
+ @active_timeouts = []
103
109
  end
104
110
 
105
111
  # the read timeout defined for this requet.
@@ -245,8 +251,10 @@ module HTTPX
245
251
  @body.rewind
246
252
  @response = nil
247
253
  @drainer = nil
254
+ @active_timeouts.clear
248
255
  when :headers
249
256
  return unless @state == :idle
257
+
250
258
  when :body
251
259
  return unless @state == :headers ||
252
260
  @state == :expect
@@ -268,7 +276,6 @@ module HTTPX
268
276
  when :done
269
277
  return if @state == :expect
270
278
 
271
- @body.close
272
279
  end
273
280
  @state = nextstate
274
281
  emit(@state, self)
@@ -279,6 +286,15 @@ module HTTPX
279
286
  def expects?
280
287
  @headers["expect"] == "100-continue" && @informational_status == 100 && !@response
281
288
  end
289
+
290
+ def set_timeout_callback(event, &callback)
291
+ clb = once(event, &callback)
292
+
293
+ # reset timeout callbacks when requests get rerouted to a different connection
294
+ once(:idle) do
295
+ callbacks(event).delete(clb)
296
+ end
297
+ end
282
298
  end
283
299
  end
284
300