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
data/lib/httpx/io/udp.rb CHANGED
@@ -39,16 +39,20 @@ module HTTPX
39
39
  end
40
40
  end
41
41
 
42
- def write(buffer)
43
- siz = @io.send(buffer.to_s, 0, @host, @port)
44
- log { "WRITE: #{siz} bytes..." }
45
- buffer.shift!(siz)
46
- siz
47
- end
48
-
49
42
  # :nocov:
50
43
  if (RUBY_ENGINE == "truffleruby" && RUBY_ENGINE_VERSION < "21.1.0") ||
51
44
  RUBY_VERSION < "2.3"
45
+ def write(buffer)
46
+ siz = @io.sendmsg_nonblock(buffer.to_s, 0, Socket.sockaddr_in(@port, @host.to_s))
47
+ log { "WRITE: #{siz} bytes..." }
48
+ buffer.shift!(siz)
49
+ siz
50
+ rescue ::IO::WaitWritable
51
+ 0
52
+ rescue EOFError
53
+ nil
54
+ end
55
+
52
56
  def read(size, buffer)
53
57
  data, _ = @io.recvfrom_nonblock(size)
54
58
  buffer.replace(data)
@@ -59,6 +63,18 @@ module HTTPX
59
63
  rescue IOError
60
64
  end
61
65
  else
66
+
67
+ def write(buffer)
68
+ siz = @io.sendmsg_nonblock(buffer.to_s, 0, Socket.sockaddr_in(@port, @host.to_s), exception: false)
69
+ return 0 if siz == :wait_writable
70
+ return if siz.nil?
71
+
72
+ log { "WRITE: #{siz} bytes..." }
73
+
74
+ buffer.shift!(siz)
75
+ siz
76
+ end
77
+
62
78
  def read(size, buffer)
63
79
  ret = @io.recvfrom_nonblock(size, 0, buffer, exception: false)
64
80
  return 0 if ret == :wait_readable
@@ -68,6 +84,14 @@ module HTTPX
68
84
  rescue IOError
69
85
  end
70
86
  end
87
+
88
+ # In JRuby, sendmsg_nonblock is not implemented
89
+ def write(buffer)
90
+ siz = @io.send(buffer.to_s, 0, @host, @port)
91
+ log { "WRITE: #{siz} bytes..." }
92
+ buffer.shift!(siz)
93
+ siz
94
+ end if RUBY_ENGINE == "jruby"
71
95
  # :nocov:
72
96
  end
73
97
  end
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)
@@ -66,7 +66,6 @@ module HTTPX
66
66
  @status_code = code.to_i
67
67
  raise(Error, "wrong status code (#{@status_code})") unless (100..599).cover?(@status_code)
68
68
 
69
- # @buffer.slice!(0, idx + 1)
70
69
  @buffer = @buffer.byteslice((idx + 1)..-1)
71
70
  nextstate(:headers)
72
71
  end
@@ -74,7 +73,8 @@ module HTTPX
74
73
  def parse_headers
75
74
  headers = @headers
76
75
  while (idx = @buffer.index("\n"))
77
- line = @buffer.slice!(0, idx + 1).sub(/\s+\z/, "")
76
+ line = @buffer.byteslice(0..idx).sub(/\s+\z/, "")
77
+ @buffer = @buffer.byteslice((idx + 1)..-1)
78
78
  if line.empty?
79
79
  case @state
80
80
  when :headers
@@ -96,11 +96,11 @@ module HTTPX
96
96
  separator_index = line.index(":")
97
97
  raise Error, "wrong header format" unless separator_index
98
98
 
99
- key = line[0..separator_index - 1]
99
+ key = line.byteslice(0..(separator_index - 1))
100
100
  raise Error, "wrong header format" if key.start_with?("\s", "\t")
101
101
 
102
102
  key.strip!
103
- value = line[separator_index + 1..-1]
103
+ value = line.byteslice((separator_index + 1)..-1)
104
104
  value.strip!
105
105
  raise Error, "wrong header format" if value.nil?
106
106
 
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin applies AWS Sigv4 to requests, using the AWS SDK credentials and configuration.
7
+ #
8
+ # It requires the "aws-sdk-core" gem.
9
+ #
10
+ module AwsSdkAuthentication
11
+ #
12
+ # encapsulates access to an AWS SDK credentials store.
13
+ #
14
+ class Credentials
15
+ def initialize(aws_credentials)
16
+ @aws_credentials = aws_credentials
17
+ end
18
+
19
+ def username
20
+ @aws_credentials.access_key_id
21
+ end
22
+
23
+ def password
24
+ @aws_credentials.secret_access_key
25
+ end
26
+
27
+ def security_token
28
+ @aws_credentials.session_token
29
+ end
30
+ end
31
+
32
+ class << self
33
+ attr_reader :credentials, :region
34
+
35
+ def load_dependencies(_klass)
36
+ 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
+ end
51
+
52
+ def configure(klass)
53
+ klass.plugin(:aws_sigv4)
54
+ end
55
+
56
+ def extra_options(options)
57
+ options.merge(max_concurrent_requests: 1)
58
+ end
59
+ end
60
+
61
+ module InstanceMethods
62
+ #
63
+ # aws_authentication
64
+ # aws_authentication(credentials: Aws::Credentials.new('akid', 'secret'))
65
+ # aws_authentication()
66
+ #
67
+ def aws_sdk_authentication(**options)
68
+ credentials = AwsSdkAuthentication.credentials
69
+ region = AwsSdkAuthentication.region
70
+
71
+ aws_sigv4_authentication(
72
+ credentials: credentials,
73
+ region: region,
74
+ provider_prefix: "aws",
75
+ header_provider_field: "amz",
76
+ **options
77
+ )
78
+ end
79
+ alias_method :aws_auth, :aws_sdk_authentication
80
+ end
81
+ end
82
+ register_plugin :aws_sdk_authentication, AwsSdkAuthentication
83
+ end
84
+ end