httpx 1.2.6 → 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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/doc/release_notes/1_3_0.md +18 -0
  4. data/doc/release_notes/1_3_1.md +17 -0
  5. data/doc/release_notes/1_3_2.md +6 -0
  6. data/doc/release_notes/1_3_3.md +5 -0
  7. data/doc/release_notes/1_3_4.md +6 -0
  8. data/doc/release_notes/1_4_0.md +43 -0
  9. data/doc/release_notes/1_4_1.md +19 -0
  10. data/doc/release_notes/1_4_2.md +20 -0
  11. data/doc/release_notes/1_4_3.md +11 -0
  12. data/doc/release_notes/1_4_4.md +14 -0
  13. data/lib/httpx/adapters/datadog.rb +56 -80
  14. data/lib/httpx/adapters/faraday.rb +5 -2
  15. data/lib/httpx/adapters/webmock.rb +24 -8
  16. data/lib/httpx/callbacks.rb +2 -7
  17. data/lib/httpx/chainable.rb +3 -1
  18. data/lib/httpx/connection/http1.rb +11 -7
  19. data/lib/httpx/connection/http2.rb +57 -34
  20. data/lib/httpx/connection.rb +270 -71
  21. data/lib/httpx/errors.rb +15 -4
  22. data/lib/httpx/io/ssl.rb +6 -3
  23. data/lib/httpx/io/tcp.rb +1 -1
  24. data/lib/httpx/io/unix.rb +1 -1
  25. data/lib/httpx/loggable.rb +17 -10
  26. data/lib/httpx/options.rb +30 -23
  27. data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
  28. data/lib/httpx/plugins/aws_sigv4.rb +36 -17
  29. data/lib/httpx/plugins/callbacks.rb +13 -2
  30. data/lib/httpx/plugins/circuit_breaker.rb +11 -5
  31. data/lib/httpx/plugins/content_digest.rb +202 -0
  32. data/lib/httpx/plugins/cookies.rb +9 -6
  33. data/lib/httpx/plugins/digest_auth.rb +3 -0
  34. data/lib/httpx/plugins/expect.rb +10 -4
  35. data/lib/httpx/plugins/follow_redirects.rb +68 -33
  36. data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
  37. data/lib/httpx/plugins/grpc.rb +2 -2
  38. data/lib/httpx/plugins/h2c.rb +23 -20
  39. data/lib/httpx/plugins/internal_telemetry.rb +48 -1
  40. data/lib/httpx/plugins/oauth.rb +1 -1
  41. data/lib/httpx/plugins/persistent.rb +16 -0
  42. data/lib/httpx/plugins/proxy/http.rb +19 -16
  43. data/lib/httpx/plugins/proxy/socks4.rb +1 -1
  44. data/lib/httpx/plugins/proxy/socks5.rb +1 -1
  45. data/lib/httpx/plugins/proxy.rb +96 -85
  46. data/lib/httpx/plugins/retries.rb +28 -10
  47. data/lib/httpx/plugins/ssrf_filter.rb +4 -1
  48. data/lib/httpx/plugins/stream.rb +42 -18
  49. data/lib/httpx/plugins/upgrade.rb +5 -10
  50. data/lib/httpx/plugins/webdav.rb +6 -0
  51. data/lib/httpx/plugins/xml.rb +76 -0
  52. data/lib/httpx/pool.rb +73 -244
  53. data/lib/httpx/request/body.rb +50 -55
  54. data/lib/httpx/request.rb +77 -14
  55. data/lib/httpx/resolver/https.rb +17 -20
  56. data/lib/httpx/resolver/multi.rb +34 -16
  57. data/lib/httpx/resolver/native.rb +140 -61
  58. data/lib/httpx/resolver/resolver.rb +64 -19
  59. data/lib/httpx/resolver/system.rb +32 -16
  60. data/lib/httpx/resolver.rb +21 -14
  61. data/lib/httpx/response/body.rb +12 -1
  62. data/lib/httpx/response.rb +16 -9
  63. data/lib/httpx/selector.rb +170 -91
  64. data/lib/httpx/session.rb +282 -139
  65. data/lib/httpx/timers.rb +17 -2
  66. data/lib/httpx/transcoder/body.rb +15 -29
  67. data/lib/httpx/transcoder/form.rb +2 -0
  68. data/lib/httpx/transcoder/gzip.rb +0 -3
  69. data/lib/httpx/transcoder/json.rb +16 -2
  70. data/lib/httpx/transcoder/multipart/encoder.rb +11 -2
  71. data/lib/httpx/transcoder/multipart/part.rb +1 -1
  72. data/lib/httpx/transcoder/utils/deflater.rb +7 -4
  73. data/lib/httpx/transcoder.rb +0 -1
  74. data/lib/httpx/version.rb +1 -1
  75. data/lib/httpx.rb +20 -21
  76. data/sig/callbacks.rbs +2 -3
  77. data/sig/chainable.rbs +6 -2
  78. data/sig/connection/http1.rbs +2 -2
  79. data/sig/connection/http2.rbs +22 -18
  80. data/sig/connection.rbs +40 -9
  81. data/sig/errors.rbs +9 -3
  82. data/sig/httpx.rbs +3 -3
  83. data/sig/io/tcp.rbs +1 -1
  84. data/sig/io/unix.rbs +1 -1
  85. data/sig/loggable.rbs +4 -2
  86. data/sig/options.rbs +8 -13
  87. data/sig/plugins/aws_sigv4.rbs +8 -2
  88. data/sig/plugins/content_digest.rbs +51 -0
  89. data/sig/plugins/cookies/cookie.rbs +9 -0
  90. data/sig/plugins/follow_redirects.rbs +1 -1
  91. data/sig/plugins/grpc/call.rbs +4 -0
  92. data/sig/plugins/persistent.rbs +4 -1
  93. data/sig/plugins/proxy/http.rbs +3 -0
  94. data/sig/plugins/proxy/socks5.rbs +11 -3
  95. data/sig/plugins/proxy.rbs +18 -9
  96. data/sig/plugins/push_promise.rbs +6 -3
  97. data/sig/plugins/rate_limiter.rbs +2 -0
  98. data/sig/plugins/retries.rbs +1 -1
  99. data/sig/plugins/ssrf_filter.rbs +26 -0
  100. data/sig/plugins/stream.rbs +3 -0
  101. data/sig/plugins/webdav.rbs +23 -0
  102. data/sig/plugins/xml.rbs +37 -0
  103. data/sig/pool.rbs +27 -33
  104. data/sig/request/body.rbs +4 -10
  105. data/sig/request.rbs +14 -1
  106. data/sig/resolver/multi.rbs +26 -1
  107. data/sig/resolver/native.rbs +6 -3
  108. data/sig/resolver/resolver.rbs +22 -3
  109. data/sig/resolver.rbs +5 -1
  110. data/sig/response/body.rbs +2 -2
  111. data/sig/response/buffer.rbs +2 -2
  112. data/sig/response.rbs +9 -4
  113. data/sig/selector.rbs +31 -4
  114. data/sig/session.rbs +54 -20
  115. data/sig/timers.rbs +15 -4
  116. data/sig/transcoder/body.rbs +2 -4
  117. data/sig/transcoder/chunker.rbs +1 -1
  118. data/sig/transcoder/deflate.rbs +1 -0
  119. data/sig/transcoder/form.rbs +8 -0
  120. data/sig/transcoder/gzip.rbs +4 -1
  121. data/sig/transcoder/json.rbs +1 -1
  122. data/sig/transcoder/multipart.rbs +6 -4
  123. data/sig/transcoder/utils/body_reader.rbs +3 -3
  124. data/sig/transcoder/utils/deflater.rbs +2 -3
  125. metadata +32 -14
  126. data/lib/httpx/session2.rb +0 -23
  127. data/lib/httpx/transcoder/utils/inflater.rb +0 -19
  128. data/lib/httpx/transcoder/xml.rb +0 -52
  129. data/sig/transcoder/utils/inflater.rbs +0 -12
  130. data/sig/transcoder/xml.rbs +0 -22
