httpx 1.3.3 → 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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_3_3.md +1 -1
  3. data/doc/release_notes/1_3_4.md +6 -0
  4. data/doc/release_notes/1_4_0.md +43 -0
  5. data/lib/httpx/adapters/faraday.rb +2 -0
  6. data/lib/httpx/adapters/webmock.rb +11 -5
  7. data/lib/httpx/callbacks.rb +0 -5
  8. data/lib/httpx/chainable.rb +3 -1
  9. data/lib/httpx/connection/http2.rb +11 -7
  10. data/lib/httpx/connection.rb +128 -16
  11. data/lib/httpx/errors.rb +12 -0
  12. data/lib/httpx/loggable.rb +5 -5
  13. data/lib/httpx/options.rb +26 -16
  14. data/lib/httpx/plugins/aws_sigv4.rb +31 -16
  15. data/lib/httpx/plugins/callbacks.rb +12 -2
  16. data/lib/httpx/plugins/circuit_breaker.rb +0 -5
  17. data/lib/httpx/plugins/content_digest.rb +202 -0
  18. data/lib/httpx/plugins/expect.rb +4 -3
  19. data/lib/httpx/plugins/follow_redirects.rb +7 -8
  20. data/lib/httpx/plugins/h2c.rb +23 -20
  21. data/lib/httpx/plugins/internal_telemetry.rb +27 -0
  22. data/lib/httpx/plugins/persistent.rb +16 -0
  23. data/lib/httpx/plugins/proxy/http.rb +17 -19
  24. data/lib/httpx/plugins/proxy.rb +91 -93
  25. data/lib/httpx/plugins/retries.rb +5 -8
  26. data/lib/httpx/plugins/upgrade.rb +5 -10
  27. data/lib/httpx/plugins/webdav.rb +6 -0
  28. data/lib/httpx/plugins/xml.rb +76 -0
  29. data/lib/httpx/pool.rb +73 -244
  30. data/lib/httpx/request/body.rb +16 -12
  31. data/lib/httpx/request.rb +1 -1
  32. data/lib/httpx/resolver/https.rb +12 -19
  33. data/lib/httpx/resolver/multi.rb +34 -16
  34. data/lib/httpx/resolver/native.rb +36 -13
  35. data/lib/httpx/resolver/resolver.rb +49 -11
  36. data/lib/httpx/resolver/system.rb +29 -11
  37. data/lib/httpx/resolver.rb +21 -14
  38. data/lib/httpx/response/body.rb +12 -1
  39. data/lib/httpx/response.rb +5 -3
  40. data/lib/httpx/selector.rb +164 -95
  41. data/lib/httpx/session.rb +296 -139
  42. data/lib/httpx/transcoder/gzip.rb +0 -3
  43. data/lib/httpx/transcoder/json.rb +14 -2
  44. data/lib/httpx/transcoder/multipart/encoder.rb +3 -1
  45. data/lib/httpx/transcoder/utils/deflater.rb +7 -4
  46. data/lib/httpx/transcoder/utils/inflater.rb +2 -0
  47. data/lib/httpx/transcoder.rb +0 -1
  48. data/lib/httpx/version.rb +1 -1
  49. data/lib/httpx.rb +19 -20
  50. data/sig/callbacks.rbs +0 -1
  51. data/sig/chainable.rbs +4 -0
  52. data/sig/connection/http2.rbs +1 -1
  53. data/sig/connection.rbs +14 -3
  54. data/sig/errors.rbs +6 -0
  55. data/sig/loggable.rbs +2 -0
  56. data/sig/options.rbs +7 -0
  57. data/sig/plugins/aws_sigv4.rbs +8 -2
  58. data/sig/plugins/content_digest.rbs +51 -0
  59. data/sig/plugins/cookies/cookie.rbs +9 -0
  60. data/sig/plugins/grpc/call.rbs +4 -0
  61. data/sig/plugins/persistent.rbs +4 -1
  62. data/sig/plugins/proxy/socks5.rbs +11 -3
  63. data/sig/plugins/proxy.rbs +18 -11
  64. data/sig/plugins/push_promise.rbs +3 -0
  65. data/sig/plugins/rate_limiter.rbs +2 -0
  66. data/sig/plugins/retries.rbs +1 -1
  67. data/sig/plugins/ssrf_filter.rbs +26 -0
  68. data/sig/plugins/webdav.rbs +23 -0
  69. data/sig/plugins/xml.rbs +37 -0
  70. data/sig/pool.rbs +25 -33
  71. data/sig/request/body.rbs +5 -1
  72. data/sig/resolver/multi.rbs +26 -1
  73. data/sig/resolver/native.rbs +0 -2
  74. data/sig/resolver/resolver.rbs +21 -2
  75. data/sig/resolver.rbs +5 -1
  76. data/sig/response/body.rbs +2 -2
  77. data/sig/response/buffer.rbs +2 -2
  78. data/sig/selector.rbs +30 -4
  79. data/sig/session.rbs +45 -18
  80. data/sig/transcoder/body.rbs +1 -1
  81. data/sig/transcoder/chunker.rbs +1 -1
  82. data/sig/transcoder/deflate.rbs +1 -0
  83. data/sig/transcoder/form.rbs +8 -0
  84. data/sig/transcoder/gzip.rbs +4 -1
  85. data/sig/transcoder/multipart.rbs +3 -3
  86. data/sig/transcoder/utils/body_reader.rbs +2 -2
  87. data/sig/transcoder/utils/deflater.rbs +2 -2
  88. metadata +12 -4
  89. data/lib/httpx/transcoder/xml.rb +0 -52
  90. data/sig/transcoder/xml.rbs +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8829f986611d6b07d5c8b14ed0a51152e7f9303d01d1ec9469263f78843ce339
