httpx 1.6.1 → 1.6.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_6_2.md +11 -0
  3. data/doc/release_notes/1_6_3.md +47 -0
  4. data/lib/httpx/adapters/datadog.rb +15 -11
  5. data/lib/httpx/adapters/sentry.rb +1 -1
  6. data/lib/httpx/connection/http1.rb +9 -9
  7. data/lib/httpx/connection/http2.rb +14 -15
  8. data/lib/httpx/connection.rb +119 -102
  9. data/lib/httpx/extensions.rb +0 -14
  10. data/lib/httpx/io/ssl.rb +1 -1
  11. data/lib/httpx/loggable.rb +12 -2
  12. data/lib/httpx/options.rb +20 -0
  13. data/lib/httpx/plugins/callbacks.rb +15 -1
  14. data/lib/httpx/plugins/digest_auth.rb +1 -1
  15. data/lib/httpx/plugins/proxy/http.rb +37 -9
  16. data/lib/httpx/plugins/response_cache/file_store.rb +1 -0
  17. data/lib/httpx/plugins/response_cache.rb +13 -2
  18. data/lib/httpx/plugins/stream_bidi.rb +15 -6
  19. data/lib/httpx/pool.rb +53 -19
  20. data/lib/httpx/request.rb +3 -13
  21. data/lib/httpx/resolver/https.rb +35 -19
  22. data/lib/httpx/resolver/multi.rb +9 -32
  23. data/lib/httpx/resolver/native.rb +46 -38
  24. data/lib/httpx/resolver/resolver.rb +45 -28
  25. data/lib/httpx/resolver/system.rb +63 -39
  26. data/lib/httpx/selector.rb +35 -20
  27. data/lib/httpx/session.rb +18 -28
  28. data/lib/httpx/transcoder/deflate.rb +13 -8
  29. data/lib/httpx/transcoder/utils/body_reader.rb +1 -2
  30. data/lib/httpx/transcoder/utils/deflater.rb +1 -2
  31. data/lib/httpx/version.rb +1 -1
  32. data/sig/connection.rbs +12 -3
  33. data/sig/loggable.rbs +5 -1
  34. data/sig/options.rbs +5 -1
  35. data/sig/plugins/callbacks.rbs +3 -0
  36. data/sig/plugins/stream_bidi.rbs +3 -5
  37. data/sig/resolver/https.rbs +2 -0
  38. data/sig/resolver/multi.rbs +0 -9
  39. data/sig/resolver/native.rbs +0 -2
  40. data/sig/resolver/resolver.rbs +9 -8
  41. data/sig/resolver/system.rbs +4 -2
  42. data/sig/selector.rbs +2 -0
  43. data/sig/session.rbs +5 -3
  44. metadata +5 -1
@@ -34,8 +34,6 @@ module HTTPX
34
34
 
35
35
  using URIExtensions
36
36
 
37
- def_delegator :@io, :closed?
38
-
39
37
  def_delegator :@write_buffer, :empty?
40
38
 
41
39
  attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
@@ -48,9 +46,9 @@ module HTTPX
48
46
 
49
47
  def initialize(uri, options)
50
48
  @current_session = @current_selector =
51
- @parser = @sibling = @coalesced_connection =
52
- @family = @io = @ssl_session = @timeout =
53
- @connected_at = @response_received_at = nil
49
+ @parser = @sibling = @coalesced_connection = @altsvc_connection =
50
+ @family = @io = @ssl_session = @timeout =
51
+ @connected_at = @response_received_at = nil
54
52
 
55
53
  @exhausted = @cloned = @main_sibling = false
56
54
 
@@ -65,7 +63,6 @@ module HTTPX
65
63
  @inflight = 0
66
64
  @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
67
65
 
68
- on(:error, &method(:on_error))
69
66
  if @options.io
70
67
  # if there's an already open IO, get its
71
68
  # peer address, and force-initiate the parser
@@ -75,32 +72,6 @@ module HTTPX
75
72
  else
76
73
  transition(:idle)
77
74
  end
