httpx 1.8.1 → 1.8.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_8_2.md +30 -0
  3. data/doc/release_notes/1_8_3.md +9 -0
  4. data/doc/release_notes/1_8_4.md +13 -0
  5. data/lib/httpx/adapters/webmock.rb +2 -1
  6. data/lib/httpx/buffer.rb +5 -0
  7. data/lib/httpx/callbacks.rb +2 -2
  8. data/lib/httpx/chainable.rb +2 -2
  9. data/lib/httpx/connection/http1.rb +1 -1
  10. data/lib/httpx/connection/http2.rb +69 -17
  11. data/lib/httpx/connection.rb +58 -14
  12. data/lib/httpx/errors.rb +3 -0
  13. data/lib/httpx/io/ssl.rb +18 -8
  14. data/lib/httpx/options.rb +21 -12
  15. data/lib/httpx/plugins/auth.rb +6 -5
  16. data/lib/httpx/plugins/cookies/cookie.rb +1 -1
  17. data/lib/httpx/plugins/cookies.rb +2 -2
  18. data/lib/httpx/plugins/follow_redirects.rb +28 -14
  19. data/lib/httpx/plugins/grpc/call.rb +4 -4
  20. data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -2
  21. data/lib/httpx/plugins/oauth.rb +4 -2
  22. data/lib/httpx/plugins/persistent.rb +15 -0
  23. data/lib/httpx/plugins/proxy/http.rb +27 -6
  24. data/lib/httpx/plugins/proxy.rb +13 -7
  25. data/lib/httpx/plugins/retries.rb +26 -9
  26. data/lib/httpx/plugins/server_sent_events.rb +2 -2
  27. data/lib/httpx/plugins/stream.rb +2 -2
  28. data/lib/httpx/plugins/tracing.rb +20 -7
  29. data/lib/httpx/request/body.rb +8 -5
  30. data/lib/httpx/request.rb +8 -5
  31. data/lib/httpx/resolver/multi.rb +2 -2
  32. data/lib/httpx/resolver/native.rb +4 -0
  33. data/lib/httpx/resolver/resolver.rb +2 -2
  34. data/lib/httpx/response/body.rb +2 -2
  35. data/lib/httpx/response.rb +7 -7
  36. data/lib/httpx/selector.rb +1 -1
  37. data/lib/httpx/session.rb +7 -3
  38. data/lib/httpx/transcoder/chunker.rb +2 -2
  39. data/lib/httpx/transcoder/json.rb +8 -9
  40. data/lib/httpx/transcoder/utils/deflater.rb +1 -2
  41. data/lib/httpx/version.rb +1 -1
  42. data/sig/connection/http2.rbs +15 -1
  43. data/sig/connection.rbs +4 -2
  44. data/sig/errors.rbs +2 -2
  45. data/sig/io/ssl.rbs +5 -0
  46. data/sig/plugins/auth.rbs +3 -3
  47. data/sig/plugins/persistent.rbs +4 -0
  48. data/sig/plugins/proxy/http.rbs +2 -0
  49. data/sig/plugins/retries.rbs +4 -2
  50. data/sig/plugins/tracing.rbs +1 -1
  51. data/sig/request.rbs +2 -1
  52. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eece37bd091a7fea17ccc62eca63acaee714c08658d081a938fc38495a4c8813
4
- data.tar.gz: 72dcd038803106f08a0317c8d7bc97d22d6cdf5c469ba8f8f9d380d00e8f5ea7
3
+ metadata.gz: daca70f040a0f0afcc413a6eb3b2e586ae56a5405477c0045cf226aaddf5b49c
4
+ data.tar.gz: 53910e19b4bf264a2d3cf4aed4298de57c4c6373f868e3d9e187140c15f2ed63
5
5
  SHA512:
