httpx 0.17.0 → 0.18.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_18_0.md +69 -0
  3. data/lib/httpx/adapters/datadog.rb +1 -1
  4. data/lib/httpx/adapters/faraday.rb +5 -3
  5. data/lib/httpx/adapters/webmock.rb +7 -1
  6. data/lib/httpx/altsvc.rb +2 -2
  7. data/lib/httpx/chainable.rb +3 -3
  8. data/lib/httpx/connection/http1.rb +3 -3
  9. data/lib/httpx/connection/http2.rb +6 -4
  10. data/lib/httpx/connection.rb +67 -70
  11. data/lib/httpx/domain_name.rb +1 -1
  12. data/lib/httpx/extensions.rb +50 -4
  13. data/lib/httpx/io/ssl.rb +1 -1
  14. data/lib/httpx/io/tls.rb +7 -7
  15. data/lib/httpx/loggable.rb +5 -5
  16. data/lib/httpx/options.rb +7 -7
  17. data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
  18. data/lib/httpx/plugins/aws_sigv4.rb +9 -11
  19. data/lib/httpx/plugins/compression.rb +5 -3
  20. data/lib/httpx/plugins/cookies/jar.rb +1 -1
  21. data/lib/httpx/plugins/expect.rb +7 -3
  22. data/lib/httpx/plugins/grpc/message.rb +2 -2
  23. data/lib/httpx/plugins/grpc.rb +3 -3
  24. data/lib/httpx/plugins/internal_telemetry.rb +8 -8
  25. data/lib/httpx/plugins/multipart.rb +2 -2
  26. data/lib/httpx/plugins/response_cache/store.rb +55 -0
  27. data/lib/httpx/plugins/response_cache.rb +88 -0
  28. data/lib/httpx/plugins/retries.rb +22 -3
  29. data/lib/httpx/plugins/stream.rb +1 -1
  30. data/lib/httpx/pool.rb +39 -13
  31. data/lib/httpx/request.rb +6 -6
  32. data/lib/httpx/resolver/https.rb +5 -7
  33. data/lib/httpx/resolver/native.rb +4 -2
  34. data/lib/httpx/resolver/system.rb +2 -0
  35. data/lib/httpx/resolver.rb +2 -2
  36. data/lib/httpx/response.rb +23 -14
  37. data/lib/httpx/selector.rb +5 -17
  38. data/lib/httpx/session.rb +7 -2
  39. data/lib/httpx/session2.rb +1 -1
  40. data/lib/httpx/timers.rb +84 -0
  41. data/lib/httpx/transcoder/body.rb +2 -1
  42. data/lib/httpx/transcoder/form.rb +1 -1
  43. data/lib/httpx/transcoder/json.rb +1 -1
  44. data/lib/httpx/utils.rb +8 -0
  45. data/lib/httpx/version.rb +1 -1
  46. data/lib/httpx.rb +1 -0
  47. data/sig/chainable.rbs +1 -0
  48. data/sig/connection.rbs +12 -6
  49. data/sig/plugins/aws_sdk_authentication.rbs +22 -4
  50. data/sig/plugins/response_cache.rbs +35 -0
  51. data/sig/plugins/retries.rbs +3 -0
  52. data/sig/pool.rbs +6 -0
  53. data/sig/resolver/native.rbs +3 -4
  54. data/sig/resolver/system.rbs +2 -0
  55. data/sig/response.rbs +3 -2
  56. data/sig/timers.rbs +32 -0
  57. data/sig/utils.rbs +4 -0
  58. metadata +10 -17
@@ -8,6 +8,23 @@ module HTTPX
8
8
  # It requires the "aws-sdk-core" gem.
9
9
  #
10
10
  module AwsSdkAuthentication