@@ -41,13 +41,18 @@ module HTTPX
41
41
 
42
42
  def_delegator :@write_buffer, :empty?
43
43
 
44
- attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
44
+ attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
45
45
 
46
- attr_writer :timers
46
+ attr_writer :current_selector
47
47
 
48
- attr_accessor :family
48
+ attr_accessor :current_session, :family
49
+
50
+ protected :sibling
49
51
 
50
52
  def initialize(uri, options)
53
+ @current_session = @current_selector = @sibling = @coalesced_connection = nil
54
+ @exhausted = @cloned = @main_sibling = false
55
+
51
56
  @options = Options.new(options)
52
57
  @type = initialize_type(uri, @options)
53
58
  @origins = [uri.origin]
@@ -56,6 +61,7 @@ module HTTPX
56
61
  @read_buffer = Buffer.new(@options.buffer_size)
57
62
  @write_buffer = Buffer.new(@options.buffer_size)
58
63
  @pending = []
64
+
59
65
  on(:error, &method(:on_error))
60
66
  if @options.io
61
67
  # if there's an already open IO, get its
@@ -66,15 +72,42 @@ module HTTPX
66
72
  else
67
73
  transition(:idle)
68
74
  end
75
+ on(:close) do
76
+ next if @exhausted # it'll reset
77
+
78
+ # may be called after ":close" above, so after the connection has been checked back in.
79
+ # next unless @current_session
80
+
81
+ next unless @current_session
82
+
83
+ @current_session.deselect_connection(self, @current_selector, @cloned)
84
+ end
85
+ on(:terminate) do
86
+ next if @exhausted # it'll reset
87
+
88
+ current_session = @current_session
89
+ current_selector = @current_selector
90
+
91
+ # may be called after ":close" above, so after the connection has been checked back in.
92
+ next unless current_session && current_selector
93
+
94
+ current_session.deselect_connection(self, current_selector)
95
+ end
96
+
97
+ on(:altsvc) do |alt_origin, origin, alt_params|
98
+ build_altsvc_connection(alt_origin, origin, alt_params)
99
+ end
69
100
 
