httpx 0.11.3 → 0.14.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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/doc/release_notes/0_10_1.md +1 -1
  4. data/doc/release_notes/0_11_1.md +5 -1
  5. data/doc/release_notes/0_12_0.md +55 -0
  6. data/doc/release_notes/0_13_0.md +58 -0
  7. data/doc/release_notes/0_13_1.md +5 -0
  8. data/doc/release_notes/0_13_2.md +9 -0
  9. data/doc/release_notes/0_14_0.md +79 -0
  10. data/lib/httpx.rb +3 -3
  11. data/lib/httpx/adapters/faraday.rb +4 -6
  12. data/lib/httpx/altsvc.rb +1 -0
  13. data/lib/httpx/callbacks.rb +12 -3
  14. data/lib/httpx/chainable.rb +2 -2
  15. data/lib/httpx/connection.rb +92 -37
  16. data/lib/httpx/connection/http1.rb +37 -19
  17. data/lib/httpx/connection/http2.rb +82 -31
  18. data/lib/httpx/headers.rb +1 -1
  19. data/lib/httpx/io.rb +16 -3
  20. data/lib/httpx/io/ssl.rb +35 -24
  21. data/lib/httpx/io/tcp.rb +50 -28
  22. data/lib/httpx/io/tls.rb +218 -0
  23. data/lib/httpx/io/tls/box.rb +365 -0
  24. data/lib/httpx/io/tls/context.rb +199 -0
  25. data/lib/httpx/io/tls/ffi.rb +390 -0
  26. data/lib/httpx/io/udp.rb +31 -7
  27. data/lib/httpx/io/unix.rb +27 -12
  28. data/lib/httpx/options.rb +97 -74
  29. data/lib/httpx/parser/http1.rb +4 -4
  30. data/lib/httpx/plugins/aws_sdk_authentication.rb +84 -0
  31. data/lib/httpx/plugins/aws_sigv4.rb +219 -0
  32. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  33. data/lib/httpx/plugins/compression.rb +24 -12
  34. data/lib/httpx/plugins/compression/brotli.rb +10 -7
  35. data/lib/httpx/plugins/compression/deflate.rb +8 -10
  36. data/lib/httpx/plugins/compression/gzip.rb +4 -3
  37. data/lib/httpx/plugins/cookies.rb +3 -7
  38. data/lib/httpx/plugins/digest_authentication.rb +5 -5
  39. data/lib/httpx/plugins/expect.rb +6 -6
  40. data/lib/httpx/plugins/follow_redirects.rb +4 -4
  41. data/lib/httpx/plugins/grpc.rb +247 -0
  42. data/lib/httpx/plugins/grpc/call.rb +62 -0
  43. data/lib/httpx/plugins/grpc/message.rb +85 -0
  44. data/lib/httpx/plugins/h2c.rb +43 -58
  45. data/lib/httpx/plugins/internal_telemetry.rb +93 -0
  46. data/lib/httpx/plugins/multipart.rb +2 -0
  47. data/lib/httpx/plugins/multipart/encoder.rb +4 -9
  48. data/lib/httpx/plugins/multipart/part.rb +1 -1
  49. data/lib/httpx/plugins/proxy.rb +4 -8
  50. data/lib/httpx/plugins/proxy/http.rb +1 -1
  51. data/lib/httpx/plugins/proxy/socks4.rb +8 -0
  52. data/lib/httpx/plugins/proxy/socks5.rb +8 -0
  53. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  54. data/lib/httpx/plugins/push_promise.rb +3 -2
  55. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  56. data/lib/httpx/plugins/retries.rb +15 -16
  57. data/lib/httpx/plugins/stream.rb +99 -77
  58. data/lib/httpx/plugins/upgrade.rb +84 -0
  59. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  60. data/lib/httpx/pool.rb +14 -6
  61. data/lib/httpx/registry.rb +1 -7
  62. data/lib/httpx/request.rb +36 -3
  63. data/lib/httpx/resolver/https.rb +3 -11
  64. data/lib/httpx/resolver/native.rb +7 -3
  65. data/lib/httpx/response.rb +18 -7
  66. data/lib/httpx/selector.rb +5 -0
  67. data/lib/httpx/session.rb +41 -8
  68. data/lib/httpx/transcoder/body.rb +3 -5
  69. data/lib/httpx/transcoder/chunker.rb +1 -1
  70. data/lib/httpx/version.rb +1 -1
  71. data/sig/callbacks.rbs +2 -0
  72. data/sig/chainable.rbs +2 -1
  73. data/sig/connection/http1.rbs +7 -2
  74. data/sig/connection/http2.rbs +10 -4
  75. data/sig/options.rbs +16 -22
  76. data/sig/plugins/aws_sdk_authentication.rbs +19 -0
  77. data/sig/plugins/aws_sigv4.rbs +64 -0
  78. data/sig/plugins/basic_authentication.rbs +2 -0
  79. data/sig/plugins/compression.rbs +7 -5
  80. data/sig/plugins/compression/brotli.rbs +1 -1
  81. data/sig/plugins/compression/deflate.rbs +1 -1
  82. data/sig/plugins/compression/gzip.rbs +1 -1
  83. data/sig/plugins/cookies.rbs +0 -1
  84. data/sig/plugins/digest_authentication.rbs +0 -1
  85. data/sig/plugins/expect.rbs +0 -2
  86. data/sig/plugins/follow_redirects.rbs +0 -2
  87. data/sig/plugins/h2c.rbs +5 -10
  88. data/sig/plugins/persistent.rbs +0 -1
  89. data/sig/plugins/proxy.rbs +0 -1
  90. data/sig/plugins/push_promise.rbs +1 -1
  91. data/sig/plugins/retries.rbs +0 -4
  92. data/sig/plugins/stream.rbs +17 -16
  93. data/sig/plugins/upgrade.rbs +23 -0
  94. data/sig/request.rbs +7 -2
  95. data/sig/response.rbs +4 -1
  96. data/sig/session.rbs +4 -0
  97. metadata +56 -33
  98. data/lib/httpx/timeout.rb +0 -67
  99. data/sig/timeout.rbs +0 -29