78
- on(:close) do
79
- next if @exhausted # it'll reset
80
-
81
- # may be called after ":close" above, so after the connection has been checked back in.
82
- # next unless @current_session
83
-
84
- next unless @current_session
85
-
86
- @current_session.deselect_connection(self, @current_selector, @cloned)
87
- end
88
- on(:terminate) do
89
- next if @exhausted # it'll reset
90
-
91
- current_session = @current_session
92
- current_selector = @current_selector
93
-
94
- # may be called after ":close" above, so after the connection has been checked back in.
95
- next unless current_session && current_selector
96
-
97
- current_session.deselect_connection(self, current_selector)
98
- end
99
-
100
- on(:altsvc) do |alt_origin, origin, alt_params|
101
- build_altsvc_connection(alt_origin, origin, alt_params)
102
- end
103
-
104
75
  self.addresses = @options.addresses if @options.addresses
105
76
  end
106
77
 
@@ -158,6 +129,10 @@ module HTTPX
158
129
  connection.merge(self)
159
130
  end
160
131
 
132
+ def coalesced?
133
+ @coalesced_connection
134
+ end
135
+
161
136
  # coalescable connections need to be mergeable!
162
137
  # but internally, #mergeable? is called before #coalescable?
163
138
  def coalescable?(connection)
@@ -231,7 +206,7 @@ module HTTPX
231
206
 
232
207
  nil
233
208
  rescue StandardError => e
234
- emit(:error, e)
209
+ on_error(e)
235
210
  nil
236
211
  end
237
212
 
@@ -243,6 +218,10 @@ module HTTPX
243
218
  case @state
244
219
  when :idle
245
220
  connect
221
+
222
+ # when opening the tcp or ssl socket fails
223
+ return if @state == :closed
224
+
246
225
  consume
247
226
  when :closed
248
227
  return
@@ -255,7 +234,9 @@ module HTTPX
255
234
  nil
256
235
  rescue StandardError => e
257
236
  @write_buffer.clear
258
- emit(:error, e)
237
+ on_error(e)
238
+ rescue Exception => e # rubocop:disable Lint/RescueException
239
+ force_close(true)
259
240
  raise e
260
241
  end
261
242
 
@@ -269,7 +250,7 @@ module HTTPX
269
250
  case @state
270
251
  when :idle
271
252
  purge_after_closed
272
- emit(:terminate)
253
+ disconnect
273
254
  when :closed
274
255
  @connected_at = nil
275
256
  end
@@ -277,6 +258,23 @@ module HTTPX
277
258
  close
278
259
  end
279
260
 
261
+ # bypasses state machine rules while setting the connection in the
262
+ # :closed state.
263
+ def force_close(delete_pending = false)
264
+ if delete_pending
265
+ @pending.clear
266
+ elsif (parser = @parser)
267
+ enqueue_pending_requests_from_parser(parser)
268
+ end
269
+ return if @state == :closed
270
+
271
+ @state = :closed
272
+ @write_buffer.clear
273
+ purge_after_closed
274
+ disconnect
275
+ emit(:force_closed, delete_pending)
276
+ end
277
+
280
278
  # bypasses the state machine to force closing of connections still connecting.
281
279
  # **only** used for Happy Eyeballs v2.
282
280
  def force_reset(cloned = false)
@@ -364,18 +362,40 @@ module HTTPX
364
362
  end
365
363
 
366
364
  def handle_connect_error(error)
367
- return handle_error(error) unless @sibling && @sibling.connecting?
365
+ return on_error(error) unless @sibling && @sibling.connecting?
368
366
 
369
367
  @sibling.merge(self)
370
368
 
371
369
  force_reset(true)
372
370
  end
373
371
 
372
+ # disconnects from the current session it's attached to
374
373
  def disconnect
375
- return unless @current_session && @current_selector
374
+ return if @exhausted # it'll reset
375
+
376
+ return unless (current_session = @current_session) && (current_selector = @current_selector)
376
377
 
377
- emit(:close)
378
378
  @current_session = @current_selector = nil
379
+
380
+ current_session.deselect_connection(self, current_selector, @cloned)
381
+ end
382
+
383
+ def on_error(error, request = nil)
384
+ if error.is_a?(OperationTimeoutError)
385
+
386
+ # inactive connections do not contribute to the select loop, therefore
387
+ # they should not fail due to such errors.
388
+ return if @state == :inactive
389
+
390
+ if @timeout
391
+ @timeout -= error.timeout
392
+ return unless @timeout <= 0
393
+ end
394
+
395
+ error = error.to_connection_error if connecting?
396
+ end
397
+ handle_error(error, request)
398
+ reset
379
399
  end