70
101
  @inflight = 0
71
102
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
72
103
 
73
- @intervals = []
74
-
75
104
  self.addresses = @options.addresses if @options.addresses
76
105
  end
77
106
 
107
+ def peer
108
+ @origin
109
+ end
110
+
78
111
  # this is a semi-private method, to be used by the resolver
79
112
  # to initiate the io object.
80
113
  def addresses=(addrs)
@@ -161,12 +194,23 @@ module HTTPX
161
194
  end
162
195
  end
163
196
 
197
+ def io_connected?
198
+ return @coalesced_connection.io_connected? if @coalesced_connection
199
+
200
+ @io && @io.state == :connected
201
+ end
202
+
164
203
  def connecting?
165
204
  @state == :idle
166
205
  end
167
206
 
168
207
  def inflight?
169
- @parser && !@parser.empty? && !@write_buffer.empty?
208
+ @parser && (
209
+ # parser may be dealing with other requests (possibly started from a different fiber)
210
+ !@parser.empty? ||
211
+ # connection may be doing connection termination handshake
212
+ !@write_buffer.empty?
213
+ )
170
214
  end
171
215
 
172
216
  def interests
@@ -182,6 +226,9 @@ module HTTPX
182
226
 
183
227
  return @parser.interests if @parser
184
228
 
229
+ nil
230
+ rescue StandardError => e
231
+ emit(:error, e)
185
232
  nil
186
233
  end
187
234
 
@@ -203,6 +250,9 @@ module HTTPX
203
250
  consume
204
251
  end
205
252
  nil
253
+ rescue StandardError => e
254
+ emit(:error, e)
255
+ raise e
206
256
  end
207
257
 
