httpx 0.12.0 → 0.14.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_10_1.md +1 -1
  3. data/doc/release_notes/0_13_0.md +58 -0
  4. data/doc/release_notes/0_13_1.md +5 -0
  5. data/doc/release_notes/0_13_2.md +9 -0
  6. data/doc/release_notes/0_14_0.md +79 -0
  7. data/doc/release_notes/0_14_1.md +7 -0
  8. data/lib/httpx.rb +1 -2
  9. data/lib/httpx/callbacks.rb +12 -3
  10. data/lib/httpx/chainable.rb +2 -2
  11. data/lib/httpx/connection.rb +29 -22
  12. data/lib/httpx/connection/http1.rb +35 -15
  13. data/lib/httpx/connection/http2.rb +61 -15
  14. data/lib/httpx/headers.rb +7 -3
  15. data/lib/httpx/io/ssl.rb +30 -17
  16. data/lib/httpx/io/tcp.rb +48 -27
  17. data/lib/httpx/io/udp.rb +31 -7
  18. data/lib/httpx/io/unix.rb +27 -12
  19. data/lib/httpx/options.rb +97 -74
  20. data/lib/httpx/plugins/aws_sdk_authentication.rb +5 -2
  21. data/lib/httpx/plugins/aws_sigv4.rb +5 -4
  22. data/lib/httpx/plugins/basic_authentication.rb +8 -3
  23. data/lib/httpx/plugins/compression.rb +24 -12
  24. data/lib/httpx/plugins/compression/brotli.rb +10 -7
  25. data/lib/httpx/plugins/compression/deflate.rb +6 -5
  26. data/lib/httpx/plugins/compression/gzip.rb +4 -3
  27. data/lib/httpx/plugins/cookies.rb +3 -7
  28. data/lib/httpx/plugins/digest_authentication.rb +5 -5
  29. data/lib/httpx/plugins/expect.rb +6 -6
  30. data/lib/httpx/plugins/follow_redirects.rb +4 -4
  31. data/lib/httpx/plugins/grpc.rb +247 -0
  32. data/lib/httpx/plugins/grpc/call.rb +62 -0
  33. data/lib/httpx/plugins/grpc/message.rb +85 -0
  34. data/lib/httpx/plugins/h2c.rb +43 -58
  35. data/lib/httpx/plugins/internal_telemetry.rb +1 -1
  36. data/lib/httpx/plugins/multipart/part.rb +2 -2
  37. data/lib/httpx/plugins/proxy.rb +3 -7
  38. data/lib/httpx/plugins/proxy/http.rb +5 -4
  39. data/lib/httpx/plugins/proxy/ssh.rb +3 -3
  40. data/lib/httpx/plugins/rate_limiter.rb +1 -1
  41. data/lib/httpx/plugins/retries.rb +14 -15
  42. data/lib/httpx/plugins/stream.rb +99 -75
  43. data/lib/httpx/plugins/upgrade.rb +84 -0
  44. data/lib/httpx/plugins/upgrade/h2.rb +54 -0
  45. data/lib/httpx/pool.rb +14 -5
  46. data/lib/httpx/request.rb +25 -2
  47. data/lib/httpx/resolver/native.rb +7 -3
  48. data/lib/httpx/response.rb +9 -5
  49. data/lib/httpx/session.rb +17 -7
  50. data/lib/httpx/transcoder/chunker.rb +1 -1
  51. data/lib/httpx/version.rb +1 -1
  52. data/sig/callbacks.rbs +2 -0
  53. data/sig/chainable.rbs +2 -1
  54. data/sig/connection/http1.rbs +6 -1
  55. data/sig/connection/http2.rbs +6 -2
  56. data/sig/headers.rbs +2 -2
  57. data/sig/options.rbs +16 -22
  58. data/sig/plugins/aws_sdk_authentication.rbs +2 -0
  59. data/sig/plugins/aws_sigv4.rbs +0 -1
  60. data/sig/plugins/basic_authentication.rbs +2 -0
  61. data/sig/plugins/compression.rbs +7 -5
  62. data/sig/plugins/compression/brotli.rbs +1 -1
  63. data/sig/plugins/compression/deflate.rbs +1 -1
  64. data/sig/plugins/compression/gzip.rbs +1 -1
  65. data/sig/plugins/cookies.rbs +0 -1
  66. data/sig/plugins/digest_authentication.rbs +0 -1
  67. data/sig/plugins/expect.rbs +0 -2
  68. data/sig/plugins/follow_redirects.rbs +0 -2
  69. data/sig/plugins/h2c.rbs +5 -10
  70. data/sig/plugins/persistent.rbs +0 -1
  71. data/sig/plugins/proxy.rbs +0 -1
  72. data/sig/plugins/retries.rbs +0 -4
  73. data/sig/plugins/stream.rbs +17 -16
  74. data/sig/plugins/upgrade.rbs +23 -0
  75. data/sig/request.rbs +7 -2
  76. data/sig/response.rbs +4 -1
  77. data/sig/session.rbs +4 -0
  78. metadata +21 -7
  79. data/lib/httpx/timeout.rb +0 -67
  80. data/sig/timeout.rbs +0 -29