4
- data.tar.gz: 8e55596dd065b9b3d352a0ad3173577556ea5bfb7963ea85287bb58d9018160b
3
+ metadata.gz: 7edc783221b5d919a1c788bf360f0cc2d43ee1556eb893c6b5d377d1bd3b4241
4
+ data.tar.gz: 14dc4593ae5c46acb33a4f242a0058e75eddec70bd22fdf75c05496240de3abc
5
5
  SHA512:
6
- metadata.gz: b46a5f9f27b7571051fc95ef5d11d03cdaaa822cd08684ad1b1cf2605aacabc42ea1762f152303932bdd1e1f41c89447017ffcfaee8f995f04fdd908cbe69444
7
- data.tar.gz: 57c5407473eb1fa8bbe7e4f3e3fef3288e8a8074776378a130894e72931833acf7bd30cd235f904fcd96cda810a3ec7118a3de9a66b28124ffd18de6b2fc1c42
6
+ metadata.gz: 181bc9e6155708c2d1cdaee3dc7c5c15cd5e2b1ba8d7911b6dd5a79e1cebe38558766ec89d8446483d434c7387e330874a70633b2ed5fb8f6a9d8a3401a4abf4
7
+ data.tar.gz: 49f57c406a2a5c5be83c018c2151ef2e842c266e93d9609dff15684e662bb25580e7c84e2e96a32d1a45094c9baf8ad18f89dbab1e8235127f4d40429f733572
@@ -1,4 +1,4 @@
1
- # 1.3.2
1
+ # 1.3.3
2
2
 
3
3
  ## Bugfixes
4
4
 