6
- metadata.gz: 5af43571f7da30f709883474fb61ef3661b31fa836d249c423726f4815fb7b2bf5c1281e22ca7ad15c00ed26b2ffc950d521b14a9f5c02e7d95d4198b4b986d0
7
- data.tar.gz: bd73537be9a610b590d7897cd96f7b6213ece24ea7079ce995a3e05cbee363dcf0f575405ea8d2e612a64737e3bbc70dbdc25ab9a24f5b7e154b9a5187974ffe
6
+ metadata.gz: 8b19e3219fb672bfbb45c4e5427c68b9f7ba715003f5b39796a2314bd8b991b58c5bc185767e303557159f04d61d84c19523f25b953b604a74753e55859defb8
7
+ data.tar.gz: 844bf656335bdbc5599003d6ee9724ff54b05478bfa9de37d5e251acff788154d4d7cea4b866b56c7d4c42d07b1afa8ece6982b02c5236d981843f092ee25ceb
@@ -0,0 +1,30 @@
1
+ # 1.8.2
2
+
3
+ ## Improvements
4
+
5
+ * `:proxy` plugin: allow setting `:no_proxy` option to `"*"` (or `["*"]`) to allow bypassing proxy for all requests.
6
+ * Extending sessions with unknown options will now raise an error, instead of silently ignoring them (consistent with the initialization case now):
7
+
8
+ ```ruby
9
+ session = HTTPX.with(unknown_option: 1) # will raise a HTTPX::Error: unknown option: unknown_option
10
+ ```
11
+
12
+ * Use `HTTP2::Connection#closed?` internally (instead of `#state`) to check for connection state; this will, in a future release of `http-2`, seamlessly support the new `:closing` state.
13
+ * Add missing `response` attribute reader to `HTTPX::RequestTimeoutError`.
14
+ * Improve correctness of calling `#dup` on requests and connections.
15
+
16
+ ## Bugfixes
17
+
18
+ * `:auth` plugin: generate token first, then set the "expires_at" timestamp (bearer tokens, specially if JWT, may carry information about when it expires).
19
+ * `:auth` plugin: do not check for token freshness on a retry, instead force token regeneration.
20
+ * `:retries` plugin: do not apply "retry after" delay after a ping timeout error (the ping timeout already implies a delay, so retry on a new connection should be immediate).
21
+ * `:retries` plugin: do not retry on any HTTP/2 frame error (only on GOAWAY or PING frame errors).
22
+ * `:persistent` plugin: do not decrement request remaining retries on ping or GOAWAY frame errors.
23
+ * `:tracing` plugin: set connection initialization time to nil no termination (so that it doesn't get reused on persistent connections after reuse).
24
+ * `:follow_redirects` plugin: fixed issue when a timeout error would be raised but only bubble up to the root request, not properly closing the in-flight follow up request, and leaving the request handling phase in a broken state.
25
+ * datadog adapter: the connection initialization time is now set only on connect or first request received, the reason, making it more accurate.
26
+ * webmock adapter: `.to_timeout` helper will make the request raise a `HTTPX::RequestTimeoutError` instead of a plain `TimeoutError` (otherwise, if using persistent sessions, it'd trigger a retry instead and mess with webmock request count bookkeeping).
27
+ * native resolver: do not transition to `:open` if the socket was closed during resolution (which happens when an error is raised while the query bytes are buffered).
28
+ * fix for when setting a new session callback on an ssl context object which has been frozen after the first connection attempt (done by binding the ssl context callback to an internal ivar which can be changed).
29
+ * connection: protect against `Errno::ETIMEDOUT` errors on socket close by forcing transition to closed.
30
+ * do not set a ping timer for HTTP/1.1 on keep alive flow, as there's no ping and the connection is reestablished.
@@ -0,0 +1,9 @@
1
+ # 1.8.3
2
+
3
+ ## Bugfixes
4
+
5
+ options print a warning instead of raising an error on unknown option
6
+
7
+ the approach introduced in the previous version, of erroring on something people have been accidentally relying on, is too strict in hindsight for a patch version. This was reverted, and instead, a deprecation warning will be printed, to give time for folks to migrate.
8
+
9
+ the format of options messages (exceptions and warnings) also slightly changes to wrap the option in quotes, for proper highlighting.
@@ -0,0 +1,13 @@
1
+ # 1.8.4
2
+
3
+ ## Improvements
4
+
5
+ * send requests which were not sent, or were not processed by the server, when the server sends back a GOAWAY frame (which is deemed recoverable, like when there's no error), after forcing a reconnection (instead of bubbling up the GOAWAY error for all of them).
6
+
7
+ ## Bugfixes
8
+
9
+ * `:retries` plugin: also retry requests which failed due to a RST_STREAM frame with REFUSED_STREAM error code.
10
+ * `:retries` plugin: resend requests which were piped to a closed connection internally.
11
+ * `:proxy` plugin: fix issue related with reconnecting inactive connections shut down by the peer, when used with the `:persistent` plugin, where the CONNECT request wasn't being sent to establish the tunnel.
12
+ * fixed compatibility issue starting with the `json` gem 3.0.0, when calling `HTTPX::Response#json`.
13
+ * avoid bookkepping issue in session, where the `on_response_arrived` on a request may be called more than once (which it can happen in the `:follow_redirects` plugin, where a request may have completed due to an error, while the request is waiting to be resent due to `retry-after`).
@@ -34,7 +34,8 @@ module WebMock
34
34
  end
35
35
 
36
36
  def build_from_webmock_response(request, webmock_response)
37
- return build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
37
+ return build_error_response(request,
38
+ HTTPX::RequestTimeoutError.new(request, webmock_response, 1)) if webmock_response.should_timeout
38
39
 
39
40
  return build_error_response(request, webmock_response.exception) if webmock_response.exception
40
41
 
data/lib/httpx/buffer.rb CHANGED
@@ -46,6 +46,11 @@ module HTTPX
46
46
  def_delegator :@buffer, :<<
47
47
  end
48
48
 
49
+ def initialize_dup(other)
50
+ super
51
+ @buffer = other.instance_variable_get(:@buffer).dup
52
+ end
53
+
49
54
  def full?
50
55
  @buffer.bytesize >= @limit
51
56
  end
@@ -14,9 +14,9 @@ module HTTPX
14
14
  end
15
15
  end
16
16
 
17
- def emit(type, *args)
17
+ def emit(type, ...)
18
18
  log { "emit #{type.inspect} callbacks" } if respond_to?(:log)
19
- callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
19
+ callbacks(type).delete_if { |pr| :delete == pr.call(...) } # rubocop:disable Style/YodaCondition
20
20
  end
21
21
 
22
22
  def callbacks_for?(type)
@@ -13,8 +13,8 @@ module HTTPX
13
13
  end
14
14
 
15
15
  # delegates to the default session (see HTTPX::Session#request).
16
- def request(*args, **options)
17
- branch(default_options).request(*args, **options)
16
+ def request(...)
17
+ branch(default_options).request(...)
18
18
  end
19
19
 
20
20
  def accept(type)
@@ -29,7 +29,7 @@ module HTTPX
29
29
  @version = [1, 1]
30
30
  @pending = []
31
31
  @requests = []
32
- @request = nil
32
+ @callbacks = @request = nil
33
33
  @handshake_completed = @pipelining = false
34
34
  end
35
35
 
@@ -23,14 +23,35 @@ module HTTPX
23
23
  end
24
24
 
25
25
  class GoawayError < Error
26
- def initialize(code = :no_error)
26
+ UNRECOVERABLE_ERRORS = %i[settings_timeout inadequate_security].freeze
27
+
28
+ def initialize(code, last_stream_id)
29
+ @code = code
30
+ @last_stream_id = last_stream_id
27
31
  super(0, code)
28
32
  end
33
+
34
+ def last_stream_id
35
+ return Float::INFINITY if unrecoverable?
36
+
37
+ @last_stream_id
38
+ end
39
+
40
+ def unrecoverable?
41
+ UNRECOVERABLE_ERRORS.include?(@code)
42
+ end
43
+ end
44
+
45
+ class RstStreamError < Error
46
+ end
47
+
48
+ class RefusedStreamError < RstStreamError
29
49
  end
30
50
 
31
51
  attr_reader :streams, :pending
32
52
 
33
53
  def initialize(buffer, options)
54
+ @callbacks = nil
34
55
  @options = options
35
56
  @settings = @options.http2_settings
36
57
  @pending = []
@@ -53,7 +74,7 @@ module HTTPX
53
74
  end
54
75
 
55
76
  def interests
56
- if @connection.state == :closed
77
+ if @connection.closed?
57
78
  return unless @handshake_completed
58
79
 
59
80
  return if @buffer.empty?
@@ -94,7 +115,7 @@ module HTTPX
94
115
  end
95
116
 
96
117
  def close
97
- unless @connection.state == :closed
118
+ unless @connection.closed?
98
119
  @connection.goaway
99
120
  emit(:timeout, @options.timeout[:close_handshake_timeout])
100
121
  end
@@ -102,7 +123,7 @@ module HTTPX
102
123
  end
103
124
 
104
125
  def empty?
105
- @connection.state == :closed || @streams.empty?
126
+ @connection.closed? || @streams.empty?
106
127
  end
107
128
 
108
129
  def exhausted?
@@ -148,18 +169,43 @@ module HTTPX
148
169
  end
149
170
 
150
171
  def handle_error(ex, request = nil)
151
- if ex.is_a?(OperationTimeoutError) && !@handshake_completed && @connection.state != :closed
152
- @connection.goaway(:settings_timeout, "closing due to settings timeout")
153
- emit(:close_handshake)
154
- settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
155
- settings_ex.set_backtrace(ex.backtrace)
156
- ex = settings_ex
157
- end
158
- while (req, _ = @streams.shift)
172
+ last_stream_id = Float::INFINITY
173
+ case ex
174
+ when OperationTimeoutError
175
+ if !@handshake_completed && @connection.state != :closed
176
+ @connection.goaway(:settings_timeout, "closing due to settings timeout")
177
+ emit(:close_handshake)
178
+ settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
179
+ settings_ex.set_backtrace(ex.backtrace)
180
+ ex = settings_ex
181
+ end
182
+ when GoawayError
183
+ last_stream_id = ex.last_stream_id
184
+ end
185
+
186
+ inflight_unprocessed_requests = [] #: Array[Request]
187
+
188
+ while (req, stream = @streams.shift)
159
189
  next if request && request == req
160
190
 
191
+ if stream.id > last_stream_id
192
+ req.transition(:idle)
193
+ # unprocessed request
194
+ inflight_unprocessed_requests << req
195
+
196
+ next
197
+ end
198
+
161
199
  emit(:error, req, ex)
162
200
  end
201
+
202
+ if ex.is_a?(GoawayError) && !ex.unrecoverable?
203
+ # resend unprocessed requests on a different connection
204
+ @pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
205
+ emit(:exhausted, ex) if @pending.any?
206
+ return
207
+ end
208
+
163
209
  while (req = @pending.shift)
164
210
  next if request && request == req
165
211
 
@@ -394,7 +440,14 @@ module HTTPX
394
440
  when :http_1_1_required
395
441
  emit(:error, request, error)
396
442
  else
397
- ex = Error.new(stream.id, error)
443
+ ex =
444
+ case error
445
+ when :refused_stream
446
+ RefusedStreamError.new(stream.id, error)
447
+ else
448
+ RstStreamError.new(stream.id, error)
449
+ end
450
+
398
451
  ex.set_backtrace(caller)
399
452
  response = ErrorResponse.new(request, ex)
400
453
  request.response = response
@@ -431,8 +484,8 @@ module HTTPX
431
484
  send_pending
432
485
  end
433
486
 
434
- def on_close(_last_frame, error, _payload)
435
- is_connection_closed = @connection.state == :closed
487
+ def on_close(last_stream_id, error, _payload)
488
+ is_connection_closed = @connection.closed?
436
489
  if error
437
490
  @buffer.clear if is_connection_closed
438
491
  case error
@@ -441,12 +494,11 @@ module HTTPX
441
494
  emit(:error, request, error)
442
495
  end
443
496
  else
444
- ex = GoawayError.new(error)
497
+ ex = GoawayError.new(error, last_stream_id)
445
498
  ex.set_backtrace(caller)
446
499
 
447
500
  handle_error(ex)
448
501
  teardown
449
-
450
502
  end
451
503
  end
452
504
  return unless is_connection_closed && @streams.empty?
@@ -36,21 +36,25 @@ module HTTPX
36
36
 
37
37
  def_delegator :@write_buffer, :empty?
38
38
 
39
- attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
39
+ attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling,
40
+ :read_buffer, :write_buffer
40
41
 
41
42
  attr_writer :current_selector
42
43
 
43
44
  attr_accessor :current_session, :family
44
45
 
45
- protected :ssl_session, :sibling
46
+ protected :ssl_session, :sibling, :read_buffer, :write_buffer
46
47
 
47
48
  def initialize(uri, options)
48
49
  @current_session = @current_selector = @max_concurrent_requests =
49
50
  @parser = @sibling = @coalesced_connection = @altsvc_connection =
50
- @ping_timer = @family = @io = @ssl_session =
51
- @timeout = @connected_at = @response_received_at = nil
51
+ @callbacks = @ping_timer = @family =
52
+ @io = @ssl_session = @timeout = @connected_at = @response_received_at = nil
52
53
 
53
- @exhausted = @cloned = @main_sibling = false
54
+ @exhausted = @cloned = @main_sibling =
55
+ # variable used to gate against a potential endless loop where the peer continuously closes the connection with
56
+ # GOAWAY frames without ever processing a request.
57
+ @previously_exhausted_with_error = false
54
58
 
55
59
  @options = Options.new(options)
56
60
  @type = initialize_type(uri, @options)
@@ -76,6 +80,29 @@ module HTTPX
76
80
  self.addresses = @options.addresses if @options.addresses
77
81
  end
78
82
 
83
+ # dupped initialization
84
+ def initialize_dup(orig)
85
+ super
86
+ @callbacks = @parser = @sibling = @coalesced_connection = @altsvc_connection = nil
87
+ @origins = orig.origins.dup
88
+ @read_buffer = orig.read_buffer.dup
89
+ @write_buffer = orig.write_buffer.dup
90
+ @inflight = 0
91
+ @pending = []
92
+ transition(:idle)
93
+
94
+ if @io
95
+ # initialize new IO object with the same set of addresses
96
+ addresses = @io.addresses
97
+ @io = nil
98
+ self.addresses = addresses
99
+ end
100
+
101
+ return unless @current_session && @current_selector
102
+
103
+ @current_session.pin(self, @current_selector)
104
+ end
105
+
79
106
  def peer
80
107
  @origin
81
108
  end
@@ -148,11 +175,11 @@ module HTTPX
148
175
 
149
176
  def merge(connection)
150
177
  @origins |= connection.instance_variable_get(:@origins)
151
- if @ssl_session.nil? && connection.ssl_session
152
- @ssl_session = connection.ssl_session
153
- @io.session_new_cb do |sess|
154
- @ssl_session = sess
155
- end if @io
178
+ if @ssl_session.nil? && (ssl_session = connection.ssl_session)
179
+ @ssl_session = ssl_session
180
+ # the socket only needs the merged session if it can still resume it,
181
+ # i.e. if TLS hasn't been negotiated yet.
182
+ @io.ssl_session = ssl_session if @io.is_a?(SSL) && !@io.connected?
156
183
  end
157
184
  connection.purge_pending do |req|
158
185
  req.transition(:idle)
@@ -341,7 +368,6 @@ module HTTPX
341
368
  end
342
369
 
343
370
  transition(:closing)
344
-
345
371
  transition(:closed)
346
372
  end
347
373
 
@@ -688,6 +714,7 @@ module HTTPX
688
714
  AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
689
715
  build_altsvc_connection(alt_origin, origin, alt_params)
690
716
  end
717
+ @previously_exhausted_with_error = false
691
718
  @response_received_at = Utils.now
692
719
  @no_more_requests_counter = 0
693
720
  @inflight -= 1
@@ -703,9 +730,19 @@ module HTTPX
703
730
  parser.on(:promise) do |request, stream|
704
731
  request.emit(:promise, parser, stream)
705
732
  end
706
- parser.on(:exhausted) do
733
+ parser.on(:exhausted) do |error|
707
734
  enqueue_pending_requests_from_parser(parser)
708
735
 
736
+ if error
737
+ if @previously_exhausted_with_error
738
+ @previously_exhausted_with_error = false
739
+ on_error(error)
740
+ next
741
+ else
742
+ @previously_exhausted_with_error = true
743
+ end
744
+ end
745
+
709
746
  @exhausted = true
710
747
  parser.close
711
748
 
@@ -775,14 +812,19 @@ module HTTPX
775
812
  Errno::ECONNRESET,
776
813
  Errno::EADDRNOTAVAIL,
777
814
  Errno::EHOSTUNREACH,
778
- Errno::EINVAL,
779
815
  Errno::ENETUNREACH,
816
+ Errno::EHOSTDOWN,
817
+ Errno::ENETDOWN,
818
+ Errno::EINVAL,
780
819
  Errno::EPIPE,
781
820
  Errno::ENOENT,
782
821
  SocketError,
783
822
  IOError => e
784
823
  on_connect_error(e)
785
- rescue TLSError, ::HTTP2::Error::ProtocolError, ::HTTP2::Error::HandshakeError => e
824
+ rescue TLSError,
825
+ Errno::ETIMEDOUT,
826
+ ::HTTP2::Error::ProtocolError,
827
+ ::HTTP2::Error::HandshakeError => e
786
828
  # connect errors, exit gracefully
787
829
  handle_error(e)
788
830
  handle_connect_error(e) if connecting?
@@ -979,6 +1021,8 @@ module HTTPX
979
1021
 
980
1022
  parser.ping
981
1023
 
1024
+ return unless parser.waiting_for_ping?
1025
+
982
1026
  ping_timeout = @options.timeout[:ping_timeout]
983
1027
 
984
1028
  @ping_timer = @current_selector.after(ping_timeout) do
data/lib/httpx/errors.rb CHANGED
@@ -43,6 +43,9 @@ module HTTPX
43
43
  # The HTTPX::Request request object this exception refers to.
44
44
  attr_reader :request
45
45
 
46
+ # The response object this exception refers to.
47
+ attr_reader :response
48
+
46
49
  # initializes the exception with the +request+ and +response+ it refers to, and the
47
50
  # +timeout+ causing the error, and the
48
51
  def initialize(request, response, timeout)
data/lib/httpx/io/ssl.rb CHANGED
@@ -6,20 +6,19 @@ module HTTPX
6
6
  TLSError = OpenSSL::SSL::SSLError
7
7
 
8
8
  class SSL < TCP
9
- # rubocop:disable Style/MutableConstant
10
- TLS_OPTIONS = { alpn_protocols: %w[h2 http/1.1].freeze }
11
- # https://github.com/jruby/jruby-openssl/issues/284
9
+ tls_options = { alpn_protocols: %w[h2 http/1.1].freeze }
12
10
  # TODO: remove when dropping support for jruby-openssl < 0.15.4
13
- TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby" && JOpenSSL::VERSION < "0.15.4"
14
- # rubocop:enable Style/MutableConstant
15
- TLS_OPTIONS.freeze
11
+ # https://github.com/jruby/jruby-openssl/issues/284
12
+ tls_options[:verify_hostname] = true if RUBY_ENGINE == "jruby" && JOpenSSL::VERSION < "0.15.4"
13
+ TLS_OPTIONS = tls_options.freeze
16
14
 
17
15
  attr_writer :ssl_session
18
16
 
19
17
  def initialize(_, _, options)
20
18
  super
21
19
 
22
- @ssl_session = nil
20
+ @ssl_session = @session_new_cb = nil
21
+
23
22
  ctx_options = TLS_OPTIONS
24
23
  ctx_options = ctx_options.merge(options.ssl) if options.ssl && !options.ssl.empty?
25
24
  @sni_hostname = (ctx_options.delete(:hostname) if ctx_options.key?(:hostname)) || @hostname
@@ -35,6 +34,7 @@ module HTTPX
35
34
  @ctx.session_cache_mode =
36
35
  OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
37
36
  end
37
+ init_session_new_cb
38
38
 
39
39
  yield(self) if block_given?
40
40
  end
@@ -43,14 +43,24 @@ module HTTPX
43
43
  end
44
44
 
45
45
  if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
46
+ # sets the ssl session callback to be picked up by the ssl context.
46
47
  def session_new_cb(&pr)
47
- @ctx.session_new_cb = proc { |_, sess| pr.call(sess) }
48
+ @session_new_cb = pr
49
+ end
50
+
51
+ # sets the ssl context's new session callback, which points at @session_new_cb when available.
52
+ def init_session_new_cb
53
+ @ctx.session_new_cb = proc { |_, sess| @session_new_cb&.call(sess) }
48
54
  end
49
55
  else
50
56
  # session_new_cb not implemented under JRuby
51
57
  def session_new_cb; end
58
+
59
+ def init_session_new_cb; end
52
60
  end
53
61
 
62
+ private :init_session_new_cb
63
+
54
64
  def protocol
55
65
  return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)
56
66
 
data/lib/httpx/options.rb CHANGED
@@ -133,12 +133,12 @@ module HTTPX
133
133
  when Options
134
134
  unknown_options = options.class.options_names - options_names
135
135
 
136
- raise Error, "unknown option: #{unknown_options.first}" unless unknown_options.empty?
136
+ raise Error, "unknown option: `:#{unknown_options.first}`" unless unknown_options.empty?
137
137
 
138
138
  DEFAULT_OPTIONS.merge(options)
139
139
  else
140
140
  options.each_key do |k|
141
- raise Error, "unknown option: #{k}" unless options_names.include?(k)
141
+ raise Error, "unknown option: `:#{k}`" unless options_names.include?(k)
142
142
  end
143
143
 
144
144
  options.empty? ? DEFAULT_OPTIONS : DEFAULT_OPTIONS.merge(options)
@@ -184,7 +184,7 @@ module HTTPX
184
184
  cache_type.respond_to?(:get) &&
185
185
  cache_type.respond_to?(:set) &&
186
186
  cache_type.respond_to?(:evict)
187
- raise TypeError, ":resolver_cache must be a compatible resolver cache and implement #get, #set and #evict"
187
+ raise TypeError, "`:resolver_cache` must be a compatible resolver cache and implement `#get`, `#set` and `#evict`"
188
188
  end
189
189
 
190
190
  cache_type #: Object & Resolver::_Cache
@@ -257,12 +257,21 @@ module HTTPX
257
257
 
258
258
  other_opts = opts_names
259
259
  else
260
- other_opts = other # : Hash[Symbol, untyped]
260
+ other_opts = other #: Hash[Symbol, untyped]
261
261
  other_opts = Hash[other] unless other.is_a?(Hash)
262
262
 
263
263
  return self if other_opts.empty?
264
264
 
265
- return self if other_opts.all? { |opt, v| !respond_to?(opt) || public_send(opt) == v }
265
+ return self if other_opts.all? do |opt, v|
266
+ unless respond_to?(opt)
267
+ # TODO: do this instead in a major or minor version bump
268
+ # raise Error, "unknown option: #{opt}" unless respond_to?(opt)
269
+ warn "DEPRECATION WARNING: unknown option: `:#{opt}`. an exception will be raised in a future version."
270
+ next(true)
271
+ end
272
+
273
+ !respond_to?(opt) || public_send(opt) == v
274
+ end
266
275
  end
267
276
 
268
277
  opts = dup
@@ -400,7 +409,7 @@ module HTTPX
400
409
  # converts +v+ into an Integer before setting the +#{option}+ option.
401
410
  private def option_#{option}(value) # private def option_max_requests(v)
402
411
  value = Integer(value) unless value.respond_to?(:infinite?) && value.infinite?
403
- raise TypeError, ":#{option} must be positive" unless value.positive? # raise TypeError, ":max_requests must be positive" unless value.positive?
412
+ raise TypeError, "`:#{option}` must be positive" unless value.positive? # raise TypeError, ":max_requests must be positive" unless value.positive?
404
413
 
405
414
  value
406
415
  end
@@ -452,11 +461,11 @@ module HTTPX
452
461
 
453
462
  # Validate keys and values
454
463
  timeout_hash.each do |key, val|
455
- raise TypeError, "invalid timeout: :#{key}" unless default_timeouts.key?(key)
464
+ raise TypeError, "invalid timeout: `:#{key}`" unless default_timeouts.key?(key)
456
465
 
457
466
  next if val.nil?
458
467
 
459
- raise TypeError, ":#{key} must be numeric" unless val.is_a?(Numeric)
468
+ raise TypeError, "`:#{key}` must be numeric" unless val.is_a?(Numeric)
460
469
  end
461
470
 
462
471
  timeout_hash
@@ -486,15 +495,15 @@ module HTTPX
486
495
  when Symbol
487
496
  meth = :"resolver_#{resolver_type}_class"
488
497
 
489
- raise TypeError, ":resolver_class must be a supported type" unless respond_to?(meth)
498
+ raise TypeError, "`:resolver_class` must be a supported type" unless respond_to?(meth)
490
499
 
491
500
  resolver_type
492
501
  when Class
493
- raise TypeError, ":resolver_class must be a subclass of `#{Resolver::Resolver}`" unless resolver_type < Resolver::Resolver
502
+ raise TypeError, "`:resolver_class` must be a subclass of `#{Resolver::Resolver}`" unless resolver_type < Resolver::Resolver
494
503
 
495
504
  resolver_type
496
505
  else
497
- raise TypeError, ":resolver_class must be a supported type"
506
+ raise TypeError, "`:resolver_class` must be a supported type"
498
507
  end
499
508
  end
500
509
 
@@ -509,7 +518,7 @@ module HTTPX
509
518
  cache_type.respond_to?(:get) &&
510
519
  cache_type.respond_to?(:set) &&
511
520
  cache_type.respond_to?(:evict)
512
- raise TypeError, ":resolver_cache must be a compatible resolver cache and implement #resolve, #get, #set and #evict"
521
+ raise TypeError, "`:resolver_cache` must be a compatible resolver cache and implement `#resolve`, `#get`, `#set` and `#evict`"
513
522
  end
514
523
  end
515
524
 
@@ -19,6 +19,8 @@ module HTTPX
19
19
  # adds support for the following options:
20
20
  #
21
21
  # :auth_header_value :: the token to use as a string, or a callable which returns a string when called.
22
+ # the callable is called a request, and a boolean: when true, the user must regenerate a token, otherwise it
23
+ # may probe for token freshness and return the same token.
22
24
  # :auth_header_type :: the authentication type to use in the "authorization" header value (i.e. "Bearer", "Digest"...)
23
25
  # :auth_header_expires_at :: timestamp at which the auth header will be discarded. should be a callable (like a proc)
24
26
  # receiving the request as an argument, and should return either a Time object, or an integer (UNIX time).
@@ -96,9 +98,8 @@ module HTTPX
96
98
  auth_header_value = @auth_header_value_mtx.synchronize do
97
99
  try_invalidate_auth_header_value
98
100
 
99
- @auth_header_value ||= begin
101
+ @auth_header_value ||= generate_auth_token(request, false).tap do
100
102
  set_auth_header_expires_at(request)
101
- generate_auth_token
102
103
  end
103
104
  end
104
105
 
@@ -115,10 +116,10 @@ module HTTPX
115
116
  @auth_header_value = @auth_header_expires_at = nil
116
117
  end
117
118
 
118
- def generate_auth_token
119
+ def generate_auth_token(request, should_regenerate)
119
120
  return unless (auth_value = @options.auth_header_value)
120
121
 
121
- auth_value = auth_value.call(self) if dynamic_auth_token?(auth_value)
122
+ auth_value = auth_value.call(request, should_regenerate) if dynamic_auth_token?(auth_value)
122
123
 
123
124
  auth_value
124
125
  end
@@ -196,7 +197,7 @@ module HTTPX
196
197
  # use whatever was generated for it.
197
198
  @auth_header_value_mtx.synchronize do
198
199
  if request.auth_token_value == @auth_header_value
199
- @auth_header_value = generate_auth_token
200
+ @auth_header_value = generate_auth_token(request, true)
200
201
  set_auth_header_expires_at(request)
201
202
  end
202
203
  end
@@ -68,7 +68,7 @@ module HTTPX
68
68
  end
69
69
 
70
70
  class << self
71
- def new(cookie, *args)
71
+ def new(cookie, *)
72
72
  case cookie
73
73
  when self
74
74
  cookie
@@ -46,8 +46,8 @@ module HTTPX
46
46
 
47
47
  # factory method to return a Jar to the user, which can then manipulate
48
48
  # externally to the session.
49
- def make_jar(*args)
50
- Jar.new(*args)
49
+ def make_jar(...)
50
+ Jar.new(...)
51
51
  end
52
52
 
53
53
  private