@@ -6,68 +6,53 @@ module HTTPX
6
6
  # This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
7
7
  # (https://tools.ietf.org/html/rfc7540#section-3.2)
8
8
  #
9
- # https://gitlab.com/honeyryderchuck/httpx/wikis/Follow-Redirects
9
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Upgrade#h2c
10
10
  #
11
11
  module H2C
12
- def self.load_dependencies(*)
13
- require "base64"
12
+ VALID_H2C_VERBS = %i[get options head].freeze
13
+
14
+ class << self
15
+ def load_dependencies(*)
16
+ require "base64"
17
+ end
18
+
19
+ def configure(klass)
20
+ klass.plugin(:upgrade)
21
+ klass.default_options.upgrade_handlers.register "h2c", self
22
+ end
23
+
24
+ def call(connection, request, response)
25
+ connection.upgrade_to_h2c(request, response)
26
+ end
14
27
  end
15
28
 
16
29
  module InstanceMethods
17
- def request(*args, **options)
18
- h2c_options = options.merge(fallback_protocol: "h2c")
30
+ def send_requests(*requests, options)
31
+ upgrade_request, *remainder = requests
32
+
33
+ return super unless VALID_H2C_VERBS.include?(upgrade_request.verb) && upgrade_request.scheme == "http"
19
34
 
20
- requests = build_requests(*args, h2c_options)
35
+ connection = pool.find_connection(upgrade_request.uri, @options.merge(options))
21
36
 
22
- upgrade_request = requests.first
23
- return super unless valid_h2c_upgrade_request?(upgrade_request)
37
+ return super if connection && connection.upgrade_protocol == :h2c
24
38
 
39
+ # build upgrade request
25
40
  upgrade_request.headers.add("connection", "upgrade")
26
41
  upgrade_request.headers.add("connection", "http2-settings")
27
42
  upgrade_request.headers["upgrade"] = "h2c"
28
43
  upgrade_request.headers["http2-settings"] = HTTP2Next::Client.settings_header(upgrade_request.options.http2_settings)
29
- wrap { send_requests(*upgrade_request, h2c_options).first }
30
44
 
31
- responses = send_requests(*requests, h2c_options)
32
-
33
- responses.size == 1 ? responses.first : responses
34
- end
35
-
36
- private
37
-
38
- def fetch_response(request, connections, options)
39
- response = super
40
- if response && valid_h2c_upgrade?(request, response, options)
41
- log { "upgrading to h2c..." }
42
- connection = find_connection(request, connections, options)
43
- connections << connection unless connections.include?(connection)
44
- connection.upgrade(request, response)
45
- end
46
- response
47
- end
48
-
49
- VALID_H2C_METHODS = %i[get options head].freeze
50
- private_constant :VALID_H2C_METHODS
51
-
52
- def valid_h2c_upgrade_request?(request)
53
- VALID_H2C_METHODS.include?(request.verb) &&
54
- request.scheme == "http"
55
- end
56
-
57
- def valid_h2c_upgrade?(request, response, options)
58
- options.fallback_protocol == "h2c" &&
59
- request.headers.get("connection").include?("upgrade") &&
60
- request.headers.get("upgrade").include?("h2c") &&
61
- response.status == 101
45
+ super(upgrade_request, *remainder, options.merge(max_concurrent_requests: 1))
62
46
  end