@@ -0,0 +1,6 @@
1
+ # 1.3.4
2
+
3
+ ## Bugfixes
4
+
5
+ * webmock adapter: fix tempfile usage in multipart requests.
6
+ * fix: fallback to binary encoding when parsing incoming invalid charset in HTTP "content-type" header.
@@ -0,0 +1,43 @@
1
+ # 1.4.0
2
+
3
+ ## Features
4
+
5
+ ### `:content_digest` plugin
6
+
7
+ The `:content_digest` can be used to calculate the digest of request payloads and set them in the `"content-digest"` header; it can also validate the integrity of responses which declare the same `"content-digest"` header.
8
+
9
+ More info under https://honeyryderchuck.gitlab.io/httpx/wiki/Content-Digest
10
+
11
+ ## Per-session connection pools
12
+
13
+ This architectural changes moves away from per-thread shared connection pools, and into per-session (also thread-safe) connection pools. Unlike before, this enables connections from a session to be reused across threads, as well as limiting the number of connections that can be open on a given origin peer. This fixes long-standing issues, such as reusing connections under a fiber scheduler loop (such as the one from the gem `async`).
14
+
15
+ A new `:pool_options` option is introduced, which can be passed an hash with the following sub-options:
16
+
17
+ * `:max_connections_per_origin`: maximum number of connections a pool allows (unbounded by default, for backwards compatibility).
18
+ * `:pool_timeout`: the number of seconds a session will wait for a connection to be checked out (default: 5)
19
+
20
+ More info under https://honeyryderchuck.gitlab.io/httpx/wiki/Connection-Pools
21
+
22
+
23
+ ## Improvements
24
+
25
+ * `:aws_sigv4` plugin: improved digest calculation on compressed request bodies by buffering content to a tempfile.
26
+ * `HTTPX::Response#json` will parse payload from extended json MIME types (like `application/ld+json`, `application/hal+json`, ...).
27
+
28
+ ## Bugfixes
29
+
30
+ * `:aws_sigv4` plugin: do not try to rewind a request body which yields chunks.
31
+ * fixed request encoding when `:json` param is passed, and the `oj` gem is used (by using the `:compat` flag).
32
+ * native resolver: on message truncation, bubble up tcp handshake errors as resolve errors.
33
+ * allow `HTTPX::Response#json` to accept extended JSON mime types (such as responses with `content-type: application/ld+json`)
34
+
35
+ ## Chore
36
+
37
+ * default options are now fully frozen (in case anyone relies on overriding them).
38
+
39
+ ### `:xml` plugin
40
+
41
+ XML encoding/decoding (via `:xml` request param, and `HTTPX::Response#xml`) is now available via the `:xml` plugin.
42
+
43
+ Using `HTTPX::Response#xml` without the plugin will issue a deprecation warning.
@@ -108,9 +108,11 @@ module Faraday
108
108
  ssl_options
109
109
  end
110
110
  else
111
+ # :nocov:
111
112
  def ssl_options_from_env(*)
112
113
  {}
113
114
  end
115
+ # :nocov:
114
116
  end
115
117
  end
116
118
 
@@ -52,16 +52,18 @@ module WebMock
52
52
  end
53
53
 
54
54
  module InstanceMethods
55
- def init_connection(*)
56
- connection = super
55
+ private
56
+
57
+ def do_init_connection(connection, selector)
58
+ super
59
+
57
60
  connection.once(:unmock_connection) do
58
61
  unless connection.addresses
59
62
  connection.__send__(:callbacks)[:connect_error].clear
60
- pool.__send__(:unregister_connection, connection)
63
+ deselect_connection(connection, selector)
61
64
  end
62
- pool.__send__(:resolve_connection, connection)
65
+ resolve_connection(connection, selector)
63
66
  end
64
- connection
65
67
  end
66
68
  end
67
69
 
@@ -100,6 +102,10 @@ module WebMock
100
102
  super
101
103
  end
102
104
 
105
+ def terminate
106
+ force_reset
107
+ end
108
+
103
109
  def send(request)
104
110
  request_signature = Plugin.build_webmock_request_signature(request)
105
111
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -15,11 +15,6 @@ module HTTPX
15
15
  self
16
16
  end
17
17
 
18
- def only(type, &block)
19
- callbacks(type).clear
20
- on(type, &block)
21
- end
22
-
23
18
  def emit(type, *args)
24
19
  callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
25
20
  end
@@ -73,7 +73,7 @@ module HTTPX
73
73
  ].include?(callback)
74
74
 
75
75
  warn "DEPRECATION WARNING: calling `.#{meth}` on plain HTTPX sessions is deprecated. " \
76
- "Use HTTPX.plugin(:callbacks).#{meth} instead."
76
+ "Use `HTTPX.plugin(:callbacks).#{meth}` instead."
77
77
 
78
78
  plugin(:callbacks).__send__(meth, *args, **options, &blk)
79
79
  else
@@ -101,4 +101,6 @@ module HTTPX
101
101
  end
102
102
  end
103
103
  end
104
+
105
+ extend Chainable
104
106
  end
@@ -98,10 +98,10 @@ module HTTPX
98
98
  @streams.size < @max_requests
99
99
  end
100
100
 
101
- def send(request)
101
+ def send(request, head = false)
102
102
  unless can_buffer_more_requests?
103
- @pending << request
104
- return
103
+ head ? @pending.unshift(request) : @pending << request
104
+ return false
105
105
  end
106
106
  unless (stream = @streams[request])
107
107
  stream = @connection.new_stream
@@ -113,6 +113,7 @@ module HTTPX
113
113
  true
114
114
  rescue ::HTTP2::Error::StreamLimitExceeded