data/lib/httpx/io/unix.rb CHANGED
@@ -6,34 +6,43 @@ module HTTPX
6
6
  class UNIX < TCP
7
7
  extend Forwardable
8
8
 
9
- def_delegator :@uri, :port, :scheme
9
+ using URIExtensions
10
10
 
11
- def initialize(uri, addresses, options)
12
- @uri = uri
11
+ attr_reader :path
12
+
13
+ alias_method :host, :path
14
+
15
+ def initialize(origin, addresses, options)
13
16
  @addresses = addresses
17
+ @hostname = origin.host
14
18
  @state = :idle
15
19
  @options = Options.new(options)
16
- @path = @options.transport_options[:path]
17
20
  @fallback_protocol = @options.fallback_protocol
18
21
  if @options.io
19
22
  @io = case @options.io
20
23
  when Hash
21
- @options.io[@path]
24
+ @options.io[origin.authority]
22
25
  else
23
26
  @options.io
24
27
  end
25
- unless @io.nil?
26
- @keep_open = true
27
- @state = :connected
28
+ raise Error, "Given IO objects do not match the request authority" unless @io
29
+
30
+ @path = @io.path
31
+ @keep_open = true
32
+ @state = :connected
33
+ else
34
+ if @options.transport_options
35
+ # :nocov:
36
+ warn ":#{__method__} is deprecated, use :addresses instead"
37
+ @path = @options.transport_options[:path]
38
+ # :nocov:
39
+ else
40
+ @path = addresses.first
28
41
  end
29
42
  end
30
43
  @io ||= build_socket
31
44
  end
32
45
 
33
- def hostname
34
- @uri.host
35
- end
36
-
37
46
  def connect
38
47
  return unless closed?
39
48
 
@@ -51,6 +60,12 @@ module HTTPX
51
60
  ::IO::WaitReadable
52
61
  end
53
62
 
63
+ # :nocov:
64
+ def inspect
65
+ "#<#{self.class}(path: #{@path}): (state: #{@state})>"
66
+ end
67
+ # :nocov:
68
+
54
69
  private
55
70
 
56
71
  def build_socket
data/lib/httpx/options.rb CHANGED
@@ -4,13 +4,39 @@ module HTTPX
4
4
  class Options
5
5
  WINDOW_SIZE = 1 << 14 # 16K
6
6
  MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
7
+ CONNECT_TIMEOUT = 60
8
+ OPERATION_TIMEOUT = 60
9
+ KEEP_ALIVE_TIMEOUT = 20
10
+
11
+ DEFAULT_OPTIONS = {
12
+ :debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
13
+ :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
14
+ :ssl => {},
15
+ :http2_settings => { settings_enable_push: 0 },
16
+ :fallback_protocol => "http/1.1",
17
+ :timeout => {
18
+ connect_timeout: CONNECT_TIMEOUT,
19
+ operation_timeout: OPERATION_TIMEOUT,
20
+ keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
21
+ },
22
+ :headers => {},
23
+ :window_size => WINDOW_SIZE,
24
+ :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
25
+ :request_class => Class.new(Request),
26
+ :response_class => Class.new(Response),
27
+ :headers_class => Class.new(Headers),
28
+ :request_body_class => Class.new(Request::Body),
29
+ :response_body_class => Class.new(Response::Body),
30
+ :connection_class => Class.new(Connection),
31
+ :transport => nil,
32
+ :transport_options => nil,
33
+ :addresses => nil,
34
+ :persistent => false,
35
+ :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
36
+ :resolver_options => { cache: true },
37
+ }.freeze
7
38
 