11
+ # Mock configuration, to be used only when resolving credentials
12
+ class Configuration
13
+ attr_reader :profile
14
+
15
+ def initialize(profile)
16
+ @profile = profile
17
+ end
18
+
19
+ def respond_to_missing?(*)
20
+ true
21
+ end
22
+
23
+ def method_missing(*)
24
+ nil
25
+ end
26
+ end
27
+
11
28
  #
12
29
  # encapsulates access to an AWS SDK credentials store.
13
30
  #
@@ -30,23 +47,8 @@ module HTTPX
30
47
  end
31
48
 
32
49
  class << self
33
- attr_reader :credentials, :region
34
-
35
50
  def load_dependencies(_klass)
36
51
  require "aws-sdk-core"
37
-
38
- client = Class.new(Seahorse::Client::Base) do
39
- @identifier = :httpx
40
- set_api(Aws::S3::ClientApi::API)
41
- add_plugin(Aws::Plugins::CredentialsConfiguration)
42
- add_plugin(Aws::Plugins::RegionalEndpoint)
43
- class << self
44
- attr_reader :identifier
45
- end
46
- end.new
47
-
48
- @credentials = Credentials.new(client.config[:credentials])
49
- @region = client.config[:region]
50
52
  end
51
53
 
52
54
  def configure(klass)
@@ -56,6 +58,26 @@ module HTTPX
56
58
  def extra_options(options)
57
59
  options.merge(max_concurrent_requests: 1)
58
60
  end
61
+
62
+ def credentials(profile)
63
+ mock_configuration = Configuration.new(profile)
64
+ Credentials.new(Aws::CredentialProviderChain.new(mock_configuration).resolve)
65
+ end
66
+
67
+ def region(profile)
68
+ # https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-core/lib/aws-sdk-core/plugins/regional_endpoint.rb#L62
69
+ keys = %w[AWS_REGION AMAZON_REGION AWS_DEFAULT_REGION]
70
+ env_region = ENV.values_at(*keys).compact.first
71
+ env_region = nil if env_region == ""
72
+ cfg_region = Aws.shared_config.region(profile: profile)
73
+ env_region || cfg_region
74
+ end
75
+ end
76
+
77
+ module OptionsMethods
78
+ def option_aws_profile(value)
79
+ String(value)
80
+ end
59
81
  end
60
82
 
61
83
  module InstanceMethods
@@ -64,9 +86,11 @@ module HTTPX
64
86
  # aws_authentication(credentials: Aws::Credentials.new('akid', 'secret'))
65
87
  # aws_authentication()
66
88
  #
67
- def aws_sdk_authentication(**options)
68
- credentials = AwsSdkAuthentication.credentials
69
- region = AwsSdkAuthentication.region
89
+ def aws_sdk_authentication(
90
+ credentials: AwsSdkAuthentication.credentials(@options.aws_profile),
91
+ region: AwsSdkAuthentication.region(@options.aws_profile),
92
+ **options
93
+ )
70
94
 