208
258
  def close
@@ -219,8 +269,9 @@ module HTTPX
219
269
 
220
270
  # bypasses the state machine to force closing of connections still connecting.
221
271
  # **only** used for Happy Eyeballs v2.
222
- def force_reset
272
+ def force_reset(cloned = false)
223
273
  @state = :closing
274
+ @cloned = cloned
224
275
  transition(:closed)
225
276
  end
226
277
 
@@ -233,6 +284,8 @@ module HTTPX
233
284
  end
234
285
 
235
286
  def send(request)
287
+ return @coalesced_connection.send(request) if @coalesced_connection
288
+
236
289
  if @parser && !@write_buffer.full?
237
290
  if @response_received_at && @keep_alive_timeout &&
238
291
  Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
@@ -243,6 +296,7 @@ module HTTPX
243
296
  @pending << request
244
297
  transition(:active) if @state == :inactive
245
298
  parser.ping
299
+ request.ping!
246
300
  return
247
301
  end
248
302
 
@@ -253,6 +307,8 @@ module HTTPX
253
307
  end
254
308
 
255
309
  def timeout
310
+ return if @state == :closed || @state == :inactive
311
+
256
312
  return @timeout if @timeout
257
313
 
258
314
  return @options.timeout[:connect_timeout] if @state == :idle
@@ -280,17 +336,46 @@ module HTTPX
280
336
  end
281
337
 
282
338
  def handle_socket_timeout(interval)
283
- @intervals.delete_if(&:elapsed?)
339
+ error = OperationTimeoutError.new(interval, "timed out while waiting on select")
340
+ error.set_backtrace(caller)
341
+ on_error(error)
342
+ end
284
343
 
285
- unless @intervals.empty?
286
- # remove the intervals which will elapse
344
+ def coalesced_connection=(connection)
345
+ @coalesced_connection = connection
287
346
 
288
- return
289
- end
347
+ close_sibling
348
+ connection.merge(self)
349
+ end
290
350
 
291
- error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
292
- error.set_backtrace(caller)
293
- on_error(error)
351
+ def sibling=(connection)
352
+ @sibling = connection
353
+
354
+ return unless connection
355
+
356
+ @main_sibling = connection.sibling.nil?
357
+
358
+ return unless @main_sibling
359
+
360
+ connection.sibling = self
361
+ end
362
+
363
+ def handle_connect_error(error)
364
+ @connect_error = error
365
+
366
+ return handle_error(error) unless @sibling && @sibling.connecting?
367
+
368
+ @sibling.merge(self)
369
+
370
+ force_reset(true)
371
+ end
372
+
373
+ def disconnect
374
+ return unless @current_session && @current_selector
375
+
376
+ emit(:close)
377
+ @current_session = nil
378
+ @current_selector = nil
294
379
  end
295
380
 
296
381
  private
@@ -337,8 +422,10 @@ module HTTPX
337
422
  #
338
423
  loop do
339
424
  siz = @io.read(@window_size, @read_buffer)
340
- log(level: 3, color: :cyan) { "IO READ: #{siz} bytes..." }
425
+ log(level: 3, color: :cyan) { "IO READ: #{siz} bytes... (wsize: #{@window_size}, rbuffer: #{@read_buffer.bytesize})" }
341
426
  unless siz
427
+ @write_buffer.clear
428
+
342
429
  ex = EOFError.new("descriptor closed")
343
430
  ex.set_backtrace(caller)
344
431
  on_error(ex)
@@ -393,6 +480,8 @@ module HTTPX
393
480
  end
394
481
  log(level: 3, color: :cyan) { "IO WRITE: #{siz} bytes..." }
395
482
  unless siz
483
+ @write_buffer.clear
484
+
396
485
  ex = EOFError.new("descriptor closed")
397
486
  ex.set_backtrace(caller)
398
487
  on_error(ex)
@@ -473,8 +562,27 @@ module HTTPX
473
562
  request.emit(:promise, parser, stream)
474
563
  end
