httpx 0.10.0 → 0.10.1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_10_1.md +39 -0
  3. data/lib/httpx/chainable.rb +7 -6
  4. data/lib/httpx/connection.rb +4 -15
  5. data/lib/httpx/connection/http1.rb +14 -1
  6. data/lib/httpx/connection/http2.rb +11 -12
  7. data/lib/httpx/errors.rb +1 -1
  8. data/lib/httpx/plugins/multipart.rb +15 -1
  9. data/lib/httpx/plugins/proxy.rb +16 -2
  10. data/lib/httpx/plugins/proxy/socks4.rb +14 -16
  11. data/lib/httpx/pool.rb +8 -14
  12. data/lib/httpx/request.rb +1 -1
  13. data/lib/httpx/resolver.rb +0 -2
  14. data/lib/httpx/resolver/https.rb +15 -22
  15. data/lib/httpx/resolver/native.rb +12 -13
  16. data/lib/httpx/resolver/resolver_mixin.rb +4 -2
  17. data/lib/httpx/resolver/system.rb +2 -2
  18. data/lib/httpx/selector.rb +8 -13
  19. data/lib/httpx/session.rb +9 -3
  20. data/lib/httpx/transcoder.rb +18 -0
  21. data/lib/httpx/transcoder/form.rb +9 -1
  22. data/lib/httpx/version.rb +1 -1
  23. data/sig/connection.rbs +84 -1
  24. data/sig/connection/http1.rbs +66 -0
  25. data/sig/connection/http2.rbs +74 -0
  26. data/sig/httpx.rbs +1 -0
  27. data/sig/options.rbs +3 -3
  28. data/sig/plugins/basic_authentication.rbs +1 -1
  29. data/sig/plugins/compression.rbs +1 -1
  30. data/sig/plugins/compression/brotli.rbs +1 -1
  31. data/sig/plugins/compression/deflate.rbs +1 -1
  32. data/sig/plugins/compression/gzip.rbs +1 -1
  33. data/sig/plugins/h2c.rbs +1 -1
  34. data/sig/plugins/multipart.rbs +4 -2
  35. data/sig/plugins/persistent.rbs +1 -1
  36. data/sig/plugins/proxy.rbs +2 -2
  37. data/sig/plugins/proxy/ssh.rbs +1 -1
  38. data/sig/plugins/rate_limiter.rbs +1 -1
  39. data/sig/pool.rbs +36 -2
  40. data/sig/request.rbs +1 -1
  41. data/sig/resolver.rbs +26 -0
  42. data/sig/resolver/https.rbs +49 -0
  43. data/sig/resolver/native.rbs +60 -0
  44. data/sig/resolver/resolver_mixin.rbs +27 -0
  45. data/sig/resolver/system.rbs +17 -0
  46. data/sig/response.rbs +1 -1
  47. data/sig/selector.rbs +20 -0
  48. data/sig/session.rbs +2 -2
  49. data/sig/transcoder.rbs +4 -2
  50. data/sig/transcoder/form.rbs +1 -1
  51. metadata +11 -4
  52. data/lib/httpx/resolver/options.rb +0 -25
  53. data/sig/test.rbs +0 -9
@@ -15,7 +15,6 @@ module HTTPX
15
15
  "AAAA" => Resolv::DNS::Resource::IN::AAAA,
16
16
  }.freeze
17
17
 
18
- # :nocov:
19
18
  DEFAULTS = if RUBY_VERSION < "2.2"