71
95
  aws_sigv4_authentication(
72
96
  credentials: credentials,
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
- require "aws-sdk-s3"
5
-
6
3
  module HTTPX
7
4
  module Plugins
8
5
  #
@@ -75,16 +72,16 @@ module HTTPX
75
72
 
76
73
  # canonical request
77
74
  creq = "#{request.verb.to_s.upcase}" \
78
- "\n#{request.canonical_path}" \
79
- "\n#{request.canonical_query}" \
80
- "\n#{canonical_headers}" \
81
- "\n#{signed_headers}" \
82
- "\n#{content_hashed}"
75
+ "\n#{request.canonical_path}" \
76
+ "\n#{request.canonical_query}" \
77
+ "\n#{canonical_headers}" \
78
+ "\n#{signed_headers}" \
79
+ "\n#{content_hashed}"
83
80
 
84
81
  credential_scope = "#{date}" \
85
- "/#{@region}" \
86
- "/#{@service}" \
87
- "/#{lower_provider_prefix}_request"
82
+ "/#{@region}" \
83
+ "/#{@service}" \
84
+ "/#{lower_provider_prefix}_request"
88
85
 
89
86
  algo_line = "#{upper_provider_prefix}-HMAC-#{@algorithm}"
90
87
  # string to sign
@@ -142,6 +139,7 @@ module HTTPX
142
139
 
143
140
  class << self
144
141
  def load_dependencies(*)
142
+ require "set"
145
143
  require "digest/sha2"
146
144
  require "openssl"
147
145
  end
@@ -72,6 +72,8 @@ module HTTPX
72
72
  end
73
73
 
74
74
  module ResponseBodyMethods
75
+ using ArrayExtensions
76
+
75
77
  attr_reader :encodings
76
78
 
77
79
  def initialize(*)
@@ -90,7 +92,7 @@ module HTTPX
90
92
  Float::INFINITY
91
93
  end
92
94
 
93
- @_inflaters = @headers.get("content-encoding").map do |encoding|
95
+ @_inflaters = @headers.get("content-encoding").filter_map do |encoding|
94
96
  next if encoding == "identity"
95
97
 
96
98
  inflater = @options.encodings.registry(encoding).inflater(compressed_length)
@@ -100,7 +102,7 @@ module HTTPX
100
102
 
101
103
  @encodings << encoding
102
104
  inflater
103
- end.compact
105
+ end
104
106
 
105
107
  # this can happen if the only declared encoding is "identity"
106
108
  remove_instance_variable(:@_inflaters) if @_inflaters.empty?
@@ -134,7 +136,7 @@ module HTTPX
134
136
  end
135
137
 
136
138
  def each(&blk)
137
- return enum_for(__method__) unless block_given?
139
+ return enum_for(__method__) unless blk
138
140
 
139
141
  return deflate(&blk) if @buffer.size.zero?
140
142
 
@@ -55,7 +55,7 @@ module HTTPX
55
55
  end
56
56
 
57
57
  def each(uri = nil, &blk)
58
- return enum_for(__method__, uri) unless block_given?
58
+ return enum_for(__method__, uri) unless blk
59
59
 
60
60
  return @cookies.each(&blk) unless uri
61
61
 
@@ -69,9 +69,14 @@ module HTTPX
69
69
  end
70
70
 
71
71
  module ConnectionMethods
72
- def send(request)
72
+ def send_request_to_parser(request)
73
+ super
74
+
75
+ return unless request.headers["expect"] == "100-continue"
76
+
73
77
  request.once(:expect) do
74
- @timers.after(@options.expect_timeout) do
78
+ @timers.after(request.options.expect_timeout) do
79
+ # expect timeout expired
75
80
  if request.state == :expect && !request.expects?
76
81
  Expect.no_expect_store << request.origin
77
82
  request.headers.delete("expect")
@@ -79,7 +84,6 @@ module HTTPX
79
84
  end
80
85
  end
81
86
  end
82
- super
83
87
  end
84
88
  end
85
89
 
@@ -17,7 +17,7 @@ module HTTPX
17
17
 
18
18
  # lazy decodes a grpc stream response
19
19
  def stream(response, &block)
20
- return enum_for(__method__, response) unless block_given?
20
+ return enum_for(__method__, response) unless block
21
21
 
22
22
  response.each do |frame|
23
23
  decode(frame, encodings: response.headers.get("grpc-encoding"), encoders: response.encoders, &block)
@@ -57,7 +57,7 @@ module HTTPX
57
57
 
58
58
  yield data
59
59
 
60
- message = message.byteslice(5 + size..-1)
60
+ message = message.byteslice((5 + size)..-1)
61
61
  end
62
62
  end
63
63
 
@@ -143,9 +143,9 @@ module HTTPX
143
143
 
144
144
  session_class = Class.new(self.class) do
145
145
  class_eval(<<-OUT, __FILE__, __LINE__ + 1)
146
- def #{rpc_name}(input, **opts)
147
- rpc_execute("#{rpc_name}", input, **opts)
148
- end
146
+ def #{rpc_name}(input, **opts) # def grpc_action(input, **opts)
147
+ rpc_execute("#{rpc_name}", input, **opts) # rpc_execute("grpc_action", input, **opts)
148
+ end # end
149
149
  OUT
150
150
  end
151
151
 
@@ -44,6 +44,11 @@ module HTTPX
44
44
  meter_elapsed_time("Session: initialized!!!")
45
45
  end
46
46
 
47
+ def close(*)
48
+ super
49
+ meter_elapsed_time("Session -> close")
50
+ end
51
+
47
52
  private
48
53
 
49
54
  def build_requests(*)
@@ -55,11 +60,6 @@ module HTTPX
55
60
  meter_elapsed_time("Session -> response") if response
56
61
  response
57
62
  end
58
-
59
- def close(*)
60
- super
61
- meter_elapsed_time("Session -> close")
62
- end
63
63
  end
64
64
 
65
65
  module RequestMethods
@@ -69,9 +69,9 @@ module HTTPX
69
69
  end
70
70
 
71
71
  def transition(nextstate)
72
- state = @state
72
+ prev_state = @state
73
73
  super
74
- meter_elapsed_time("Request##{object_id}[#{@verb} #{@uri}: #{state}] -> #{nextstate}") if nextstate == @state
74
+ meter_elapsed_time("Request##{object_id}[#{@verb} #{@uri}: #{prev_state}] -> #{@state}") if prev_state != @state
75
75
  end
76
76
  end
77
77
 
@@ -84,7 +84,7 @@ module HTTPX
84
84
  def transition(nextstate)
85
85
  state = @state
86
86
  super
87
- meter_elapsed_time("Connection[#{@origin}]: #{state} -> #{nextstate}") if nextstate == @state
87
+ meter_elapsed_time("Connection##{object_id}[#{@origin}]: #{state} -> #{nextstate}") if nextstate == @state
88
88
  end
89
89
  end
90
90
  end
@@ -29,8 +29,8 @@ module HTTPX
29
29
  # in order not to break legacy code, we'll keep loading http/form_data for them.
30
30
  require "http/form_data"
31
31
  warn "httpx: http/form_data is no longer a requirement to use HTTPX :multipart plugin. See migration instructions under" \
32
- "https://honeyryderchuck.gitlab.io/httpx/wiki/Multipart-Uploads.html#notes. \n\n" \
33
- "If you'd like to stop seeing this message, require 'http/form_data' yourself."
32
+ "https://honeyryderchuck.gitlab.io/httpx/wiki/Multipart-Uploads.html#notes. \n\n" \
33
+ "If you'd like to stop seeing this message, require 'http/form_data' yourself."
34
34
  end
35
35
  rescue LoadError
36
36
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module HTTPX::Plugins
6
+ module ResponseCache
7
+ class Store
8
+ extend Forwardable
9
+
10
+ def_delegator :@store, :clear
11
+
12
+ def initialize
13
+ @store = {}
14
+ end
15
+
16
+ def lookup(uri)
17
+ @store[uri]
18
+ end
19
+
20
+ def cached?(uri)
21
+ @store.key?(uri)
22
+ end
23
+
24
+ def cache(uri, response)
25
+ @store[uri] = response
26
+ end
27
+
28
+ def prepare(request)
29
+ cached_response = @store[request.uri]
30
+
31
+ return unless cached_response
32
+
33
+ original_request = cached_response.instance_variable_get(:@request)
34
+
35
+ if (vary = cached_response.headers["vary"])
36
+ if vary == "*"
37
+ return unless request.headers.same_headers?(original_request.headers)
38
+ else
39
+ return unless vary.split(/ *, */).all? do |cache_field|
40
+ !original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
41
+ end
42
+ end
43
+ end
44
+
45
+ if !request.headers.key?("if-modified-since") && (last_modified = cached_response.headers["last-modified"])
46
+ request.headers.add("if-modified-since", last_modified)
47
+ end
48
+
49
+ if !request.headers.key?("if-none-match") && (etag = cached_response.headers["etag"]) # rubocop:disable Style/GuardClause
50
+ request.headers.add("if-none-match", etag)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for retrying requests when certain errors happen.
7
+ #
8
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Response-Cache
9
+ #
10
+ module ResponseCache
11
+ CACHEABLE_VERBS = %i[get head].freeze
12
+ private_constant :CACHEABLE_VERBS
13
+
14
+ class << self
15
+ def load_dependencies(*)
16
+ require_relative "response_cache/store"
17
+ end
18
+
19
+ def cacheable_request?(request)
20
+ CACHEABLE_VERBS.include?(request.verb)
21
+ end
22
+
23
+ def cacheable_response?(response)
24
+ response.is_a?(Response) &&
25
+ # partial responses shall not be cached, only full ones.
26
+ response.status != 206 && (
27
+ response.headers.key?("etag") || response.headers.key?("last-modified-at")
28
+ )
29
+ end
30
+
31
+ def cached_response?(response)
32
+ response.is_a?(Response) && response.status == 304
33
+ end
34
+
35
+ def extra_options(options)
36
+ options.merge(response_cache_store: Store.new)
37
+ end
38
+ end
39
+
40
+ module OptionsMethods
41
+ def option_response_cache_store(value)
42
+ raise TypeError, "must be an instance of #{Store}" unless value.is_a?(Store)
43
+
44
+ value
45
+ end
46
+ end
47
+
48
+ module InstanceMethods
49
+ def clear_response_cache
50
+ @options.response_cache_store.clear
51
+ end
52
+
53
+ def build_request(*)
54
+ request = super
55
+ return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request.uri)
56
+
57
+ @options.response_cache_store.prepare(request)
58
+
59
+ request
60
+ end
61
+
62
+ def fetch_response(request, *)
63
+ response = super
64
+
65
+ if response && ResponseCache.cached_response?(response)
66
+ log { "returning cached response for #{request.uri}" }
67
+ cached_response = @options.response_cache_store.lookup(request.uri)
68
+
69
+ response.copy_from_cached(cached_response)
70
+ end
71
+
72
+ @options.response_cache_store.cache(request.uri, response) if response && ResponseCache.cacheable_response?(response)
73
+
74
+ response
75
+ end
76
+ end
77
+
78
+ module ResponseMethods
79
+ def copy_from_cached(other)
80
+ @body = other.body
81
+
82
+ @body.__send__(:rewind)
83
+ end
84
+ end
85
+ end
86
+ register_plugin :response_cache, ResponseCache
87
+ end
88
+ end
@@ -22,9 +22,16 @@ module HTTPX
22
22
  Parser::Error,