8
39
  class << self
9
- def inherited(klass)
10
- super
11
- klass.instance_variable_set(:@defined_options, @defined_options.dup)
12
- end
13
-
14
40
  def new(options = {})
15
41
  # let enhanced options go through
16
42
  return options if self == Options && options.class > self
@@ -19,31 +45,30 @@ module HTTPX
19
45
  super
20
46
  end
21
47
 
22
- def defined_options
23
- @defined_options ||= []
24
- end
48
+ def def_option(name, layout = nil, &interpreter)
49
+ attr_reader name
25
50
 
26
- def def_option(name, &interpreter)
27
- defined_options << name.to_sym
51
+ if layout
52
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
53
+ def #{name}=(value)
54
+ return if value.nil?
28
55
 
29
- attr_reader name
56
+ value = begin
57
+ #{layout}
58
+ end
59
+
60
+ @#{name} = value
61
+ end
62
+ OUT
30
63
 
31
- if interpreter
64
+ elsif interpreter
32
65
  define_method(:"#{name}=") do |value|
33
66
  return if value.nil?
34
67
 
35
68
  instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
36
69
  end
37
-
38
- define_method(:"with_#{name}") do |value|
39
- merge(name => instance_exec(value, &interpreter))
40
- end
41
70
  else
42
71
  attr_writer name
43
-
44
- define_method(:"with_#{name}") do |value|
45
- merge(name => value)
46
- end
47
72
  end
48
73
 
49
74
  protected :"#{name}="
@@ -51,75 +76,71 @@ module HTTPX
51
76
  end
52
77
 
53
78
  def initialize(options = {})
54
- defaults = {
55
- :debug => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
56
- :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
57
- :ssl => {},
58
- :http2_settings => { settings_enable_push: 0 },
59
- :fallback_protocol => "http/1.1",
60
- :timeout => Timeout.new,
61
- :headers => {},
62
- :window_size => WINDOW_SIZE,
63
- :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
64
- :request_class => Class.new(Request),
65
- :response_class => Class.new(Response),
66
- :headers_class => Class.new(Headers),
67
- :request_body_class => Class.new(Request::Body),
68
- :response_body_class => Class.new(Response::Body),
69
- :connection_class => Class.new(Connection),
70
- :transport => nil,
71
- :transport_options => nil,
72
- :persistent => false,
73
- :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
74
- :resolver_options => { cache: true },
75
- }
76
-
77
- defaults.merge!(options)
79
+ defaults = DEFAULT_OPTIONS.merge(options)
78
80
  defaults.each do |(k, v)|
79
81
  next if v.nil?
80
82
 
81
- __send__(:"#{k}=", v)
83
+ begin
84
+ __send__(:"#{k}=", v)
85
+ rescue NoMethodError
86
+ raise Error, "unknown option: #{k}"
87
+ end
82
88
  end
83
89
  end
84
90
 
85
- def_option(:headers) do |headers|
91
+ def_option(:origin, <<-OUT)
92
+ URI(value)
93
+ OUT
94
+
95
+ def_option(:headers, <<-OUT)
86
96
  if self.headers
87
- self.headers.merge(headers)
97
+ self.headers.merge(value)
88
98
  else
89
- Headers.new(headers)
99
+ Headers.new(value)
90
100
  end
91
- end
101
+ OUT
92
102
 
93
- def_option(:timeout) do |opts|
94
- Timeout.new(opts)
95
- end
103
+ def_option(:timeout, <<-OUT)
104
+ timeouts = Hash[value]
96
105
 
97
- def_option(:max_concurrent_requests) do |num|
98
- raise Error, ":max_concurrent_requests must be positive" unless num.positive?
106
+ if timeouts.key?(:loop_timeout)
107
+ warn ":loop_timeout is deprecated, use :operation_timeout instead"
108
+ timeouts[:operation_timeout] = timeouts.delete(:loop_timeout)
109
+ end
99
110
 
100
- num
101
- end
111
+ timeouts
112
+ OUT
102
113
 