63
47
  end
64
48
 
65
49
  class H2CParser < Connection::HTTP2
66
50
  def upgrade(request, response)
67
- @connection.send_connection_preface
68
51
  # skip checks, it is assumed that this is the first
69
52
  # request in the connection
70
53
  stream = @connection.upgrade
54
+
55
+ # on_settings
71
56
  handle_stream(stream, request)
72
57
  @streams[request] = stream
73
58
 
@@ -81,29 +66,29 @@ module HTTPX
81
66
  module ConnectionMethods
82
67
  using URIExtensions
83
68
 
84
- def match?(uri, options)
85
- return super unless uri.scheme == "http" && @options.fallback_protocol == "h2c"
86
-
87
- super && options.fallback_protocol == "h2c"
88
- end
89
-
90
- def coalescable?(connection)
91
- return super unless @options.fallback_protocol == "h2c" && @origin.scheme == "http"
69
+ def upgrade_to_h2c(request, response)
70
+ prev_parser = @parser
92
71
 
93
- @origin == connection.origin && connection.options.fallback_protocol == "h2c"
94
- end
72
+ if prev_parser
73
+ prev_parser.reset
74
+ @inflight -= prev_parser.requests.size
75
+ end
95
76
 
96
- def upgrade(request, response)
97
- @parser.reset if @parser
98
- @parser = H2CParser.new(@write_buffer, @options)
77
+ parser_options = @options.merge(max_concurrent_requests: request.options.max_concurrent_requests)
78
+ @parser = H2CParser.new(@write_buffer, parser_options)
99
79
  set_parser_callbacks(@parser)
80
+ @inflight += 1
100
81
  @parser.upgrade(request, response)
101
- end
82
+ @upgrade_protocol = :h2c
102
83
 
103
- def build_parser(*)
104
- return super unless @origin.scheme == "http"
84
+ if request.options.max_concurrent_requests != @options.max_concurrent_requests
85
+ @options = @options.merge(max_concurrent_requests: nil)
86
+ end
105
87
 
106
- super("http/1.1")
88
+ prev_parser.requests.each do |req|
89
+ req.transition(:idle)
90
+ send(req)
91
+ end
107
92
  end
108
93
  end
109
94
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # The InternalTelemetry plugin is for internal use only. It is therefore undocumented, and
7
+ # its use is disencouraged, as API compatiblity will **not be guaranteed**.
8
+ #
9
+ # The gist of it is: when debug_level of logger is enabled to 3 or greater, considered internal-only
10
+ # supported log levels, it'll be loaded by default.
11
+ #
12
+ # Against a specific point of time, which will be by default the session initialization, but can be set
13
+ # by the end user in $http_init_time, different diff metrics can be shown. The "point of time" is calculated
14
+ # using the monotonic clock.
15
+ module InternalTelemetry
16
+ module TrackTimeMethods
17
+ private
18
+
19
+ def elapsed_time
20
+ yield
21
+ ensure
22
+ meter_elapsed_time("#{self.class.superclass}##{caller_locations(1, 1)[0].label}")
23
+ end
24
+
25
+ def meter_elapsed_time(label)
26
+ $http_init_time ||= Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
27
+ prev_time = $http_init_time
28
+ after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
29
+ # $http_init_time = after_time
30
+ elapsed = after_time - prev_time
31
+ warn(+"\e[31m" << "[ELAPSED TIME]: #{label}: #{elapsed} (ms)" << "\e[0m")
32
+ end
33
+ end
34
+
35
+ module InstanceMethods
36
+ def self.included(klass)
37
+ klass.prepend TrackTimeMethods
38
+ super
39
+ end
40
+
41
+ def initialize(*)
42
+ meter_elapsed_time("Session: initializing...")
43
+ super
44
+ meter_elapsed_time("Session: initialized!!!")
45
+ end
46
+
47
+ private
48
+
49
+ def build_requests(*)
50
+ elapsed_time { super }
51
+ end
52
+
53
+ def fetch_response(*)
54
+ response = super
55
+ meter_elapsed_time("Session -> response") if response
56
+ response
57
+ end
58
+
59
+ def close(*)
60
+ super
61
+ meter_elapsed_time("Session -> close")
62
+ end
63
+ end
64
+
65
+ module RequestMethods
66
+ def self.included(klass)
67
+ klass.prepend TrackTimeMethods
68
+ super
69
+ end
70
+
71
+ def transition(nextstate)
72
+ state = @state
73
+ super
74
+ meter_elapsed_time("Request##{object_id}[#{@verb} #{@uri}: #{state}] -> #{nextstate}") if nextstate == @state
75
+ end
76
+ end
77
+
78
+ module ConnectionMethods
79
+ def self.included(klass)
80
+ klass.prepend TrackTimeMethods
81
+ super
82
+ end
83
+
84
+ def transition(nextstate)
85
+ state = @state
86
+ super
87
+ meter_elapsed_time("Connection[#{@origin}]: #{state} -> #{nextstate}") if nextstate == @state
88
+ end
89
+ end
90
+ end
91
+ register_plugin :internal_telemetry, InternalTelemetry
92
+ end
93
+ end
@@ -23,6 +23,7 @@ module HTTPX
23
23
  end