475
564
  parser.on(:exhausted) do
476
- @pending.concat(parser.pending)
477
- emit(:exhausted)
565
+ @exhausted = true
566
+ current_session = @current_session
567
+ current_selector = @current_selector
568
+ begin
569
+ parser.close
570
+ @pending.concat(parser.pending)
571
+ ensure
572
+ @current_session = current_session
573
+ @current_selector = current_selector
574
+ end
575
+
576
+ case @state
577
+ when :closed
578
+ idling
579
+ @exhausted = false
580
+ when :closing
581
+ once(:closed) do
582
+ idling
583
+ @exhausted = false
584
+ end
585
+ end
478
586
  end
479
587
  parser.on(:origin) do |origin|
480
588
  @origins |= [origin]
@@ -490,8 +598,14 @@ module HTTPX
490
598
  end
491
599
  parser.on(:reset) do
492
600
  @pending.concat(parser.pending) unless parser.empty?
601
+ current_session = @current_session
602
+ current_selector = @current_selector
493
603
  reset
494
- idling unless @pending.empty?
604
+ unless @pending.empty?
605
+ idling
606
+ @current_session = current_session
607
+ @current_selector = current_selector
608
+ end
495
609
  end
496
610
  parser.on(:current_timeout) do
497
611
  @current_timeout = @timeout = parser.timeout
@@ -499,15 +613,28 @@ module HTTPX
499
613
  parser.on(:timeout) do |tout|
500
614
  @timeout = tout
501
615
  end
502
- parser.on(:error) do |request, ex|
503
- case ex
504
- when MisdirectedRequestError
505
- emit(:misdirected, request)
506
- else
507
- response = ErrorResponse.new(request, ex, @options)
508
- request.response = response
509
- request.emit(:response, response)
616
+ parser.on(:error) do |request, error|
617
+ case error
618
+ when :http_1_1_required
619
+ current_session = @current_session
620
+ current_selector = @current_selector
621
+ parser.close
622
+
623
+ other_connection = current_session.find_connection(@origin, current_selector,
624
+ @options.merge(ssl: { alpn_protocols: %w[http/1.1] }))
625
+ other_connection.merge(self)
626
+ request.transition(:idle)
627
+ other_connection.send(request)
628
+ next
629
+ when OperationTimeoutError
630
+ # request level timeouts should take precedence
631
+ next unless request.active_timeouts.empty?
510
632
  end
633
+
634
+ @inflight -= 1
635
+ response = ErrorResponse.new(request, error)
636
+ request.response = response
637
+ request.emit(:response, response)
511
638
  end
512
639
  end
513
640
 
@@ -527,15 +654,17 @@ module HTTPX
527
654
  # connect errors, exit gracefully
528
655
  error = ConnectionError.new(e.message)
529
656
  error.set_backtrace(e.backtrace)
530
- connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, error) : handle_error(error)
657
+ handle_connect_error(error) if connecting?
531
658
  @state = :closed
532
- emit(:close)
533
- rescue TLSError, HTTP2Next::Error::ProtocolError, HTTP2Next::Error::HandshakeError => e
659
+ purge_after_closed
660
+ disconnect
661
+ rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
534
662
  # connect errors, exit gracefully
535
663
  handle_error(e)
536
- connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, e) : handle_error(e)
664
+ handle_connect_error(e) if connecting?
537
665
  @state = :closed
538
- emit(:close)
666
+ purge_after_closed
667
+ disconnect
539
668
  end
540
669
 
541
670
  def handle_transition(nextstate)
@@ -543,12 +672,12 @@ module HTTPX
543
672
  when :idle
544
673
  @timeout = @current_timeout = @options.timeout[:connect_timeout]
545
674
 
546
- @connected_at = nil
675
+ @connected_at = @response_received_at = nil
547
676
  when :open
548
677
  return if @state == :closed
549
678
 
550
679
  @io.connect
551
- emit(:tcp_open, self) if @io.state == :connected
680
+ close_sibling if @io.state == :connected
552
681
 