23
23
  Errno::EINVAL,
24
24
  Errno::ETIMEDOUT].freeze
25
+ DEFAULT_JITTER = ->(interval) { interval * (0.5 * (1 + rand)) }
25
26
 
26
- def self.extra_options(options)
27
- options.merge(max_retries: MAX_RETRIES)
27
+ if ENV.key?("HTTPX_NO_JITTER")
28
+ def self.extra_options(options)
29
+ options.merge(max_retries: MAX_RETRIES)
30
+ end
31
+ else
32
+ def self.extra_options(options)
33
+ options.merge(max_retries: MAX_RETRIES, retry_jitter: DEFAULT_JITTER)
34
+ end
28
35
  end
29
36
 
30
37
  module OptionsMethods
@@ -38,6 +45,13 @@ module HTTPX
38
45
  value
39
46
  end
40
47
 
48
+ def option_retry_jitter(value)
49
+ # return early if callable
50
+ raise TypeError, ":retry_jitter must be callable" unless value.respond_to?(:call)
51
+
52
+ value
53
+ end
54
+
41
55
  def option_max_retries(value)
42
56
  num = Integer(value)
43
57
  raise TypeError, ":max_retries must be positive" unless num.positive?
@@ -87,10 +101,15 @@ module HTTPX
87
101
  retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