103
- def_option(:max_requests) do |num|
104
- raise Error, ":max_requests must be positive" unless num.positive?
114
+ def_option(:max_concurrent_requests, <<-OUT)
115
+ raise Error, ":max_concurrent_requests must be positive" unless value.positive?
105
116
 
106
- num
107
- end
117
+ value
118
+ OUT
108
119
 
109
- def_option(:window_size) do |num|
110
- Integer(num)
111
- end
120
+ def_option(:max_requests, <<-OUT)
121
+ raise Error, ":max_requests must be positive" unless value.positive?
112
122
 
113
- def_option(:body_threshold_size) do |num|
114
- Integer(num)
115
- end
123
+ value
124
+ OUT
116
125
 
117
- def_option(:transport) do |tr|
118
- transport = tr.to_s
119
- raise Error, "#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
126
+ def_option(:window_size, <<-OUT)
127
+ Integer(value)
128
+ OUT
129
+
130
+ def_option(:body_threshold_size, <<-OUT)
131
+ Integer(value)
132
+ OUT
133
+
134
+ def_option(:transport, <<-OUT)
135
+ transport = value.to_s
136
+ raise Error, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
120
137
 
121
138
  transport
122
- end
139
+ OUT
140
+
141
+ def_option(:addresses, <<-OUT)
142
+ Array(value)
143
+ OUT
123
144
 