553
682
  return unless @io.connected?
554
683
 
@@ -560,6 +689,9 @@ module HTTPX
560
689
  emit(:open)
561
690
  when :inactive
562
691
  return unless @state == :open
692
+
693
+ # do not deactivate connection in use
694
+ return if @inflight.positive?
563
695
  when :closing
564
696
  return unless @state == :idle || @state == :open
565
697
 
@@ -577,7 +709,8 @@ module HTTPX
577
709
  return unless @write_buffer.empty?
578
710
 
579
711
  purge_after_closed
580
- emit(:close) if @pending.empty?
712
+ disconnect if @pending.empty?
713
+
581
714
  when :already_open
582
715
  nextstate = :open
583
716
  # the first check for given io readiness must still use a timeout.
@@ -588,11 +721,29 @@ module HTTPX
588
721
  return unless @state == :inactive
589
722
 
590
723
  nextstate = :open
591
- emit(:activate)
724
+
725
+ # activate
726
+ @current_session.select_connection(self, @current_selector)
592
727
  end
593
728
  @state = nextstate
594
729
  end
595
730
 
731
+ def close_sibling
732
+ return unless @sibling
733
+
734
+ if @sibling.io_connected?
735
+ reset
736
+ # TODO: transition connection to closed
737
+ end
738
+
739
+ unless @sibling.state == :closed
740
+ merge(@sibling) unless @main_sibling
741
+ @sibling.force_reset(true)
742
+ end
743
+
744
+ @sibling = nil
745
+ end
746
+
596
747
  def purge_after_closed
597
748
  @io.close if @io
598
749
  @read_buffer.clear
@@ -612,12 +763,40 @@ module HTTPX
612
763
  end
613
764
  end
614
765
 
766
+ # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
767
+ def build_altsvc_connection(alt_origin, origin, alt_params)
768
+ # do not allow security downgrades on altsvc negotiation
769
+ return if @origin.scheme == "https" && alt_origin.scheme != "https"
770
+
771
+ altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
772
+
773
+ # altsvc already exists, somehow it wasn't advertised, probably noop
774
+ return unless altsvc
775
+
776
+ alt_options = @options.merge(ssl: @options.ssl.merge(hostname: URI(origin).host))
777
+
778
+ connection = @current_session.find_connection(alt_origin, @current_selector, alt_options)
779
+
780
+ # advertised altsvc is the same origin being used, ignore
781
+ return if connection == self
782
+
783
+ connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
784
+
785
+ log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
786
+
787
+ connection.merge(self)
788
+ terminate
789
+ rescue UnsupportedSchemeError
790
+ altsvc["noop"] = true
791
+ nil
792
+ end
793
+
615
794
  def build_socket(addrs = nil)
616
795
  case @type
617
796
  when "tcp"
618
- TCP.new(@origin, addrs, @options)
797
+ TCP.new(peer, addrs, @options)
619
798
  when "ssl"
620
- SSL.new(@origin, addrs, @options) do |sock|
799
+ SSL.new(peer, addrs, @options) do |sock|
621
800
  sock.ssl_session = @ssl_session
622
801
  sock.session_new_cb do |sess|
623
802
  @ssl_session = sess
@@ -630,14 +809,14 @@ module HTTPX
630
809
 
631
810
  path = String(path) if path
632
811
 
633
- UNIX.new(@origin, path, @options)
812
+ UNIX.new(peer, path, @options)
634
813
  else
635
814
  raise Error, "unsupported transport (#{@type})"
636
815
  end
637
816
  end
638
817
 
639
- def on_error(error)
640
- if error.instance_of?(TimeoutError)
818
+ def on_error(error, request = nil)
819
+ if error.is_a?(OperationTimeoutError)
641
820
 
642
821
  # inactive connections do not contribute to the select loop, therefore
643
822
  # they should not fail due to such errors.
@@ -650,39 +829,60 @@ module HTTPX
650
829
 
651
830
  error = error.to_connection_error if connecting?
