httpx 1.3.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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