380
400
 
381
401
  # :nocov:
@@ -413,7 +433,11 @@ module HTTPX
413
433
  # * the number of pending requests
414
434
  # * whether the write buffer has bytes (i.e. for close handshake)
415
435
  if @pending.empty? && @inflight.zero? && @write_buffer.empty?
416
- log(level: 3) { "NO MORE REQUESTS..." }
436
+ log(level: 3) { "NO MORE REQUESTS..." } if @parser && @parser.pending.any?
437
+
438
+ # terminate if an altsvc connection has been established
439
+ terminate if @altsvc_connection
440
+
417
441
  return
418
442
  end
419
443
 
@@ -458,7 +482,14 @@ module HTTPX
458
482
  break if @state == :closing || @state == :closed
459
483
 
460
484
  # exit #consume altogether if all outstanding requests have been dealt with
461
- return if @pending.empty? && @inflight.zero?
485
+ if @pending.empty? && @inflight.zero? && @write_buffer.empty? # rubocop:disable Style/Next
486
+ log(level: 3) { "NO MORE REQUESTS..." } if @parser && @parser.pending.any?
487
+
488
+ # terminate if an altsvc connection has been established
489
+ terminate if @altsvc_connection
490
+
491
+ return
492
+ end
462
493
  end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
463
494
 
464
495
  #
@@ -551,6 +582,17 @@ module HTTPX
551
582
  request.ping!
552
583
  end
553
584
 
585
+ def enqueue_pending_requests_from_parser(parser)
586
+ parser_pending_requests = parser.pending
587
+
588
+ return if parser_pending_requests.empty?
589
+
590
+ # the connection will be reused, so parser requests must come
591
+ # back to the pending list before the parser is reset.
592
+ @inflight -= parser_pending_requests.size
593
+ @pending.unshift(*parser_pending_requests)
594
+ end
595
+
554
596
  def build_parser(protocol = @io.protocol)
555
597
  parser = parser_type(protocol).new(@write_buffer, @options)
556
598
  set_parser_callbacks(parser)
@@ -560,7 +602,7 @@ module HTTPX
560
602
  def set_parser_callbacks(parser)
561
603
  parser.on(:response) do |request, response|
562
604
  AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
563
- emit(:altsvc, alt_origin, origin, alt_params)
605
+ build_altsvc_connection(alt_origin, origin, alt_params)
564
606
  end
565
607
  @response_received_at = Utils.now
566
608
  @inflight -= 1
@@ -568,7 +610,7 @@ module HTTPX
568
610
  request.emit(:response, response)
569
611
  end
570
612
  parser.on(:altsvc) do |alt_origin, origin, alt_params|
571
- emit(:altsvc, alt_origin, origin, alt_params)
613
+ build_altsvc_connection(alt_origin, origin, alt_params)
572
614
  end
573
615
 
574
616
  parser.on(:pong, &method(:send_pending))
@@ -577,50 +619,31 @@ module HTTPX
577
619
  request.emit(:promise, parser, stream)
578
620
  end
579
621
  parser.on(:exhausted) do
622
+ enqueue_pending_requests_from_parser(parser)
623
+
580
624
  @exhausted = true
581
- current_session = @current_session
582
- current_selector = @current_selector
583
- begin
584
- parser.close
585
- @pending.concat(parser.pending)
586
- ensure
587
- @current_session = current_session
588
- @current_selector = current_selector
589
- end
625
+ parser.close
590
626
 
591
- case @state
592
- when :closed
593
- idling
594
- @exhausted = false
595
- when :closing
596
- once(:closed) do
597
- idling
598
- @exhausted = false
599
- end
600
- end
627
+ idling
628
+ @exhausted = false
601
629
  end
602
630
  parser.on(:origin) do |origin|
603
631
  @origins |= [origin]
604
632
  end
605
- parser.on(:close) do |force|
606
- if force
607
- reset
608
- emit(:terminate)
609
- end
633
+ parser.on(:close) do
634
+ reset
635
+ disconnect
610
636
  end
611
637
  parser.on(:close_handshake) do
612
- consume
638
+ consume unless @state == :closed
613
639
  end
614
640
  parser.on(:reset) do