115
115
  @pending.unshift(request)
116
+ false
116
117
  end
117
118
 
118
119
  def consume
@@ -154,8 +155,7 @@ module HTTPX
154
155
 
155
156
  def send_pending
156
157
  while (request = @pending.shift)
157
- # TODO: this request should go back to top of stack
158
- break unless send(request)
158
+ break unless send(request, true)
159
159
  end
160
160
  end
161
161
 
@@ -327,10 +327,14 @@ module HTTPX
327
327
  end
328
328
  end
329
329
  send(@pending.shift) unless @pending.empty?
330
+
330
331
  return unless @streams.empty? && exhausted?
331
332
 
332
- close
333
- emit(:exhausted) unless @pending.empty?
333
+ if @pending.empty?
334
+ close
335
+ else
336
+ emit(:exhausted)
337
+ end
334
338
  end
335
339
 
336
340
  def on_frame(bytes)
@@ -43,13 +43,14 @@ module HTTPX
43
43
 
44
44
  attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session
45
45
 
46
- attr_writer :timers
46
+ attr_writer :current_selector, :coalesced_connection
47
47
 
48
- attr_accessor :family
48
+ attr_accessor :current_session, :family
49
49
 
50
50
  def initialize(uri, options)
51
- @origins = [uri.origin]
52
- @origin = Utils.to_uri(uri.origin)
51
+ @current_session = @current_selector = @coalesced_connection = nil
52
+ @exhausted = @cloned = false
53
+
53
54
  @options = Options.new(options)
54
55
  @type = initialize_type(uri, @options)
55
56
  @origins = [uri.origin]
@@ -58,6 +59,7 @@ module HTTPX
58
59
  @read_buffer = Buffer.new(@options.buffer_size)
59
60
  @write_buffer = Buffer.new(@options.buffer_size)
60
61
  @pending = []
62
+
61
63
  on(:error, &method(:on_error))
62
64
  if @options.io
63
65
  # if there's an already open IO, get its
@@ -68,6 +70,31 @@ module HTTPX
68
70
  else
69
71
  transition(:idle)
70
72
  end
73
+ on(:activate) do
74
+ @current_session.select_connection(self, @current_selector)
75
+ end
76
+ on(:close) do
77
+ next if @exhausted # it'll reset
78
+
79
+ # may be called after ":close" above, so after the connection has been checked back in.
80
+ # next unless @current_session
81
+
82
+ next unless @current_session
83
+
84
+ @current_session.deselect_connection(self, @current_selector, @cloned)
85
+ end
86
+ on(:terminate) do
87
+ next if @exhausted # it'll reset
88
+
89
+ # may be called after ":close" above, so after the connection has been checked back in.
90
+ next unless @current_session
91
+
92
+ @current_session.deselect_connection(self, @current_selector)
93
+ end
94
+
95
+ on(:altsvc) do |alt_origin, origin, alt_params|
96
+ build_altsvc_connection(alt_origin, origin, alt_params)
97
+ end
71
98
 
72
99
  @inflight = 0
73
100
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
@@ -77,6 +104,10 @@ module HTTPX
77
104
  self.addresses = @options.addresses if @options.addresses
78
105
  end
79
106
 
107
+ def peer
108
+ @origin
109
+ end
110
+
80
111
  # this is a semi-private method, to be used by the resolver
81
112
  # to initiate the io object.
82
113
  def addresses=(addrs)
@@ -168,7 +199,12 @@ module HTTPX
168
199
  end
169
200
 
170
201
  def inflight?
171
- @parser && !@parser.empty? && !@write_buffer.empty?
202
+ @parser && (
203
+ # parser may be dealing with other requests (possibly started from a different fiber)
204
+ !@parser.empty? ||
205
+ # connection may be doing connection termination handshake
206
+ !@write_buffer.empty?
207
+ )
172
208
  end
173
209
 
174
210
  def interests
@@ -184,6 +220,9 @@ module HTTPX
184
220
 
185
221
  return @parser.interests if @parser
186
222
 
223
+ nil
224
+ rescue StandardError => e
225
+ emit(:error, e)
187
226
  nil
188
227
  end
189
228
 
@@ -205,6 +244,9 @@ module HTTPX
205
244
  consume
206
245
  end
207
246
  nil
247
+ rescue StandardError => e
248
+ emit(:error, e)
249
+ raise e
208
250
  end