652
831
  end
653
- handle_error(error)
832
+ handle_error(error, request)
654
833
  reset
655
834
  end
656
835
 
657
- def handle_error(error)
658
- parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
659
- while (request = @pending.shift)
660
- response = ErrorResponse.new(request, error, request.options)
661
- request.response = response
662
- request.emit(:response, response)
836
+ def handle_error(error, request = nil)
837
+ parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
838
+ while (req = @pending.shift)
839
+ next if request && req == request
840
+
841
+ response = ErrorResponse.new(req, error)
842
+ req.response = response
843
+ req.emit(:response, response)
663
844
  end
845
+
846
+ return unless request
847
+
848
+ @inflight -= 1
849
+ response = ErrorResponse.new(request, error)
850
+ request.response = response
851
+ request.emit(:response, response)
664
852
  end
665
853
 
666
854
  def set_request_timeouts(request)
667
- write_timeout = request.write_timeout
855
+ set_request_write_timeout(request)
856
+ set_request_read_timeout(request)
857
+ set_request_request_timeout(request)
858
+ end
859
+
860
+ def set_request_read_timeout(request)
668
861
  read_timeout = request.read_timeout
669
- request_timeout = request.request_timeout
670
862
 
671
- unless write_timeout.nil? || write_timeout.infinite?
672
- set_request_timeout(request, write_timeout, :headers, %i[done response]) do
673
- write_timeout_callback(request, write_timeout)
674
- end
863
+ return if read_timeout.nil? || read_timeout.infinite?
864
+
865
+ set_request_timeout(:read_timeout, request, read_timeout, :done, :response) do
866
+ read_timeout_callback(request, read_timeout)
675
867
  end
868
+ end
676
869
 
677
- unless read_timeout.nil? || read_timeout.infinite?
678
- set_request_timeout(request, read_timeout, :done, :response) do
679
- read_timeout_callback(request, read_timeout)
680
- end
870
+ def set_request_write_timeout(request)
871
+ write_timeout = request.write_timeout
872
+
873
+ return if write_timeout.nil? || write_timeout.infinite?
874
+
875
+ set_request_timeout(:write_timeout, request, write_timeout, :headers, %i[done response]) do
876
+ write_timeout_callback(request, write_timeout)
681
877
  end
878
+ end
879
+
880
+ def set_request_request_timeout(request)
881
+ request_timeout = request.request_timeout
682
882
 
683
883
  return if request_timeout.nil? || request_timeout.infinite?
684
884
 
685
- set_request_timeout(request, request_timeout, :headers, :response) do
885
+ set_request_timeout(:request_timeout, request, request_timeout, :headers, :complete) do
686
886
  read_timeout_callback(request, request_timeout, RequestTimeoutError)
687
887
  end
688
888
  end
@@ -692,7 +892,8 @@ module HTTPX
692
892
 
693
893
  @write_buffer.clear
694
894
  error = WriteTimeoutError.new(request, nil, write_timeout)
695
- on_error(error)
895
+
896
+ on_error(error, request)
696
897
  end
697
898
 
698
899
  def read_timeout_callback(request, read_timeout, error_type = ReadTimeoutError)
@@ -702,24 +903,22 @@ module HTTPX
702
903
 
703
904
  @write_buffer.clear
704
905
  error = error_type.new(request, request.response, read_timeout)
705
- on_error(error)
906
+
907
+ on_error(error, request)
706
908
  end
707
909
 
708
- def set_request_timeout(request, timeout, start_event, finish_events, &callback)
709
- request.once(start_event) do
710
- interval = @timers.after(timeout, callback)
910
+ def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
911
+ request.set_timeout_callback(start_event) do
912
+ timer = @current_selector.after(timeout, callback)
913
+ request.active_timeouts << label
711
914
 
712
915
  Array(finish_events).each do |event|
713
916
  # clean up request timeouts if the connection errors out