124
145
  %w[
125
146
  params form json body ssl http2_settings
@@ -153,6 +174,8 @@ module HTTPX
153
174
 
154
175
  h1 = to_hash
155
176
 
177
+ return self if h1 == h2
178
+
156
179
  merged = h1.merge(h2) do |k, v1, v2|
157
180
  case k
158
181
  when :headers, :ssl, :http2_settings, :timeout
@@ -166,10 +189,10 @@ module HTTPX
166
189
  end
167
190
 
168
191
  def to_hash
169
- hash_pairs = self.class
170
- .defined_options
171
- .flat_map { |opt_name| [opt_name, send(opt_name)] }
172
- Hash[*hash_pairs]
192
+ hash_pairs = instance_variables.map do |ivar|
193
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
194
+ end
195
+ Hash[hash_pairs]
173
196
  end
174
197
 
175
198
  def initialize_dup(other)
@@ -32,9 +32,8 @@ module HTTPX
32
32
  class << self
33
33
  attr_reader :credentials, :region
34
34
 
35
- def load_dependencies(klass)
35
+ def load_dependencies(_klass)
36
36
  require "aws-sdk-core"
37
- klass.plugin(:aws_sigv4)
38
37
 
39
38
  client = Class.new(Seahorse::Client::Base) do
40
39
  @identifier = :httpx
@@ -50,6 +49,10 @@ module HTTPX
50
49
  @region = client.config[:region]
51
50
  end
52
51
 
52
+ def configure(klass)
53
+ klass.plugin(:aws_sigv4)
54
+ end
55
+
53
56
  def extra_options(options)
54
57
  options.merge(max_concurrent_requests: 1)
55
58
  end
@@ -143,13 +143,14 @@ module HTTPX
143
143
  class << self
144
144
  def extra_options(options)
145
145
  Class.new(options.class) do
146
- def_option(:sigv4_signer) do |signer|
147
- signer.is_a?(Signer) ? signer : Signer.new(signer)
148
- end
149
- end.new.merge(options)
146
+ def_option(:sigv4_signer, <<-OUT)
147
+ value.is_a?(#{Signer}) ? value : #{Signer}.new(value)
148
+ OUT
149
+ end.new(options)
150
150
  end
151
151
 
152
152
  def load_dependencies(klass)
153
+ require "digest/sha2"
153
154
  require "openssl"
154
155
  klass.plugin(:expect)
155
156
  klass.plugin(:compression)
@@ -8,9 +8,14 @@ module HTTPX
8
8
  # https://gitlab.com/honeyryderchuck/httpx/wikis/Authentication#basic-authentication
9
9
  #
10
10
  module BasicAuthentication
11
- def self.load_dependencies(klass)
12
- require "base64"
13
- klass.plugin(:authentication)
11
+ class << self
12
+ def load_dependencies(_klass)
13
+ require "base64"
14
+ end
15
+
16
+ def configure(klass)
17
+ klass.plugin(:authentication)
18
+ end
14
19
  end
15
20
 
16
21
  module InstanceMethods
@@ -13,23 +13,31 @@ module HTTPX
13
13
  # https://gitlab.com/honeyryderchuck/httpx/wikis/Compression
14
14
  #
15
15
  module Compression
16
- extend Registry
17
-
18
16
  class << self
19
- def load_dependencies(klass)
17
+ def configure(klass)
20
18
  klass.plugin(:"compression/gzip")
21
19
  klass.plugin(:"compression/deflate")
22
20
  end
23
21
 
24
22
  def extra_options(options)
23
+ encodings = Module.new do
24
+ extend Registry
25
+ end
26
+
25
27
  Class.new(options.class) do
26
- def_option(:compression_threshold_size) do |bytes|
27
- bytes = Integer(bytes)
28
+ def_option(:compression_threshold_size, <<-OUT)
29
+ bytes = Integer(value)
28
30
  raise Error, ":expect_threshold_size must be positive" unless bytes.positive?
29
31
 
30
32
  bytes
31
- end
32
- end.new(options).merge(headers: { "accept-encoding" => Compression.registry.keys })
33
+ OUT
34
+
35
+ def_option(:encodings, <<-OUT)
36
+ raise Error, ":encodings must be a registry" unless value.respond_to?(:registry)
37
+
38
+ value
39
+ OUT
40
+ end.new(options).merge(encodings: encodings)
33
41
  end
34
42
  end
35
43
 
@@ -37,7 +45,11 @@ module HTTPX
37
45
  def initialize(*)
38
46
  super
39
47
  # forego compression in the Range cases
40
- @headers.delete("accept-encoding") if @headers.key?("range")
48
+ if @headers.key?("range")
49
+ @headers.delete("accept-encoding")
50
+ else
51
+ @headers["accept-encoding"] ||= @options.encodings.registry.keys
52
+ end
41
53
  end
42
54
  end
43
55
 
@@ -52,16 +64,16 @@ module HTTPX
52
64
  @headers.get("content-encoding").each do |encoding|
53
65
  next if encoding == "identity"
54
66
 
55
- @body = Encoder.new(@body, Compression.registry(encoding).deflater)
67
+ @body = Encoder.new(@body, options.encodings.registry(encoding).deflater)
56
68
  end
57
- @headers["content-length"] = @body.bytesize unless chunked?
69
+ @headers["content-length"] = @body.bytesize unless unbounded_body?
58
70
  end
59
71
  end
60
72
 
61
73
  module ResponseBodyMethods
62
74
  attr_reader :encodings
63
75
 
64
- def initialize(*, **)
76
+ def initialize(*)
65
77
  @encodings = []
66
78
 
67
79
  super
@@ -80,7 +92,7 @@ module HTTPX
80
92
  @_inflaters = @headers.get("content-encoding").map do |encoding|
81
93
  next if encoding == "identity"
82
94
 
83
- inflater = Compression.registry(encoding).inflater(compressed_length)
95
+ inflater = @options.encodings.registry(encoding).inflater(compressed_length)
84
96
  # do not uncompress if there is no decoder available. In fact, we can't reliably
85
97
  # continue decompressing beyond that, so ignore.
86
98
  break unless inflater
@@ -4,24 +4,27 @@ module HTTPX
4
4
  module Plugins
5
5
  module Compression
6
6
  module Brotli
7
- def self.load_dependencies(klass)
8
- klass.plugin(:compression)
9
- require "brotli"
10
- end
7
+ class << self
8
+ def load_dependencies(_klass)
9
+ require "brotli"
10
+ end
11
11
 
12
- def self.configure(*)
13
- Compression.register "br", self
12
+ def configure(klass)
13
+ klass.plugin(:compression)
14
+ klass.default_options.encodings.register "br", self
15
+ end
14
16
  end
15
17
 
16
18
  module Deflater
17
19
  module_function
18
20
 
19
- def deflate(raw, buffer, chunk_size:)
21
+ def deflate(raw, buffer = "".b, chunk_size: 16_384)
20
22
  while (chunk = raw.read(chunk_size))
21
23
  compressed = ::Brotli.deflate(chunk)
22
24
  buffer << compressed
23
25
  yield compressed if block_given?
24
26
  end
27
+ buffer
25
28
  end
26
29
  end
27
30