209
251
 
210
252
  def close
@@ -221,8 +263,9 @@ module HTTPX
221
263
 
222
264
  # bypasses the state machine to force closing of connections still connecting.
223
265
  # **only** used for Happy Eyeballs v2.
224
- def force_reset
266
+ def force_reset(cloned = false)
225
267
  @state = :closing
268
+ @cloned = cloned
226
269
  transition(:closed)
227
270
  end
228
271
 
@@ -235,6 +278,8 @@ module HTTPX
235
278
  end
236
279
 
237
280
  def send(request)
281
+ return @coalesced_connection.send(request) if @coalesced_connection
282
+
238
283
  if @parser && !@write_buffer.full?
239
284
  if @response_received_at && @keep_alive_timeout &&
240
285
  Utils.elapsed_time(@response_received_at) > @keep_alive_timeout
@@ -255,6 +300,8 @@ module HTTPX
255
300
  end
256
301
 
257
302
  def timeout
303
+ return if @state == :closed || @state == :inactive
304
+
258
305
  return @timeout if @timeout
259
306
 
260
307
  return @options.timeout[:connect_timeout] if @state == :idle
@@ -301,6 +348,12 @@ module HTTPX
301
348
  transition(:open)
302
349
  end
303
350
 
351
+ def disconnect
352
+ emit(:close)
353
+ @current_session = nil
354
+ @current_selector = nil
355
+ end
356
+
304
357
  def consume
305
358
  return unless @io
306
359
 
@@ -475,8 +528,25 @@ module HTTPX
475
528
  request.emit(:promise, parser, stream)
476
529
  end
477
530
  parser.on(:exhausted) do
531
+ @exhausted = true
532
+ current_session = @current_session
533
+ current_selector = @current_selector
534
+ parser.close
478
535
  @pending.concat(parser.pending)
479
- emit(:exhausted)
536
+ case @state
537
+ when :closed
538
+ idling
539
+ @exhausted = false
540
+ @current_session = current_session
541
+ @current_selector = current_selector
542
+ when :closing
543
+ once(:close) do
544
+ idling
545
+ @exhausted = false
546
+ @current_session = current_session
547
+ @current_selector = current_selector
548
+ end
549
+ end
480
550
  end
481
551
  parser.on(:origin) do |origin|
482
552
  @origins |= [origin]
@@ -492,8 +562,14 @@ module HTTPX
492
562
  end
493
563
  parser.on(:reset) do
494
564
  @pending.concat(parser.pending) unless parser.empty?
565
+ current_session = @current_session
566
+ current_selector = @current_selector
495
567
  reset
496
- idling unless @pending.empty?
568
+ unless @pending.empty?
569
+ idling
570
+ @current_session = current_session
571
+ @current_selector = current_selector
572
+ end
497
573
  end
498
574
  parser.on(:current_timeout) do
499
575
  @current_timeout = @timeout = parser.timeout
@@ -504,7 +580,15 @@ module HTTPX
504
580
  parser.on(:error) do |request, ex|
505
581
  case ex
506
582
  when MisdirectedRequestError
507
- emit(:misdirected, request)
583
+ current_session = @current_session
584
+ current_selector = @current_selector
585
+ parser.close
586
+
587
+ other_connection = current_session.find_connection(@origin, current_selector,
588
+ @options.merge(ssl: { alpn_protocols: %w[http/1.1] }))
589
+ other_connection.merge(self)
590
+ request.transition(:idle)
591
+ other_connection.send(request)
508
592
  else
509
593
  response = ErrorResponse.new(request, ex)
510
594
  request.response = response
@@ -531,13 +615,13 @@ module HTTPX
531
615
  error.set_backtrace(e.backtrace)
532
616
  connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, error) : handle_error(error)
533
617
  @state = :closed
534
- emit(:close)
618
+ disconnect
535
619
  rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
536
620
  # connect errors, exit gracefully
537
621
  handle_error(e)
538
622
  connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, e) : handle_error(e)
539
623
  @state = :closed
540
- emit(:close)
624
+ disconnect
541
625
  end
542
626
 
543
627
  def handle_transition(nextstate)
@@ -582,7 +666,7 @@ module HTTPX
582
666
  return unless @write_buffer.empty?
583
667
 
584
668
  purge_after_closed
585
- emit(:close) if @pending.empty?
669
+ disconnect if @pending.empty?
586
670
  when :already_open