615
- @pending.concat(parser.pending) unless parser.empty?
616
- current_session = @current_session
617
- current_selector = @current_selector
641
+ enqueue_pending_requests_from_parser(parser)
642
+
618
643
  reset
619
- unless @pending.empty?
620
- idling
621
- @current_session = current_session
622
- @current_selector = current_selector
623
- end
644
+ # :reset event only fired in http/1.1, so this guarantees
645
+ # that the connection will be closed here.
646
+ idling unless @pending.empty?
624
647
  end
625
648
  parser.on(:current_timeout) do
626
649
  @current_timeout = @timeout = parser.timeout
@@ -670,16 +693,12 @@ module HTTPX
670
693
  error = ConnectionError.new(e.message)
671
694
  error.set_backtrace(e.backtrace)
672
695
  handle_connect_error(error) if connecting?
673
- @state = :closed
674
- purge_after_closed
675
- disconnect
696
+ force_close
676
697
  rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
677
698
  # connect errors, exit gracefully
678
699
  handle_error(e)
679
700
  handle_connect_error(e) if connecting?
680
- @state = :closed
681
- purge_after_closed
682
- disconnect
701
+ force_close
683
702
  end
684
703
 
685
704
  def handle_transition(nextstate)
@@ -707,6 +726,8 @@ module HTTPX
707
726
 
708
727
  # do not deactivate connection in use
709
728
  return if @inflight.positive? || @parser.waiting_for_ping?
729
+
730
+ disconnect
710
731
  when :closing
711
732
  return unless @state == :idle || @state == :open
712
733
 
@@ -781,6 +802,8 @@ module HTTPX
781
802
 
782
803
  # returns an HTTPX::Connection for the negotiated Alternative Service (or none).
783
804
  def build_altsvc_connection(alt_origin, origin, alt_params)
805
+ return if @altsvc_connection
806
+
784
807
  # do not allow security downgrades on altsvc negotiation
785
808
  return if @origin.scheme == "https" && alt_origin.scheme != "https"
786
809
 
@@ -798,10 +821,11 @@ module HTTPX
798
821
 
799
822
  connection.extend(AltSvc::ConnectionMixin) unless connection.is_a?(AltSvc::ConnectionMixin)
800
823
 
801
- log(level: 1) { "#{origin} alt-svc: #{alt_origin}" }
824
+ @altsvc_connection = connection
825
+
826
+ log(level: 1) { "#{origin}: alt-svc connection##{connection.object_id} established to #{alt_origin}" }
802
827
 
803
828
  connection.merge(self)
804
- terminate
805
829
  rescue UnsupportedSchemeError
806
830
  altsvc["noop"] = true
807
831
  nil
@@ -831,26 +855,8 @@ module HTTPX
831
855
  end
832
856
  end
833
857
 
834
- def on_error(error, request = nil)
835
- if error.is_a?(OperationTimeoutError)
836
-
837
- # inactive connections do not contribute to the select loop, therefore
838
- # they should not fail due to such errors.
839
- return if @state == :inactive
840
-
841
- if @timeout
842
- @timeout -= error.timeout
843
- return unless @timeout <= 0
844
- end
845
-
846
- error = error.to_connection_error if connecting?
847
- end
848
- handle_error(error, request)
849
- reset
850
- end
851
-
852
858
  def handle_error(error, request = nil)
853
- parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
859
+ parser.handle_error(error, request) if @parser && @parser.respond_to?(:handle_error)
854
860
  while (req = @pending.shift)
855
861
  next if request && req == request
856
862
 
@@ -925,6 +931,17 @@ module HTTPX
925
931
 
926
932
  def set_request_timeout(label, request, timeout, start_event, finish_events, &callback)
927
933
  request.set_timeout_callback(start_event) do
934
+ unless @current_selector
935
+ raise Error, "request has been resend to an out-of-session connection, and this " \
936
+ "should never happen!!! Please report this error! " \
937
+ "(state:#{@state}, " \
938
+ "parser?:#{!!@parser}, " \
939
+ "bytes in write buffer?:#{!@write_buffer.empty?}, " \
940
+ "cloned?:#{@cloned}, " \
941
+ "sibling?:#{!!@sibling}, " \
942
+ "coalesced?:#{coalesced?})"
943
+ end
944
+
928
945
  timer = @current_selector.after(timeout, callback)
929
946
  request.active_timeouts << label