88
102
 
89
103
  if retry_after
104
+ # apply jitter
105
+ if (jitter = request.options.retry_jitter)
106
+ retry_after = jitter.call(retry_after)
107
+ end
90
108
 
109
+ retry_start = Utils.now
91
110
  log { "retrying after #{retry_after} secs..." }
92
111
  pool.after(retry_after) do
93
- log { "retrying!!" }
112
+ log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
94
113
  connection = find_connection(request, connections, options)
95
114
  connection.send(request)
96
115
  end
@@ -9,7 +9,7 @@ module HTTPX
9
9
  end
10
10
 
11
11
  def each(&block)
12
- return enum_for(__method__) unless block_given?
12
+ return enum_for(__method__) unless block
13
13
 
14
14
  raise Error, "response already streamed" if @response
15
15
 
data/lib/httpx/pool.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
- require "timers"
5
4
  require "httpx/selector"
6
5
  require "httpx/connection"
7
6
  require "httpx/resolver"
8
7
 
9
8
  module HTTPX
10
9
  class Pool
10
+ using ArrayExtensions
11
11
  extend Forwardable
12
12
 
13
13
  def_delegator :@timers, :after
@@ -15,7 +15,7 @@ module HTTPX
15
15
  def initialize
16
16
  @resolvers = {}