587
671
  nextstate = :open
588
672
  # the first check for given io readiness must still use a timeout.
@@ -617,12 +701,40 @@ module HTTPX
617
701
  end
618
702
  end
619
703
 
704
+ # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
705
+ def build_altsvc_connection(alt_origin, origin, alt_params)
706
+ # do not allow security downgrades on altsvc negotiation
707
+ return if @origin.scheme == "https" && alt_origin.scheme != "https"
708
+
709
+ altsvc = AltSvc.cached_altsvc_set(origin, alt_params.merge("origin" => alt_origin))
710
+
711
+ # altsvc already exists, somehow it wasn't advertised, probably noop
712
+ return unless altsvc
713
+
714
+ alt_options = @options.merge(ssl: @options.ssl.merge(hostname: URI(origin).host))
715
+
716
+ connection = @current_session.find_connection(alt_origin, @current_selector, alt_options)
717
+
718
+ # advertised altsvc is the same origin being used, ignore
719
+ return if connection == self
720
+
721
+ connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
722
+
723
+ log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
724
+
725
+ connection.merge(self)
726
+ terminate
727
+ rescue UnsupportedSchemeError
728
+ altsvc["noop"] = true
729
+ nil
730
+ end
731
+
620
732
  def build_socket(addrs = nil)
621
733
  case @type
622
734
  when "tcp"
623
- TCP.new(@origin, addrs, @options)
735
+ TCP.new(peer, addrs, @options)
624
736
  when "ssl"
625
- SSL.new(@origin, addrs, @options) do |sock|
737
+ SSL.new(peer, addrs, @options) do |sock|
626
738
  sock.ssl_session = @ssl_session
627
739
  sock.session_new_cb do |sess|
628
740
  @ssl_session = sess
@@ -635,7 +747,7 @@ module HTTPX
635
747
 
636
748
  path = String(path) if path
637
749
 
638
- UNIX.new(@origin, path, @options)
750
+ UNIX.new(peer, path, @options)
639
751
  else
640
752
  raise Error, "unsupported transport (#{@type})"
641
753
  end
@@ -734,7 +846,7 @@ module HTTPX
734
846
 
735
847
  def set_request_timeout(request, timeout, start_event, finish_events, &callback)
736
848
  request.once(start_event) do
737
- interval = @timers.after(timeout, callback)
849
+ interval = @current_selector.after(timeout, callback)
738
850
 
739
851
  Array(finish_events).each do |event|
740
852
  # clean up request timeouts if the connection errors out
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.
@@ -13,11 +13,14 @@ module HTTPX
13
13
  white: 37,
14
14
  }.freeze
15
15
 
16
+ USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
17
+
16
18
  def log(level: @options.debug_level, color: nil, &msg)
17
- return unless @options.debug
18
19
  return unless @options.debug_level >= level
19
20
 
20
- debug_stream = @options.debug
21
+ debug_stream = @options.debug || ($stderr if USE_DEBUG_LOG)
22
+
23
+ return unless debug_stream
21
24
 
22
25
  message = (+"" << msg.call << "\n")
23
26
  message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
@@ -25,9 +28,6 @@ module HTTPX
25
28
  end
26
29
 
27
30
  def log_exception(ex, level: @options.debug_level, color: nil)
28
- return unless @options.debug
29
- return unless @options.debug_level >= level
30
-
31
31
  log(level: level, color: color) { ex.full_message }
32
32
  end
33
33
  end
data/lib/httpx/options.rb CHANGED
@@ -25,14 +25,14 @@ module HTTPX
25
25
  end
26
26
  rescue NotImplementedError
27
27
  [Socket::AF_INET]
28
- end
28
+ end.freeze
29
29
 
