httpx 0.15.1 → 0.16.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_15_2.md +9 -0
  3. data/doc/release_notes/0_15_3.md +5 -0
  4. data/doc/release_notes/0_15_4.md +5 -0
  5. data/doc/release_notes/0_16_0.md +93 -0
  6. data/lib/httpx.rb +6 -3
  7. data/lib/httpx/adapters/faraday.rb +3 -11
  8. data/lib/httpx/buffer.rb +1 -1
  9. data/lib/httpx/callbacks.rb +1 -1
  10. data/lib/httpx/chainable.rb +15 -8
  11. data/lib/httpx/connection.rb +2 -2
  12. data/lib/httpx/connection/http1.rb +3 -1
  13. data/lib/httpx/connection/http2.rb +1 -11
  14. data/lib/httpx/errors.rb +11 -11
  15. data/lib/httpx/io/ssl.rb +2 -2
  16. data/lib/httpx/io/tls.rb +1 -1
  17. data/lib/httpx/loggable.rb +1 -1
  18. data/lib/httpx/options.rb +81 -74
  19. data/lib/httpx/parser/http1.rb +1 -1
  20. data/lib/httpx/plugins/aws_sigv4.rb +10 -9
  21. data/lib/httpx/plugins/compression.rb +12 -11
  22. data/lib/httpx/plugins/cookies.rb +20 -7
  23. data/lib/httpx/plugins/cookies/cookie.rb +4 -3
  24. data/lib/httpx/plugins/cookies/jar.rb +24 -1
  25. data/lib/httpx/plugins/digest_authentication.rb +15 -11
  26. data/lib/httpx/plugins/expect.rb +19 -15
  27. data/lib/httpx/plugins/follow_redirects.rb +9 -9
  28. data/lib/httpx/plugins/grpc.rb +73 -47
  29. data/lib/httpx/plugins/grpc/call.rb +4 -1
  30. data/lib/httpx/plugins/ntlm_authentication.rb +8 -6
  31. data/lib/httpx/plugins/proxy.rb +4 -6
  32. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  33. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  34. data/lib/httpx/plugins/proxy/ssh.rb +9 -9
  35. data/lib/httpx/plugins/retries.rb +25 -21
  36. data/lib/httpx/plugins/upgrade.rb +7 -6
  37. data/lib/httpx/registry.rb +1 -1
  38. data/lib/httpx/request.rb +4 -12
  39. data/lib/httpx/resolver/https.rb +0 -2
  40. data/lib/httpx/response.rb +45 -18
  41. data/lib/httpx/selector.rb +13 -12
  42. data/lib/httpx/session.rb +19 -8
  43. data/lib/httpx/session2.rb +21 -0
  44. data/lib/httpx/transcoder/body.rb +1 -1
  45. data/lib/httpx/transcoder/chunker.rb +2 -1
  46. data/lib/httpx/version.rb +1 -1
  47. data/sig/buffer.rbs +2 -0
  48. data/sig/chainable.rbs +24 -28
  49. data/sig/connection.rbs +20 -8
  50. data/sig/connection/http1.rbs +3 -3
  51. data/sig/connection/http2.rbs +1 -1
  52. data/sig/errors.rbs +35 -1
  53. data/sig/headers.rbs +5 -5
  54. data/sig/httpx.rbs +4 -1
  55. data/sig/loggable.rbs +3 -1
  56. data/sig/options.rbs +35 -32
  57. data/sig/plugins/authentication.rbs +1 -1
  58. data/sig/plugins/aws_sdk_authentication.rbs +5 -1
  59. data/sig/plugins/aws_sigv4.rbs +1 -2
  60. data/sig/plugins/basic_authentication.rbs +1 -1
  61. data/sig/plugins/compression.rbs +4 -6
  62. data/sig/plugins/cookies.rbs +4 -5
  63. data/sig/plugins/cookies/cookie.rbs +5 -7
  64. data/sig/plugins/cookies/jar.rbs +9 -10
  65. data/sig/plugins/digest_authentication.rbs +2 -3
  66. data/sig/plugins/expect.rbs +2 -4
  67. data/sig/plugins/follow_redirects.rbs +3 -5
  68. data/sig/plugins/grpc.rbs +4 -7
  69. data/sig/plugins/h2c.rbs +0 -2
  70. data/sig/plugins/multipart.rbs +2 -4
  71. data/sig/plugins/ntlm_authentication.rbs +2 -3
  72. data/sig/plugins/persistent.rbs +3 -8
  73. data/sig/plugins/proxy.rbs +7 -7
  74. data/sig/plugins/proxy/ssh.rbs +4 -4
  75. data/sig/plugins/push_promise.rbs +0 -2
  76. data/sig/plugins/retries.rbs +4 -8
  77. data/sig/plugins/stream.rbs +1 -1
  78. data/sig/plugins/upgrade.rbs +2 -3
  79. data/sig/pool.rbs +1 -2
  80. data/sig/registry.rbs +1 -1
  81. data/sig/request.rbs +2 -2
  82. data/sig/resolver.rbs +7 -0
  83. data/sig/resolver/native.rbs +9 -5
  84. data/sig/resolver/resolver_mixin.rbs +4 -5
  85. data/sig/resolver/system.rbs +2 -0
  86. data/sig/response.rbs +17 -11
  87. data/sig/selector.rbs +7 -6
  88. data/sig/session.rbs +19 -14
  89. data/sig/transcoder.rbs +11 -4
  90. data/sig/transcoder/body.rbs +6 -1
  91. data/sig/transcoder/chunker.rbs +8 -2
  92. data/sig/transcoder/form.rbs +2 -1
  93. data/sig/transcoder/json.rbs +1 -0
  94. data/sig/utils.rbs +2 -0
  95. metadata +12 -2