17
17
  @_resolver_ios = {}
18
- @timers = Timers::Group.new
18
+ @timers = Timers.new
19
19
  @selector = Selector.new
20
20
  @connections = []
21
21
  @connected_connections = 0
@@ -27,15 +27,18 @@ module HTTPX
27
27
 
28
28
  def next_tick
29
29
  catch(:jump_tick) do
30
- timeout = [next_timeout, @timers.wait_interval].compact.min
30
+ timeout = next_timeout
31
31
  if timeout && timeout.negative?
32
32
  @timers.fire
33
33
  throw(:jump_tick)
34
34
  end
35
35
 
36
- @selector.select(timeout, &:call)
37
-
38
- @timers.fire
36
+ begin
37
+ @selector.select(timeout, &:call)
38
+ @timers.fire
39
+ rescue TimeoutError => e
40
+ @timers.fire(e)
41
+ end
39
42
  end
40
43
  rescue StandardError => e
41
44
  @connections.each do |connection|
@@ -64,6 +67,16 @@ module HTTPX
64
67
  connection.on(:open) do
65
68
  @connected_connections += 1
66
69
  end
70
+ connection.on(:activate) do
71
+ select_connection(connection)
72
+ end
73
+ end
74
+
75
+ def deactivate(connections)
76
+ connections.each do |connection|
77
+ connection.deactivate
78
+ deselect_connection(connection) if connection.state == :inactive
79
+ end
67
80
  end
68
81
 
69
82
  # opens a connection to the IP reachable through +uri+.
@@ -81,7 +94,7 @@ module HTTPX
81
94
  def resolve_connection(connection)
82
95
  @connections << connection unless @connections.include?(connection)
83
96
 
84
- if connection.addresses || connection.state == :open
97
+ if connection.addresses || connection.open?
85
98
  #
86
99
  # there are two cases in which we want to activate initialization of
87
100
  # connection immediately:
@@ -98,7 +111,7 @@ module HTTPX
98
111
  resolver << connection
99
112
  return if resolver.empty?
100
113
 
101
- @_resolver_ios[resolver] ||= @selector.register(resolver)
114
+ @_resolver_ios[resolver] ||= select_connection(resolver)
102
115
  end
103
116
 
104
117
  def on_resolver_connection(connection)