930
947
 
@@ -4,20 +4,6 @@ require "uri"
4
4
 
5
5
  module HTTPX
6
6
  module ArrayExtensions
7
- module FilterMap
8
- refine Array do
9
- # Ruby 2.7 backport
10
- def filter_map
11
- return to_enum(:filter_map) unless block_given?
12
-
13
- each_with_object([]) do |item, res|
14
- processed = yield(item)
15
- res << processed if processed
16
- end
17
- end
18
- end unless Array.method_defined?(:filter_map)
19
- end
20
-
21
7
  module Intersect
22
8
  refine Array do
23
9
  # Ruby 3.1 backport
data/lib/httpx/io/ssl.rb CHANGED
@@ -98,7 +98,7 @@ module HTTPX
98
98
  end
99
99
 
100
100
  unless @io.is_a?(OpenSSL::SSL::SSLSocket)
101
- if (hostname_is_ip = (@ip == @sni_hostname))
101
+ if (hostname_is_ip = (@ip == @sni_hostname)) && @ctx.verify_hostname
102
102
  # IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
103
103
  @sni_hostname = @ip.to_string
104
104
  # IP addresses in SNI is not valid per RFC 6066, section 3.
@@ -34,7 +34,7 @@ module HTTPX
34
34
  klass = klass.superclass
35
35
  end
36
36
 
37
- message = +"(pid:#{Process.pid}, " \
37
+ message = +"(time:#{Time.now.utc}, pid:#{Process.pid}, " \
38
38
  "tid:#{Thread.current.object_id}, " \
39
39
  "fid:#{Fiber.current.object_id}, " \
40
40
  "self:#{class_name}##{object_id}) "
@@ -47,7 +47,17 @@ module HTTPX
47
47
  log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
48
48
  end
49
49
 
50
- def log_redact(text, should_redact = @options.debug_redact)
50
+ def log_redact_headers(text)
51
+ log_redact(text, @options.debug_redact == :headers)
52
+ end
53
+
54
+ def log_redact_body(text)
55
+ log_redact(text, @options.debug_redact == :body)
56
+ end
57
+
58
+ def log_redact(text, should_redact)
59
+ should_redact ||= @options.debug_redact == true
60
+
51
61
  return text.to_s unless should_redact
52
62
 
53
63
  "[REDACTED]"
data/lib/httpx/options.rb CHANGED
@@ -13,6 +13,9 @@ module HTTPX
13
13
  CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
14
14
  REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
15
15
 
16
+ # default value used for "user-agent" header, when not overridden.
17
+ USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
18
+
16
19
  @options_names = []
17
20
 
18
21
  class << self
@@ -144,6 +147,7 @@ module HTTPX
144
147
  instance_variable_set(:"@#{k}", value)
145
148
  end
146
149
 
150
+ do_initialize
147
151
  freeze
148
152
  end
149
153
 
@@ -369,6 +373,8 @@ module HTTPX
369
373
  end
370
374
 
371
375
  def option_headers(value)
376
+ value = value.dup if value.frozen?
377
+
372
378
  headers_class.new(value)
373
379
  end
374
380
 
@@ -395,6 +401,20 @@ module HTTPX
395
401
  Array(value)
396
402
  end
397
403
 
404
+ # called after all options are initialized
405
+ def do_initialize
406
+ hs = @headers
407
+
408
+ # initialized default request headers
409
+ hs["user-agent"] = USER_AGENT unless hs.key?("user-agent")
410
+ hs["accept"] = "*/*" unless hs.key?("accept")
411
+ if hs.key?("range")
412
+ hs.delete("accept-encoding")
413
+ else
414
+ hs["accept-encoding"] = supported_compression_formats unless hs.key?("accept-encoding")
415
+ end
416
+ end
417
+
398
418
  def access_option(obj, k, ivar_map)
399
419
  case obj
400
420
  when Hash
@@ -64,7 +64,7 @@ module HTTPX
64
64
 
65
65
  emit_or_callback_error(:connection_opened, connection.origin, connection.io.socket)
66
66
  end
67
- connection.on(:close) do
67
+ connection.on(:callback_connection_closed) do
68
68
  next unless connection.current_session == self
69
69
 
70
70
  emit_or_callback_error(:connection_closed, connection.origin) if connection.used?