30
30
  DEFAULT_OPTIONS = {
31
31
  :max_requests => Float::INFINITY,
32
- :debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
32
+ :debug => nil,
33
33
  :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
34
- :ssl => {},
35
- :http2_settings => { settings_enable_push: 0 },
34
+ :ssl => EMPTY_HASH,
35
+ :http2_settings => { settings_enable_push: 0 }.freeze,
36
36
  :fallback_protocol => "http/1.1",
37
37
  :supported_compression_formats => %w[gzip deflate],
38
38
  :decompress_response_body => true,
@@ -56,13 +56,15 @@ module HTTPX
56
56
  :response_class => Class.new(Response),
57
57
  :request_body_class => Class.new(Request::Body),
58
58
  :response_body_class => Class.new(Response::Body),
59
+ :pool_class => Class.new(Pool),
59
60
  :connection_class => Class.new(Connection),
60
61
  :options_class => Class.new(self),
61
62
  :transport => nil,
62
63
  :addresses => nil,
63
64
  :persistent => false,
64
65
  :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
65
- :resolver_options => { cache: true },
66
+ :resolver_options => { cache: true }.freeze,
67
+ :pool_options => EMPTY_HASH,
66
68
  :ip_families => ip_address_families,
67
69
  }.freeze
68
70
 
@@ -110,6 +112,7 @@ module HTTPX
110
112
  # :request_body_class :: class used to instantiate a request body
111
113
  # :response_body_class :: class used to instantiate a response body
112
114
  # :connection_class :: class used to instantiate connections
115
+ # :pool_class :: class used to instantiate the session connection pool
113
116
  # :options_class :: class used to instantiate options
114
117
  # :transport :: type of transport to use (set to "unix" for UNIX sockets)
115
118
  # :addresses :: bucket of peer addresses (can be a list of IP addresses, a hash of domain to list of adddresses;
@@ -118,7 +121,8 @@ module HTTPX
118
121
  # :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
119
122
  # :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
120
123
  # using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class)
121
- # :resolver_options :: hash of options passed to the resolver
124
+ # :resolver_options :: hash of options passed to the resolver. Accepted keys depend on the resolver type.
125
+ # :pool_options :: hash of options passed to the connection pool (See Pool#initialize).
122
126
  # :ip_families :: which socket families are supported (system-dependent)
123
127
  # :origin :: HTTP origin to set on requests with relative path (ex: "https://api.serv.com")
124
128
  # :base_path :: path to prefix given relative paths with (ex: "/v2")
@@ -215,6 +219,7 @@ module HTTPX
215
219
  ssl http2_settings
216
220
  request_class response_class headers_class request_body_class
217
221
  response_body_class connection_class options_class
222
+ pool_class pool_options
218
223
  io fallback_protocol debug debug_level resolver_class resolver_options
219
224
  compress_request_body decompress_response_body
220
225
  persistent
@@ -246,14 +251,6 @@ module HTTPX
246
251
  end
247
252
  end
248
253
 
249
- OTHER_LOOKUP = ->(obj, k, ivar_map) {
250
- case obj
251
- when Hash
252
- obj[ivar_map[k]]
253
- else
254
- obj.instance_variable_get(k)
255
- end
256
- }
257
254
  def merge(other)
258
255
  ivar_map = nil
259
256
  other_ivars = case other
@@ -266,12 +263,12 @@ module HTTPX
266
263
 
267
264
  return self if other_ivars.empty?
268
265
 
269
- return self if other_ivars.all? { |ivar| instance_variable_get(ivar) == OTHER_LOOKUP[other, ivar, ivar_map] }
266
+ return self if other_ivars.all? { |ivar| instance_variable_get(ivar) == access_option(other, ivar, ivar_map) }
270
267
 
271
268
  opts = dup
272
269
 
273
270
  other_ivars.each do |ivar|
274
- v = OTHER_LOOKUP[other, ivar, ivar_map]
271
+ v = access_option(other, ivar, ivar_map)
275
272
 
276
273
  unless v
277
274
  opts.instance_variable_set(ivar, v)
@@ -322,6 +319,10 @@ module HTTPX
322
319
  @response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
323
320
  @response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
324
321
  end
322
+ if defined?(pl::PoolMethods)
323
+ @pool_class = @pool_class.dup
324
+ @pool_class.__send__(:include, pl::PoolMethods)
325
+ end
325
326
  if defined?(pl::ConnectionMethods)
326
327
  @connection_class = @connection_class.dup
327
328
  @connection_class.__send__(:include, pl::ConnectionMethods)
@@ -346,5 +347,14 @@ module HTTPX
346
347
  instance_variable_set(:"@#{k}", value)
347
348
  end
348
349
  end
350
+
351
+ def access_option(obj, k, ivar_map)
352
+ case obj
353
+ when Hash
354
+ obj[ivar_map[k]]
355
+ else
356
+ obj.instance_variable_get(k)
357
+ end
358
+ end
349
359
  end
350
360
  end