@@ -107,7 +120,7 @@ module HTTPX
107
120
  end
108
121
  return register_connection(connection) unless found_connection
109
122
 
110
- if found_connection.state == :open
123
+ if found_connection.open?
111
124
  coalesce_connections(found_connection, connection)
112
125
  throw(:coalesced, found_connection)
113
126
  else
@@ -129,7 +142,7 @@ module HTTPX
129
142
 
130
143
  @resolvers.delete(resolver_type)
131
144
 
132
- @selector.deregister(resolver)
145
+ deselect_connection(resolver)
133
146
  @_resolver_ios.delete(resolver)
134
147
  resolver.close unless resolver.closed?
135
148
  end
@@ -140,7 +153,7 @@ module HTTPX
140
153
  # consider it connected already.
141
154
  @connected_connections += 1
142
155
  end
143
- @selector.register(connection)
156
+ select_connection(connection)
144
157
  connection.on(:close) do
145
158
  unregister_connection(connection)
146
159
  end
@@ -148,10 +161,18 @@ module HTTPX
148
161
 
149
162
  def unregister_connection(connection)
150
163
  @connections.delete(connection)
151
- @selector.deregister(connection)
164
+ deselect_connection(connection)
152
165
  @connected_connections -= 1
153
166
  end
154
167
 
168
+ def select_connection(connection)
169
+ @selector.register(connection)
170
+ end
171
+
172
+ def deselect_connection(connection)
173
+ @selector.deregister(connection)
174
+ end
175
+
155
176
  def coalesce_connections(conn1, conn2)
156
177
  if conn1.coalescable?(conn2)
157
178
  conn1.merge(conn2)
@@ -162,7 +183,11 @@ module HTTPX
162
183
  end
163
184
 
164
185
  def next_timeout
165
- @resolvers.values.reject(&:closed?).map(&:timeout).compact.min || @connections.map(&:timeout).compact.min
186
+ [
187
+ @timers.wait_interval,
188
+ *@resolvers.values.reject(&:closed?).filter_map(&:timeout),
189
+ *@connections.filter_map(&:timeout),
190
+ ].compact.min
166
191
  end
167
192
 
168
193
  def find_resolver_for(connection)
@@ -172,6 +197,7 @@ module HTTPX
172
197
 
173
198
  @resolvers[resolver_type] ||= begin
174
199
  resolver = resolver_type.new(connection_options)
200
+ resolver.pool = self if resolver.respond_to?(:pool=)
175
201
  resolver.on(:resolve, &method(:on_resolver_connection))
176
202
  resolver.on(:error, &method(:on_resolver_error))
177
203
  resolver.on(:close) { on_resolver_close(resolver) }
data/lib/httpx/request.rb CHANGED
@@ -148,10 +148,10 @@ module HTTPX
148
148
  # :nocov:
149
149
  def inspect
150
150
  "#<HTTPX::Request:#{object_id} " \
151
- "#{@verb.to_s.upcase} " \
152
- "#{uri} " \
153
- "@headers=#{@headers} " \
154
- "@body=#{@body}>"
151
+ "#{@verb.to_s.upcase} " \
152
+ "#{uri} " \
153
+ "@headers=#{@headers} " \
154
+ "@body=#{@body}>"
155
155
  end
156
156
  # :nocov:
157
157
 
@@ -181,7 +181,7 @@ module HTTPX
181
181
  end
182
182
 
183
183
  def each(&block)
184
- return enum_for(__method__) unless block_given?
184
+ return enum_for(__method__) unless block
185
185
  return if @body.nil?
186
186
 
187
187
  body = stream(@body)
@@ -236,7 +236,7 @@ module HTTPX
236
236
  # :nocov:
237
237
  def inspect
238
238
  "#<HTTPX::Request::Body:#{object_id} " \
239
- "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
239
+ "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
240
240
  end
241
241
  # :nocov:
242
242
  end