httpx 0.12.0 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
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