24
24
 
25
25
  def load_dependencies(*)
26
+ # :nocov:
26
27
  begin
27
28
  unless defined?(HTTP::FormData)
28
29
  # in order not to break legacy code, we'll keep loading http/form_data for them.
@@ -33,6 +34,7 @@ module HTTPX
33
34
  end
34
35
  rescue LoadError
35
36
  end
37
+ # :nocov:
36
38
  require "httpx/plugins/multipart/encoder"
37
39
  require "httpx/plugins/multipart/part"
38
40
  require "httpx/plugins/multipart/mime_type_detector"
@@ -29,18 +29,13 @@ module HTTPX::Plugins
29
29
 
30
30
  def rewind
31
31
  form = @form.each_with_object([]) do |(key, val), aux|
32
- v = case val
33
- when File
34
- val = val.reopen(val.path, File::RDONLY) if val.closed?
35
- val.rewind
36
- val
37
- else
38
- v
39
- end
40
- aux << [key, v]
32
+ val = val.reopen(val.path, File::RDONLY) if val.is_a?(File) && val.closed?
33
+ val.rewind if val.respond_to?(:rewind)
34
+ aux << [key, val]
41
35
  end
42
36
  @form = form
43
37
  @parts = to_parts(form)
38
+ @part_index = 0
44
39
  end
45
40
 
46
41
  private
@@ -19,7 +19,7 @@ module HTTPX
19
19
  value = value[:body]
20
20
  end
21
21
 
22
- value = value.open(:binmode => true) if value.is_a?(Pathname)
22
+ value = value.open(:binmode => true) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
23
23
 
24
24
  if value.is_a?(File)
25
25
  filename ||= File.basename(value.path)
@@ -67,13 +67,9 @@ module HTTPX
67
67
 
68
68
  def extra_options(options)
69
69
  Class.new(options.class) do
