httpx 0.11.3 → 0.14.0

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