@@ -121,6 +121,20 @@ module HTTPX
121
121
  raise e.cause
122
122
  end
123
123
  end
124
+
125
+ module ConnectionMethods
126
+ private
127
+
128
+ def disconnect
129
+ return if @exhausted
130
+
131
+ return unless @current_session && @current_selector
132
+
133
+ emit(:callback_connection_closed)
134
+
135
+ super
136
+ end
137
+ end
124
138
  end
125
139
  register_plugin :callbacks, Callbacks
126
140
  end
@@ -48,7 +48,7 @@ module HTTPX
48
48
 
49
49
  probe_response = wrap { super(request).first }
50
50
 
51
- return probe_response unless probe_response.is_a?(Response)
51
+ return ([probe_response] * requests.size) unless probe_response.is_a?(Response)
52
52
 
53
53
  if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
54
54
  request.transition(:idle)
@@ -47,6 +47,17 @@ module HTTPX
47
47
  super || @state == :connecting || @state == :connected
48
48
  end
49
49
 
50
+ def force_close(*)
51
+ if @state == :connecting
52
+ # proxy connect related requests should not be reenqueed
53
+ @parser.reset!
54
+ @inflight -= @parser.pending.size
55
+ @parser.pending.clear
56
+ end
57
+
58
+ super
59
+ end
60
+
50
61
  private
51
62
 
52
63
  def handle_transition(nextstate)
@@ -64,23 +75,40 @@ module HTTPX
64
75
  parser = @parser
65
76
  parser.extend(ProxyParser)
66
77
  parser.on(:response, &method(:__http_on_connect))
67
- parser.on(:close) do |force|
78
+ parser.on(:close) do
68
79
  next unless @parser
69
80
 
70
- if force
71
- reset
72
- emit(:terminate)
73
- end
81
+ reset
82
+ disconnect
74
83
  end
75
84
  parser.on(:reset) do
76
85
  if parser.empty?
77
86
  reset
78
87
  else
79
- transition(:closing)
80
- transition(:closed)
88
+ enqueue_pending_requests_from_parser(parser)
89
+
90
+ initial_state = @state
91
+
92
+ reset
93
+
94
+ if @pending.empty?
95
+ @parser = nil
96
+ next
97
+ end
98
+ # keep parser state around due to proxy auth protocol;
99
+ # intermediate authenticated request is already inside
100
+ # the parser
101
+ parser = nil
102
+
103
+ if initial_state == :connecting
104
+ parser = @parser
105
+ @parser.reset
106
+ end
107
+
108
+ idling
109
+
110
+ @parser = parser
81
111
 
82
- parser.reset if @parser
83
- transition(:idle)
84
112
  transition(:connecting)
85
113
  end
86
114
  end
@@ -130,6 +130,7 @@ module HTTPX::Plugins
130
130
  response = request.options.response_class.new(request, status, version, response_headers)
131
131
  response.original_request = original_request
132
132
  response.finish!
133
+ response.mark_as_cached!
133
134
 
134
135
  IO.copy_stream(f, response.body)
135
136
 
@@ -118,7 +118,10 @@ module HTTPX
118
118
 
119
119
  response.copy_from_cached!
120
120
  elsif request.cacheable_verb? && ResponseCache.cacheable_response?(response)
121
- request.options.response_cache_store.set(request, response) unless response.cached?
121
+ unless response.cached?
122
+ log { "caching response for #{request.uri}..." }
123
+ request.options.response_cache_store.set(request, response)
124
+ end
122
125
  end
123
126
 
124
127
  response
@@ -204,7 +207,7 @@ module HTTPX
204
207
  # returns a unique cache key as a String identifying this request
205
208
  def response_cache_key
206
209
  @response_cache_key ||= begin
207
- keys = [@verb, @uri]
210
+ keys = [@verb, @uri.merge(path)]
208
211
 
209
212
  @options.supported_vary_headers.each do |field|
210
213
  value = @headers[field]
@@ -327,6 +330,14 @@ module HTTPX
327
330
  Time.now
328
331
  end
329
332
  end
333
+
334
+ module ResponseBodyMethods
335
+ def decode_chunk(chunk)
336
+ return chunk if @response.cached?
337
+
338
+ super
339
+ end
340
+ end
330
341
  end
331
342
  register_plugin :response_cache, ResponseCache
332
343
  end