70
- def_option(:proxy) do |pr|
71
- if pr.is_a?(Parameters)
72
- pr
73
- else
74
- Hash[pr]
75
- end
76
- end
70
+ def_option(:proxy, <<-OUT)
71
+ value.is_a?(#{Parameters}) ? value : Hash[value]
72
+ OUT
77
73
  end.new(options)
78
74
  end
79
75
  end
@@ -242,7 +238,7 @@ module HTTPX
242
238
  register_plugin :proxy, Proxy
243
239
  end
244
240
 
245
- class ProxySSL < SSL
241
+ class ProxySSL < IO.registry["ssl"]
246
242
  def initialize(tcp, request_uri, options)
247
243
  @io = tcp.to_io
248
244
  super(request_uri, tcp.addresses, options)
@@ -81,7 +81,7 @@ module HTTPX
81
81
  request.uri.to_s
82
82
  end
83
83
 
84
- def set_request_headers(request)
84
+ def set_protocol_headers(request)
85
85
  super
86
86
  proxy_params = @options.proxy
87
87
  request.headers["proxy-authorization"] = "Basic #{proxy_params.token_authentication}" if proxy_params.authenticated?
@@ -16,6 +16,14 @@ module HTTPX
16
16
  Error = Socks4Error
17
17
 
18
18
  module ConnectionMethods
19
+ def interests
20
+ if @state == :connecting
21
+ return @write_buffer.empty? ? :r : :w
22
+ end
23
+
24
+ super
25
+ end
26
+
19
27
  private
20
28
 
21
29
  def transition(nextstate)
@@ -35,6 +35,14 @@ module HTTPX
35
35
  super || @state == :authenticating || @state == :negotiating
36
36
  end
37
37
 
38
+ def interests
39
+ if @state == :connecting || @state == :authenticating || @state == :negotiating
40
+ return @write_buffer.empty? ? :r : :w
41
+ end
42
+
43
+ super
44
+ end
45
+
38
46
  private
39
47
 
40
48
  def transition(nextstate)
@@ -12,9 +12,9 @@ module HTTPX
12
12
 
13
13
  def self.extra_options(options)
14
14
  Class.new(options.class) do
15
- def_option(:proxy) do |pr|
16
- Hash[pr]
17
- end
15
+ def_option(:proxy, <<-OUT)
16
+ Hash[value]
17
+ OUT
18
18
  end.new(options)
19
19
  end
20
20
 
@@ -43,7 +43,7 @@ module HTTPX
43
43
  end
44
44
 
45
45
  def __on_promise_request(parser, stream, h)
46
- log(level: 1) do
46
+ log(level: 1, color: :yellow) do
47
47
  # :nocov:
48
48
  h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
49
49
  # :nocov:
@@ -57,6 +57,8 @@ module HTTPX
57
57
  request.merge_headers(headers)
58
58
  promise_headers[stream] = request
59
59
  parser.pending.delete(request)
60
+ parser.streams[request] = stream
61
+ request.transition(:done)
60
62
  else
61
63
  stream.refuse
62
64
  end
@@ -67,7 +69,6 @@ module HTTPX
67
69
  return unless request
68
70
 
69
71
  parser.__send__(:on_stream_headers, stream, request, h)
70
- request.transition(:done)
71
72
  response = request.response
72
73
  response.mark_as_pushed!
73
74
  stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
@@ -15,7 +15,7 @@ module HTTPX
15
15
  class << self
16
16
  RATE_LIMIT_CODES = [429, 503].freeze
17
17
 
18
- def load_dependencies(klass)
18
+ def configure(klass)
19
19
  klass.plugin(:retries,
20
20
  retry_change_requests: true,
21
21
  retry_on: method(:retry_on_rate_limited_response),
@@ -17,7 +17,7 @@ module HTTPX
17
17
  Errno::ECONNRESET,
18
18
  Errno::ECONNABORTED,
19
19
  Errno::EPIPE,
20
- (OpenSSL::SSL::SSLError if defined?(OpenSSL)),
20
+ (TLSError if defined?(TLSError)),
21
21
  TimeoutError,
22
22
  Parser::Error,
23
23
  Errno::EINVAL,
@@ -25,37 +25,36 @@ module HTTPX
25
25
 
26
26
  def self.extra_options(options)
27
27
  Class.new(options.class) do
28
- # number of seconds after which one can retry the request
29
- def_option(:retry_after) do |num|
28
+ def_option(:retry_after, <<-OUT)
30
29
  # return early if callable
31
- unless num.respond_to?(:call)
32
- num = Integer(num)
33
- raise Error, ":retry_after must be positive" unless num.positive?
30
+ unless value.respond_to?(:call)
31
+ value = Integer(value)
32
+ raise Error, ":retry_after must be positive" unless value.positive?
34
33
  end
35
34
 
36
- num
37
- end
35
+ value
36
+ OUT
38
37
 
39
- def_option(:max_retries) do |num|
40
- num = Integer(num)
38
+ def_option(:max_retries, <<-OUT)
39
+ num = Integer(value)
41
40
  raise Error, ":max_retries must be positive" unless num.positive?
42
41
 
43
42
  num
44
- end
43
+ OUT
45
44
 
46
45
  def_option(:retry_change_requests)
47
46
 
48
- def_option(:retry_on) do |callback|
49
- raise ":retry_on must be called with the response" unless callback.respond_to?(:call)
47
+ def_option(:retry_on, <<-OUT)
48
+ raise ":retry_on must be called with the response" unless value.respond_to?(:call)
50
49
 
51
- callback
52
- end
50
+ value
51
+ OUT
53
52
  end.new(options).merge(max_retries: MAX_RETRIES)
54
53
  end
55
54
 
56
55
  module InstanceMethods
57
56
  def max_retries(n)
58
- branch(default_options.with_max_retries(n.to_i))
57
+ with(max_retries: n.to_i)
59
58
  end
60
59
 
61
60
  private