@@ -2,7 +2,7 @@
2
2
 
3
3
  module HTTPX
4
4
  module Parser
5
- Error = Class.new(Error)
5
+ class Error < Error; end
6
6
 
7
7
  class HTTP1
8
8
  VERSIONS = %w[1.0 1.1].freeze
@@ -141,22 +141,23 @@ module HTTPX
141
141
  end
142
142
 
143
143
  class << self
144
- def extra_options(options)
145
- Class.new(options.class) do
146
- def_option(:sigv4_signer, <<-OUT)
147
- value.is_a?(#{Signer}) ? value : #{Signer}.new(value)
148
- OUT
149
- end.new(options)
150
- end
151
-
152
- def load_dependencies(klass)
144
+ def load_dependencies(*)
153
145
  require "digest/sha2"
154
146
  require "openssl"
147
+ end
148
+
149
+ def configure(klass)
155
150
  klass.plugin(:expect)
156
151
  klass.plugin(:compression)
157
152
  end
158
153
  end
159
154
 
155
+ module OptionsMethods
156
+ def option_sigv4_signer(value)
157
+ value.is_a?(Signer) ? value : Signer.new(value)
158
+ end
159
+ end
160
+
160
161
  module InstanceMethods
161
162
  def aws_sigv4_authentication(**options)
162
163
  with(sigv4_signer: Signer.new(**options))
@@ -23,21 +23,22 @@ module HTTPX
23
23
  encodings = Module.new do
24
24
  extend Registry
25
25
  end
26
+ options.merge(encodings: encodings)
27
+ end
28
+ end
26
29
 
27
- Class.new(options.class) do
28
- def_option(:compression_threshold_size, <<-OUT)
29
- bytes = Integer(value)
30
- raise Error, ":expect_threshold_size must be positive" unless bytes.positive?
30
+ module OptionsMethods
31
+ def option_compression_threshold_size(value)
32
+ bytes = Integer(value)
33
+ raise TypeError, ":expect_threshold_size must be positive" unless bytes.positive?
31
34
 
32
- bytes
33
- OUT
35
+ bytes
36
+ end
34
37
 
35
- def_option(:encodings, <<-OUT)
36
- raise Error, ":encodings must be a registry" unless value.respond_to?(:registry)
38
+ def option_encodings(value)
39
+ raise TypeError, ":encodings must be a registry" unless value.respond_to?(:registry)
37
40
 
38
- value
39
- OUT
40
- end.new(options).merge(encodings: encodings)
41
+ value
41
42
  end
42
43
  end
43
44
 
@@ -18,12 +18,10 @@ module HTTPX
18
18
  require "httpx/plugins/cookies/set_cookie_parser"
19
19
  end
20
20
 
21
- def self.extra_options(options)
22
- Class.new(options.class) do
23
- def_option(:cookies, <<-OUT)
24
- value.is_a?(#{Jar}) ? value : #{Jar}.new(value)
25
- OUT
26
- end.new(options)
21
+ module OptionsMethods
22
+ def option_cookies(value)
23
+ value.is_a?(Jar) ? value : Jar.new(value)
24
+ end
27
25
  end
28
26
 
29
27
  module InstanceMethods
@@ -63,7 +61,7 @@ module HTTPX
63
61
 
64
62
  def build_request(*, _)
65
63
  request = super
66
- request.headers.set_cookie(@options.cookies[request.uri])
64
+ request.headers.set_cookie(request.options.cookies[request.uri])
67
65
  request
68
66
  end
69
67
  end
@@ -77,6 +75,21 @@ module HTTPX
77
75
  add("cookie", header_value)
78
76
  end
79
77
  end
78
+
79
+ module OptionsMethods
80
+ def initialize(*)
81
+ super
82
+
83
+ return unless @headers.key?("cookie")
84
+
85
+ @headers.delete("cookie").each do |ck|
86
+ ck.split(/ *; */).each do |cookie|
87
+ name, value = cookie.split("=", 2)
88
+ @cookies.add(Cookie.new(name, value))
89
+ end
90
+ end
91
+ end
92
+ end
80
93
  end
81
94
  register_plugin :cookies, Cookies
82
95
  end
@@ -43,8 +43,7 @@ module HTTPX
43
43
  # Precedence: 1. longer path 2. older creation
44
44
  (@name <=> other.name).nonzero? ||
45
45
  (other.path.length <=> @path.length).nonzero? ||
46
- (@created_at <=> other.created_at).nonzero? ||
47
- @value <=> other.value
46
+ (@created_at <=> other.created_at).nonzero? || 0
48
47
  end
49
48
 
50
49
  class << self
@@ -108,6 +107,8 @@ module HTTPX
108
107
 
109
108
  @path ||= "/"
110
109
  raise ArgumentError, "name must be specified" if @name.nil?
110
+
111
+ @name = @name.to_s
111
112
  end
112
113
 
113
114
  def expires
@@ -123,7 +124,7 @@ module HTTPX
123
124
  # Returns a string for use in the Cookie header, i.e. `name=value`
124
125
  # or `name="value"`.
125
126
  def cookie_value
126
- "#{@name}=#{Scanner.quote(@value)}"
127
+ "#{@name}=#{Scanner.quote(@value.to_s)}"
127
128
  end
128
129
  alias_method :to_s, :cookie_value
129
130
 
@@ -43,6 +43,10 @@ module HTTPX
43
43
 
44
44
  c.path = path if path && c.path == "/"
45
45
 
46
+ # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
47
+ # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
48
+ @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
49
+
46
50
  @cookies << c
47
51
  end
48
52
 
@@ -53,7 +57,7 @@ module HTTPX
53
57
  def each(uri = nil, &blk)
54
58
  return enum_for(__method__, uri) unless block_given?
55
59
 
56
- return @store.each(&blk) unless uri
60
+ return @cookies.each(&blk) unless uri
57
61
 
58
62
  uri = URI(uri)
59
63
 
@@ -69,6 +73,25 @@ module HTTPX
69
73
  end
70
74
  end
71
75
  end
76
+
77
+ def merge(other)
78
+ cookies_dup = dup
79
+
80
+ other.each do |elem|
81
+ cookie = case elem
82
+ when Cookie
83
+ elem
84
+ when Array
85
+ Cookie.new(*elem)
86
+ else
87
+ Cookie.new(elem)
88
+ end
89
+
90
+ cookies_dup.add(cookie)
91
+ end
92
+
93
+ cookies_dup
94
+ end
72
95
  end
73
96
  end
74
97
  end
@@ -14,19 +14,23 @@ module HTTPX
14
14
 
15
15
  DigestError = Class.new(Error)
16
16
 
17
- def self.extra_options(options)
18
- Class.new(options.class) do
19
- def_option(:digest, <<-OUT)
20
- raise Error, ":digest must be a Digest" unless value.is_a?(#{Digest})
21
-
22
- value
23
- OUT
24
- end.new(options).merge(max_concurrent_requests: 1)
17
+ class << self
18
+ def extra_options(options)
19
+ options.merge(max_concurrent_requests: 1)
20
+ end
21
+
22
+ def load_dependencies(*)
23
+ require "securerandom"
24
+ require "digest"
25
+ end
25
26
  end
26
27
 
27
- def self.load_dependencies(*)
28
- require "securerandom"
29
- require "digest"
28
+ module OptionsMethods
29
+ def option_digest(value)
30
+ raise TypeError, ":digest must be a Digest" unless value.is_a?(Digest)
31
+
32
+ value
33
+ end
30
34
  end
31
35
 
32
36
  module InstanceMethods
@@ -10,26 +10,30 @@ module HTTPX
10
10
  module Expect
11
11
  EXPECT_TIMEOUT = 2
12
12
 
13
- def self.no_expect_store
14
- @no_expect_store ||= []
13
+ class << self
14
+ def no_expect_store
15
+ @no_expect_store ||= []
16
+ end
17
+
18
+ def extra_options(options)
19
+ options.merge(expect_timeout: EXPECT_TIMEOUT)
20
+ end
15
21
  end
16
22
 
17
- def self.extra_options(options)
18
- Class.new(options.class) do
19
- def_option(:expect_timeout, <<-OUT)
20
- seconds = Integer(value)
21
- raise Error, ":expect_timeout must be positive" unless seconds.positive?
23
+ module OptionsMethods
24
+ def option_expect_timeout(value)
25
+ seconds = Integer(value)
26
+ raise TypeError, ":expect_timeout must be positive" unless seconds.positive?
22
27
 
23
- seconds
24
- OUT
28
+ seconds
29
+ end
25
30
 
26
- def_option(:expect_threshold_size, <<-OUT)
27
- bytes = Integer(value)
28
- raise Error, ":expect_threshold_size must be positive" unless bytes.positive?
31
+ def option_expect_threshold_size(value)
32
+ bytes = Integer(value)
33
+ raise TypeError, ":expect_threshold_size must be positive" unless bytes.positive?
29
34
 
30
- bytes
31
- OUT
32
- end.new(options).merge(expect_timeout: EXPECT_TIMEOUT)
35
+ bytes
36
+ end
33
37
  end
34
38
 
35
39
  module RequestMethods
@@ -17,17 +17,17 @@ module HTTPX
17
17
  MAX_REDIRECTS = 3
18
18
  REDIRECT_STATUS = (300..399).freeze
19
19
 
20
- def self.extra_options(options)
21
- Class.new(options.class) do
22
- def_option(:max_redirects, <<-OUT)
23
- num = Integer(value)
24
- raise Error, ":max_redirects must be positive" if num.negative?
20
+ module OptionsMethods
21
+ def option_max_redirects(value)
22
+ num = Integer(value)
23
+ raise TypeError, ":max_redirects must be positive" if num.negative?
25
24
 
26
- num
27
- OUT
25
+ num
26
+ end
28
27
 
29
- def_option(:follow_insecure_redirects)
30
- end.new(options)
28
+ def option_follow_insecure_redirects(value)
29
+ value
30
+ end
31
31
  end
32
32
 
33
33
  module InstanceMethods
@@ -49,7 +49,6 @@ module HTTPX
49
49
  class << self
50
50
  def load_dependencies(*)
51
51
  require "stringio"
52
- require "google/protobuf"
53
52
  require "httpx/plugins/grpc/message"
54
53
  require "httpx/plugins/grpc/call"
55
54
  end
@@ -61,36 +60,7 @@ module HTTPX
61
60
  end
62
61
 
63
62
  def extra_options(options)
64
- Class.new(options.class) do
65
- def_option(:grpc_service, <<-OUT)
66
- String(value)
67
- OUT
68
-
69
- def_option(:grpc_compression, <<-OUT)
70
- case value
71
- when true, false
72
- value
73
- else
74
- value.to_s
75
- end
76
- OUT
77
-
78
- def_option(:grpc_rpcs, <<-OUT)
79
- Hash[value]
80
- OUT
81
-
82
- def_option(:grpc_deadline, <<-OUT)
83
- raise Error, ":grpc_deadline must be positive" unless value.positive?
84
-
85
- value
86
- OUT
87
-
88
- def_option(:call_credentials, <<-OUT)
89
- raise Error, ":call_credentials must respond to #call" unless value.respond_to?(:call)
90
-
91
- value
92
- OUT
93
- end.new(options).merge(
63
+ options.merge(
94
64
  fallback_protocol: "h2",
95
65
  http2_settings: { wait_for_handshake: false },
96
66
  grpc_rpcs: {}.freeze,
@@ -100,6 +70,37 @@ module HTTPX
100
70
  end
101
71
  end
102
72
 
73
+ module OptionsMethods
74
+ def option_grpc_service(value)
75
+ String(value)
76
+ end
77
+
78
+ def option_grpc_compression(value)
79
+ case value
80
+ when true, false
81
+ value
82
+ else
83
+ value.to_s
84
+ end
85
+ end
86
+
87
+ def option_grpc_rpcs(value)
88
+ Hash[value]
89
+ end
90
+
91
+ def option_grpc_deadline(value)
92
+ raise TypeError, ":grpc_deadline must be positive" unless value.positive?
93
+
94
+ value
95
+ end
96
+
97
+ def option_call_credentials(value)
98
+ raise TypeError, ":call_credentials must respond to #call" unless value.respond_to?(:call)
99
+
100
+ value
101
+ end
102
+ end
103
+
103
104
  module ResponseMethods
104
105
  attr_reader :trailing_metadata
105
106
 
@@ -140,9 +141,19 @@ module HTTPX
140
141
  deadline: @options.grpc_deadline,
141
142
  }.merge(opts)
142
143
 
143
- with(grpc_rpcs: @options.grpc_rpcs.merge(
144
- rpc_name.underscore => [rpc_name, input, output, rpc_opts]
145
- ).freeze)
144
+ session_class = Class.new(self.class) do
145
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
146
+ def #{rpc_name}(input, **opts)
147
+ rpc_execute("#{rpc_name}", input, **opts)
148
+ end
149
+ OUT
150
+ end
151
+
152
+ session_class.new(@options.merge(
153
+ grpc_rpcs: @options.grpc_rpcs.merge(
154
+ rpc_name.underscore => [rpc_name, input, output, rpc_opts]
155
+ ).freeze
156
+ ))
146
157
  end
147
158
 
148
159
  def build_stub(origin, service: nil, compression: false)
@@ -150,7 +161,32 @@ module HTTPX
150
161
 
151
162
  origin = URI.parse("#{scheme}://#{origin}")
152
163
 
153
- with(origin: origin, grpc_service: service, grpc_compression: compression)
164
+ session = self
165
+
166
+ if service && service.respond_to?(:rpc_descs)
167
+ # it's a grpc generic service
168
+ service.rpc_descs.each do |rpc_name, rpc_desc|
169
+ rpc_opts = {
170
+ marshal_method: rpc_desc.marshal_method,
171
+ unmarshal_method: rpc_desc.unmarshal_method,
172
+ }
173
+
174
+ input = rpc_desc.input
175
+ input = input.type if input.respond_to?(:type)
176
+
177
+ output = rpc_desc.output
178
+ if output.respond_to?(:type)
179
+ rpc_opts[:stream] = true
180
+ output = output.type
181
+ end
182
+
183
+ session = session.rpc(rpc_name, input, output, **rpc_opts)
184
+ end
185
+
186
+ service = service.service_name
187
+ end
188
+
189
+ session.with(origin: origin, grpc_service: service, grpc_compression: compression)
154
190
  end
155
191
 
156
192
  def execute(rpc_method, input,
@@ -166,7 +202,7 @@ module HTTPX
166
202
  private
167
203
 
168
204
  def rpc_execute(rpc_name, input, **opts)
169
- rpc_name, input_enc, output_enc, rpc_opts = @options.grpc_rpcs[rpc_name.to_s] || raise(Error, "#{rpc_name}: undefined service")
205
+ rpc_name, input_enc, output_enc, rpc_opts = @options.grpc_rpcs[rpc_name]
170
206
 
171
207
  exec_opts = rpc_opts.merge(opts)
172
208
 
@@ -180,7 +216,7 @@ module HTTPX
180
216
  end
181
217
  end
182
218
  else
183
- input_enc.marshal(input)
219
+ input_enc.__send__(marshal_method, input)
184
220
  end
185
221
 
186
222
  call = execute(rpc_name, messages, **exec_opts)
@@ -230,16 +266,6 @@ module HTTPX
230
266
 
231
267
  build_request(:post, uri, headers: headers, body: body)
232
268
  end
233
-
234
- def respond_to_missing?(meth, *, &blk)
235
- @options.grpc_rpcs.key?(meth.to_s) || super
236
- end
237
-
238
- def method_missing(meth, *args, **kwargs, &blk)
239
- return rpc_execute(meth, *args, **kwargs, &blk) if @options.grpc_rpcs.key?(meth.to_s)
240
-
241
- super
242
- end
243
269
  end
244
270
  end
245
271
  register_plugin :grpc, GRPC