20
19
  {
21
20
  **Resolv::DNS::Config.default_config_hash,
@@ -44,7 +43,6 @@ module HTTPX
44
43
  false
45
44
  end
46
45
  end if DEFAULTS[:nameserver]
47
- # :nocov:
48
46
 
49
47
  DNS_PORT = 53
50
48
 
@@ -53,15 +51,15 @@ module HTTPX
53
51
  def initialize(options)
54
52
  @options = Options.new(options)
55
53
  @ns_index = 0
56
- @resolver_options = Resolver::Options.new(DEFAULTS.merge(@options.resolver_options || {}))
57
- @nameserver = @resolver_options.nameserver
58
- @_timeouts = Array(@resolver_options.timeouts)
54
+ @resolver_options = DEFAULTS.merge(@options.resolver_options)
55
+ @nameserver = @resolver_options[:nameserver]
56
+ @_timeouts = Array(@resolver_options[:timeouts])
59
57
  @timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
60
- @_record_types = Hash.new { |types, host| types[host] = @resolver_options.record_types.dup }
58
+ @_record_types = Hash.new { |types, host| types[host] = @resolver_options[:record_types].dup }
61
59
  @connections = []
62
60
  @queries = {}
63
61
  @read_buffer = "".b
64
- @write_buffer = Buffer.new(@resolver_options.packet_size)
62
+ @write_buffer = Buffer.new(@resolver_options[:packet_size])
65
63
  @state = :idle
66
64
  end
67
65
 
@@ -111,9 +109,9 @@ module HTTPX
111
109
  return if early_resolve(connection)
112
110
 
113
111
  if @nameserver.nil?
114
- ex = ResolveError.new("Can't resolve #{connection.origin.host}: no nameserver")
112
+ ex = ResolveError.new("No available nameserver")
115
113
  ex.set_backtrace(caller)
116
- emit(:error, connection, ex)
114
+ throw(:resolve_error, ex)
117
115
  else
118
116
  @connections << connection
119
117
  resolve
@@ -164,7 +162,7 @@ module HTTPX
164
162
  connections.each { |ch| resolve(ch) }
165
163
  end
166
164
 
167
- def dread(wsize = @resolver_options.packet_size)
165
+ def dread(wsize = @resolver_options[:packet_size])
168
166
  loop do
169
167
  siz = @io.read(wsize, @read_buffer)
170
168
  return unless siz && siz.positive?
@@ -199,13 +197,14 @@ module HTTPX
199
197
  end
200
198
  end
201
199
 
202
- if addresses.empty?
200
+ if addresses.nil? || addresses.empty?
203
201
  hostname, connection = @queries.first
204
202
  @_record_types[hostname].shift
205
203
  if @_record_types[hostname].empty?
206
204
  @queries.delete(hostname)
207
205
  @_record_types.delete(hostname)
208
206
  @connections.delete(connection)
207
+
209
208
  raise NativeResolveError.new(connection, hostname)
210
209
  end
211
210
  else
@@ -223,7 +222,7 @@ module HTTPX
223
222
  end
224
223
  else
225
224
  @connections.delete(connection)
226
- Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options.cache
225
+ Resolver.cached_lookup_set(connection.origin.host, addresses) if @resolver_options[:cache]
227
226
  emit_addresses(connection, addresses.map { |addr| addr["data"] })
228
227
  end
229
228
  end
@@ -243,7 +242,7 @@ module HTTPX
243
242
  log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname
244
243
  end
245
244
  @queries[hostname] = connection
246
- type = @_record_types[hostname].first
245
+ type = @_record_types[hostname].first || "A"
247
246
  log { "resolver: query #{type} for #{hostname}" }
248
247
  begin
249
248
  @write_buffer << Resolver.encode_dns_query(hostname, type: RECORD_TYPES[type])
@@ -38,7 +38,7 @@ module HTTPX
38
38
  def early_resolve(connection, hostname: connection.origin.host)
39
39
  addresses = connection.addresses ||
40
40
  ip_resolve(hostname) ||
41
- (@resolver_options.cache && Resolver.cached_lookup(hostname)) ||
41
+ (@resolver_options[:cache] && Resolver.cached_lookup(hostname)) ||
42
42
  system_resolve(hostname)
43
43
  return unless addresses
44
44
 
@@ -57,11 +57,13 @@ module HTTPX
57
57
  ips.map { |ip| IPAddr.new(ip) }
58
58
  end
59
59
 
60
- def emit_resolve_error(connection, hostname, ex = nil)
60
+ def emit_resolve_error(connection, hostname = connection.origin.host, ex = nil)
61
61
  emit(:error, connection, resolve_error(hostname, ex))
62
62
  end
63
63
 
64
64
  def resolve_error(hostname, ex = nil)
65
+ return ex if ex.is_a?(ResolveError)
66
+
65
67
  message = ex ? ex.message : "Can't resolve #{hostname}"
66
68
  error = ResolveError.new(message)
67
69
  error.set_backtrace(ex ? ex.backtrace : caller)
@@ -14,9 +14,9 @@ module HTTPX
14
14
 
15
15
  def initialize(options)
16
16
  @options = Options.new(options)
17
- @resolver_options = Resolver::Options.new(@options.resolver_options)
17
+ @resolver_options = @options.resolver_options
18
18
  @state = :idle
19
- resolv_options = @resolver_options.to_h
19
+ resolv_options = @resolver_options.dup
20
20
  timeouts = resolv_options.delete(:timeouts)
21
21
  resolv_options.delete(:cache)
22
22
  @resolver = Resolv::DNS.new(resolv_options.empty? ? nil : resolv_options)
@@ -4,19 +4,14 @@ require "io/wait"
4
4
 
5
5
  module IOExtensions
6
6
  refine IO do
7
- def wait(timeout = nil, mode = :read)
8
- case mode
9
- when :read
10
- wait_readable(timeout)
11
- when :write
12
- wait_writable(timeout)
13
- when :read_write
14
- r, w = IO.select([self], [self], nil, timeout)
15
-
16
- return unless r || w
17
-
18
- self
19
- end
7
+ # provides a fallback for rubies where IO#wait isn't implemented,
8
+ # but IO#wait_readable and IO#wait_writable are.
9
+ def wait(timeout = nil, _mode = :read_write)
10
+ r, w = IO.select([self], [self], nil, timeout)
11
+
12
+ return unless r || w
13
+
14
+ self
20
15
  end
21
16
  end
22
17
  end
@@ -77,10 +77,16 @@ module HTTPX
77
77
  end
78
78
 
79
79
  def set_connection_callbacks(connection, connections, options)
80
- connection.on(:uncoalesce) do |uncoalesced_uri|
81
- other_connection = build_connection(uncoalesced_uri, options)
80
+ connection.on(:misdirected) do |misdirected_request|
81
+ other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
82
+ other_connection.merge(connection)
83
+ catch(:coalesced) do
84
+ pool.init_connection(other_connection, options)
85
+ end
86
+ set_connection_callbacks(other_connection, connections, options)
82
87
  connections << other_connection
83
- connection.unmerge(other_connection)
88
+ misdirected_request.transition(:idle)
89
+ other_connection.send(misdirected_request)
84
90
  end
85
91
  connection.on(:altsvc) do |alt_origin, origin, alt_params|
86
92
  other_connection = build_altsvc_connection(connection, connections, alt_origin, origin, alt_params, options)
@@ -3,6 +3,24 @@
3
3
  module HTTPX
4
4
  module Transcoder
5
5
  extend Registry
6
+
7
+ def self.normalize_keys(key, value, &block)
8
+ if value.respond_to?(:to_ary)
9
+ if value.empty?
10
+ block.call("#{key}[]")
11
+ else
12
+ value.to_ary.each do |element|
13
+ normalize_keys("#{key}[]", element, &block)
14
+ end
15
+ end
16
+ elsif value.respond_to?(:to_hash)
17
+ value.to_hash.each do |child_key, child_value|
18
+ normalize_keys("#{key}[#{child_key}]", child_value, &block)
19
+ end
20
+ else
21
+ block.call(key.to_s, value)
22
+ end
23
+ end
6
24
  end
7
25
  end
8
26
 
@@ -12,10 +12,18 @@ module HTTPX::Transcoder
12
12
 
13
13
  def_delegator :@raw, :to_s
14
14
 
15
+ def_delegator :@raw, :to_str
16
+
15
17
  def_delegator :@raw, :bytesize
16
18
 
17
19
  def initialize(form)
18
- @raw = URI.encode_www_form(form)
20
+ @raw = form.each_with_object("".b) do |(key, val), buf|
21
+ HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
22
+ buf << "&" unless buf.empty?
23
+ buf << URI.encode_www_form_component(k)
24
+ buf << "=#{URI.encode_www_form_component(v.to_s)}" unless v.nil?
25
+ end
26
+ end
19
27
  end
20
28
 
21
29
  def content_type
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.10.0"
4
+ VERSION = "0.10.1"
5
5
  end
@@ -1,2 +1,85 @@
1
- class HTTPX::Connection
1
+ module HTTPX
2
+ class Connection
3
+ interface _Parser
4
+
5
+ def on: (Symbol) { (*untyped) -> void } -> void
6
+ def empty?: () -> bool
7
+ def close: () -> void
8
+ def consume: () -> void
9
+ def <<: (string) -> void
10
+ end
11
+
12
+ include Loggable
13
+ include Callbacks
14
+ extend HTTPX::Registry[String, Class]
15
+
16
+ attr_reader origin: generic_uri
17
+ attr_reader state: Symbol
18
+ attr_reader pending: Array[Request]
19
+ attr_reader options: options
20
+
21
+ def addresses: () -> Array[ipaddr]?
22
+
23
+ def addresses=: (Array[ipaddr]) -> void
24
+
25
+ def match?: (generic_uri, options) -> bool
26
+
27
+ def mergeable?: (Connection) -> bool
28
+
29
+ def coalescable?: (Connection) -> bool
30
+
31
+ def create_idle: (options) -> Connection
32
+ | () -> Connection
33
+
34
+ def merge: (Connection) -> void
35
+
36
+ def purge_pending: () { (Request) -> void } -> void
37
+
38
+ def match_altsvcs?: (generic_uri) -> bool
39
+
40
+ def connecting?: () -> bool
41
+ def inflight?: () -> boolish
42
+
43
+ def interests: () -> io_interests?
44
+
45
+ def to_io: () -> _ToIO
46
+
47
+ def call: () -> void
48
+
49
+ def close: () -> void
50
+ def reset: () -> void
51
+
52
+ def send: (Request) -> void
53
+
54
+ def timeout: () -> Numeric?
55
+
56
+ private
57
+
58
+ def initialize: (String, generic_uri, options) -> untyped
59
+
60
+ def connect: () -> void
61
+
62
+ def exhausted?: () -> boolish
63
+
64
+ def consume: () -> void
65
+
66
+ def send_pending: () -> void
67
+
68
+ def parser: () -> _Parser
69
+
70
+ def build_parser: () -> _Parser
71
+ | (String) -> _Parser
72
+
73
+ def set_parser_callbacks: (_Parser) -> void
74
+
75
+ def transition: (Symbol) -> void
76
+
77
+ def handle_response: () -> void
78
+
79
+ def on_error: (StandardError) -> void
80
+
81
+ def handle_error: (StandardError) -> void
82
+
83
+ def total_timeout: () -> Timers::Timer?
84
+ end
2
85
  end
@@ -0,0 +1,66 @@
1
+ module HTTPX
2
+ class Connection::HTTP1
3
+ include Callbacks
4
+ include Loggable
5
+
6
+ attr_reader pending: Array[Request]
7
+
8
+ @options: Options
9
+ @max_concurrent_requests: Integer
10
+ @max_requests: Integer
11
+ @parser: HTTP1
12
+ @buffer: Buffer
13
+
14
+ def interests: () -> io_interests?
15
+
16
+ def reset: () -> void
17
+
18
+ def close: () -> void
19
+
20
+ def empty?: () -> bool
21
+
22
+ def exhausted?: () -> bool
23
+
24
+ def <<: (String) -> void
25
+
26
+ def send: (Request) -> void
27
+
28
+ def consume: () -> void
29
+
30
+ def handle_error: (StandardError ex) -> void
31
+
32
+ def on_headers: (Hash[String, Array[String]] headers) -> void
33
+
34
+ def on_trailers: (Array[String, String] headers) -> void
35
+
36
+ def on_data: (string chunk) -> void
37
+
38
+ def on_complete: () -> void
39
+
40
+ def dispatch: () -> void
41
+
42
+ def ping: () -> void
43
+
44
+ private
45
+
46
+ def initialize: (Buffer, options) -> untyped
47
+
48
+ def manage_connection: (Response) -> void
49
+
50
+ def disable: () -> void
51
+
52
+ def disable_pipelining: () -> void
53
+
54
+ def set_request_headers: (Request) -> void
55
+
56
+ def headline_uri: (Request) -> String
57
+
58
+ def handle: (Request request) -> void
59
+
60
+ def join_headers: (Request request) -> void
61
+
62
+ def join_body: (Request request) -> void
63
+
64
+ def capitalized: (String field) -> String
65
+ end
66
+ end
@@ -1,4 +1,78 @@
1
1
  module HTTPX
2
2
  class Connection::HTTP2
3
+ include Callbacks
4
+ include Loggable
5
+
6
+ attr_reader streams: Hash[HTTP2Next::Stream, Response]
7
+ attr_reader pending: Array[Request]
8
+
9
+ @options: Options
10
+ @max_concurrent_requests: Integer
11
+ @max_requests: Integer
12
+ @drains: Hash[Request, String]
13
+ @pings: Array[String]
14
+ @buffer: Buffer
15
+
16
+ def interests: () -> io_interests
17
+
18
+ def close: () -> void
19
+
20
+ def empty?: () -> bool
21
+
22
+ def exhausted?: () -> bool
23
+
24
+ def <<: (String) -> void
25
+
26
+ def send: (Request) -> void
27
+
28
+ def consume: () -> void
29
+
30
+ def handle_error: (StandardError ex) -> void
31
+
32
+ def ping: () -> void
33
+
34
+ alias reset init_connection
35
+
36
+ private
37
+
38
+ def initialize: (Buffer, options) -> untyped
39
+
40
+ def send_pending: () -> void
41
+
42
+ def headline_uri: (Request) -> String
43
+
44
+ def set_request_headers: (Request) -> void
45
+
46
+ def handle: (Request request, HTTP2Next::Stream stream) -> void
47
+
48
+ def init_connection: () -> void
49
+
50
+ def handle_stream: (HTTP2Next::Stream stream, Request request) -> void
51
+
52
+ def join_headers: (HTTP2Next::Stream stream, Request request) -> void
53
+
54
+ def join_body: (HTTP2Next::Stream stream, Request request) -> void
55
+
56
+
57
+ # def on_stream_headers: (HTTP2Next::Stream stream, Request request, Array[String, String] headers) -> void
58
+
59
+ # def on_stream_data: (HTTP2Next::Stream stream, Request request, string data) -> void
60
+
61
+ # def on_stream_close: (HTTP2Next::Stream stream, Request request, Symbol? error) -> void
62
+
63
+ def on_frame: (string bytes) -> void
64
+
65
+ def on_settings: (*untyped) -> void
66
+
67
+ def on_close: (Integer last_frame, Symbol? error, String? payload) -> void
68
+
69
+ def on_frame_sent: (HTTP2Next::frame) -> void
70
+ def on_frame_received: (HTTP2Next::frame) -> void
71
+
72
+ def on_promise: (HTTP2Next::Stream) -> void
73
+
74
+ def on_origin: (String) -> void
75
+
76
+ def on_pong: (string ping) -> void
3
77
  end
4
78
  end