714
- request.once(event) do
715
- if @intervals.include?(interval)
716
- interval.delete(callback)
717
- @intervals.delete(interval) if interval.no_callbacks?
718
- end
917
+ request.set_timeout_callback(event) do
918
+ timer.cancel
919
+ request.active_timeouts.delete(label)
719
920
  end
720
921
  end
721
-
722
- @intervals << interval
723
922
  end
724
923
  end
725
924
 
data/lib/httpx/errors.rb CHANGED
@@ -29,6 +29,18 @@ module HTTPX
29
29
  end
30
30
  end
31
31
 
32
+ # Raise when it can't acquire a connection for a given origin.
33
+ class PoolTimeoutError < TimeoutError
34
+ attr_reader :origin
35
+
36
+ # initializes the +origin+ it refers to, and the
37
+ # +timeout+ causing the error.
38
+ def initialize(origin, timeout)
39
+ @origin = origin
40
+ super(timeout, "Timed out after #{timeout} seconds while waiting for a connection to #{origin}")
41
+ end
42
+ end
43
+
32
44
  # Error raised when there was a timeout establishing the connection to a server.
33
45
  # This may be raised due to timeouts during TCP and TLS (when applicable) connection
34
46
  # establishment.
@@ -65,6 +77,9 @@ module HTTPX
65
77
  # Error raised when there was a timeout while resolving a domain to an IP.
66
78
  class ResolveTimeoutError < TimeoutError; end
67
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
+
68
83
  # Error raised when there was an error while resolving a domain to an IP.
69
84
  class ResolveError < Error; end
70
85
 
@@ -100,8 +115,4 @@ module HTTPX
100
115
  @response.status
101
116
  end
102
117
  end
103
-
104
- # error raised when a request was sent a server which can't reproduce a response, and
105
- # has therefore returned an HTTP response using the 421 status code.
106
- class MisdirectedRequestError < HTTPError; end
107
118
  end
data/lib/httpx/io/ssl.rb CHANGED
@@ -92,9 +92,12 @@ module HTTPX
92
92
  end
93
93
 
94
94
  def connect
95
- super
96
- return if @state == :negotiated ||
97
- @state != :connected
95
+ return if @state == :negotiated
96
+
97
+ unless @state == :connected
98
+ super
99
+ return unless @state == :connected
100
+ end
98
101
 
99
102
  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
100
103
  if (hostname_is_ip = (@ip == @sni_hostname))
data/lib/httpx/io/tcp.rb CHANGED
@@ -17,7 +17,7 @@ module HTTPX
17
17
  @state = :idle
18
18
  @addresses = []
19
19
  @hostname = origin.host
20
- @options = Options.new(options)
20
+ @options = options
21
21
  @fallback_protocol = @options.fallback_protocol
22
22
  @port = origin.port
23
23
  @interests = :w
data/lib/httpx/io/unix.rb CHANGED
@@ -12,7 +12,7 @@ module HTTPX
12
12
  @addresses = []
13
13
  @hostname = origin.host
14
14
  @state = :idle
15
- @options = Options.new(options)
15
+ @options = options
16
16
  @fallback_protocol = @options.fallback_protocol
17
17
  if @options.io
18
18
  @io = case @options.io
@@ -13,22 +13,29 @@ module HTTPX
13
13
  white: 37,
14
14
  }.freeze
15
15
 
16
- def log(level: @options.debug_level, color: nil, &msg)
17
- return unless @options.debug
18
- return unless @options.debug_level >= level
16
+ USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
19
17
 
20
- debug_stream = @options.debug
18
+ def log(level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug, &msg)
19
+ return unless debug_level >= level
21
20
 
22
- message = (+"" << msg.call << "\n")
21
+ debug_stream = debug || ($stderr if USE_DEBUG_LOG)
22
+
23
+ return unless debug_stream
24
+
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"
23
33
  message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
24
34
  debug_stream << message
25
35
  end
26
36
 
27
- def log_exception(ex, level: @options.debug_level, color: nil)
28
- return unless @options.debug
29
- return unless @options.debug_level >= level
30
-
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