httpx 0.15.3 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_15_4.md +5 -0
  3. data/doc/release_notes/0_16_0.md +93 -0
  4. data/doc/release_notes/0_16_1.md +5 -0
  5. data/doc/release_notes/0_17_0.md +49 -0
  6. data/lib/httpx/adapters/faraday.rb +3 -11
  7. data/lib/httpx/adapters/webmock.rb +2 -2
  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/http1.rb +18 -10
  12. data/lib/httpx/connection/http2.rb +14 -21
  13. data/lib/httpx/connection.rb +6 -7
  14. data/lib/httpx/errors.rb +11 -11
  15. data/lib/httpx/headers.rb +1 -1
  16. data/lib/httpx/io/ssl.rb +2 -2
  17. data/lib/httpx/io/tls.rb +1 -1
  18. data/lib/httpx/options.rb +108 -81
  19. data/lib/httpx/parser/http1.rb +11 -7
  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/cookie.rb +4 -2
  23. data/lib/httpx/plugins/cookies/jar.rb +20 -1
  24. data/lib/httpx/plugins/cookies.rb +20 -7
  25. data/lib/httpx/plugins/digest_authentication.rb +19 -15
  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/call.rb +4 -1
  29. data/lib/httpx/plugins/grpc.rb +73 -47
  30. data/lib/httpx/plugins/h2c.rb +7 -3
  31. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  32. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  33. data/lib/httpx/plugins/multipart/part.rb +2 -2
  34. data/lib/httpx/plugins/multipart.rb +14 -0
  35. data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
  36. data/lib/httpx/plugins/proxy/socks4.rb +2 -1
  37. data/lib/httpx/plugins/proxy/socks5.rb +2 -1
  38. data/lib/httpx/plugins/proxy/ssh.rb +20 -13
  39. data/lib/httpx/plugins/proxy.rb +10 -10
  40. data/lib/httpx/plugins/retries.rb +25 -21
  41. data/lib/httpx/plugins/stream.rb +2 -3
  42. data/lib/httpx/plugins/upgrade.rb +7 -6
  43. data/lib/httpx/registry.rb +2 -2
  44. data/lib/httpx/request.rb +10 -19
  45. data/lib/httpx/resolver/https.rb +0 -2
  46. data/lib/httpx/resolver/native.rb +15 -3
  47. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  48. data/lib/httpx/response.rb +72 -38
  49. data/lib/httpx/selector.rb +6 -7
  50. data/lib/httpx/session.rb +34 -21
  51. data/lib/httpx/session2.rb +23 -0
  52. data/lib/httpx/transcoder/body.rb +1 -1
  53. data/lib/httpx/transcoder/chunker.rb +2 -1
  54. data/lib/httpx/transcoder/form.rb +20 -0
  55. data/lib/httpx/transcoder/json.rb +12 -0
  56. data/lib/httpx/transcoder.rb +62 -1
  57. data/lib/httpx/utils.rb +2 -2
  58. data/lib/httpx/version.rb +1 -1
  59. data/lib/httpx.rb +6 -3
  60. data/sig/buffer.rbs +3 -1
  61. data/sig/chainable.rbs +30 -29
  62. data/sig/connection/http1.rbs +11 -5
  63. data/sig/connection/http2.rbs +16 -5
  64. data/sig/connection.rbs +23 -11
  65. data/sig/errors.rbs +35 -1
  66. data/sig/headers.rbs +20 -19
  67. data/sig/httpx.rbs +4 -1
  68. data/sig/loggable.rbs +3 -1
  69. data/sig/options.rbs +45 -34
  70. data/sig/parser/http1.rbs +3 -3
  71. data/sig/plugins/authentication.rbs +1 -1
  72. data/sig/plugins/aws_sdk_authentication.rbs +5 -1
  73. data/sig/plugins/aws_sigv4.rbs +13 -5
  74. data/sig/plugins/basic_authentication.rbs +1 -1
  75. data/sig/plugins/compression.rbs +4 -6
  76. data/sig/plugins/cookies/cookie.rbs +5 -7
  77. data/sig/plugins/cookies/jar.rbs +9 -10
  78. data/sig/plugins/cookies.rbs +4 -5
  79. data/sig/plugins/digest_authentication.rbs +2 -3
  80. data/sig/plugins/expect.rbs +2 -4
  81. data/sig/plugins/follow_redirects.rbs +3 -5
  82. data/sig/plugins/grpc.rbs +4 -7
  83. data/sig/plugins/h2c.rbs +0 -2
  84. data/sig/plugins/multipart.rbs +64 -10
  85. data/sig/plugins/ntlm_authentication.rbs +2 -3
  86. data/sig/plugins/persistent.rbs +3 -8
  87. data/sig/plugins/proxy/ssh.rbs +4 -4
  88. data/sig/plugins/proxy.rbs +13 -13
  89. data/sig/plugins/push_promise.rbs +0 -2
  90. data/sig/plugins/retries.rbs +4 -8
  91. data/sig/plugins/stream.rbs +1 -1
  92. data/sig/plugins/upgrade.rbs +2 -3
  93. data/sig/pool.rbs +1 -2
  94. data/sig/registry.rbs +1 -1
  95. data/sig/request.rbs +11 -8
  96. data/sig/resolver/native.rbs +12 -6
  97. data/sig/resolver/resolver_mixin.rbs +4 -5
  98. data/sig/resolver/system.rbs +2 -0
  99. data/sig/resolver.rbs +7 -0
  100. data/sig/response.rbs +24 -12
  101. data/sig/selector.rbs +11 -9
  102. data/sig/session.rbs +22 -23
  103. data/sig/transcoder/body.rbs +6 -1
  104. data/sig/transcoder/chunker.rbs +8 -2
  105. data/sig/transcoder/form.rbs +3 -1
  106. data/sig/transcoder/json.rbs +2 -0
  107. data/sig/transcoder.rbs +13 -5
  108. data/sig/utils.rbs +2 -0
  109. metadata +12 -2
@@ -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
 
@@ -43,7 +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?
46
+ (@created_at <=> other.created_at).nonzero? || 0
47
47
  end
48
48
 
49
49
  class << self
@@ -107,6 +107,8 @@ module HTTPX
107
107
 
108
108
  @path ||= "/"
109
109
  raise ArgumentError, "name must be specified" if @name.nil?
110
+
111
+ @name = @name.to_s
110
112
  end
111
113
 
112
114
  def expires
@@ -122,7 +124,7 @@ module HTTPX
122
124
  # Returns a string for use in the Cookie header, i.e. `name=value`
123
125
  # or `name="value"`.
124
126
  def cookie_value
125
- "#{@name}=#{Scanner.quote(@value)}"
127
+ "#{@name}=#{Scanner.quote(@value.to_s)}"
126
128
  end
127
129
  alias_method :to_s, :cookie_value
128
130
 
@@ -57,7 +57,7 @@ module HTTPX
57
57
  def each(uri = nil, &blk)
58
58
  return enum_for(__method__, uri) unless block_given?
59
59
 
60
- return @store.each(&blk) unless uri
60
+ return @cookies.each(&blk) unless uri
61
61
 
62
62
  uri = URI(uri)
63
63
 
@@ -73,6 +73,25 @@ module HTTPX
73
73
  end
74
74
  end
75
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
76
95
  end
77
96
  end
78
97
  end
@@ -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
@@ -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
@@ -36,12 +40,12 @@ module HTTPX
36
40
 
37
41
  alias_method :digest_auth, :digest_authentication
38
42
 
39
- def send_requests(*requests, options)
43
+ def send_requests(*requests)
40
44
  requests.flat_map do |request|
41
45
  digest = request.options.digest
42
46
 
43
47
  if digest
44
- probe_response = wrap { super(request, options).first }
48
+ probe_response = wrap { super(request).first }
45
49
 
46
50
  if digest && !probe_response.is_a?(ErrorResponse) &&
47
51
  probe_response.status == 401 && probe_response.headers.key?("www-authenticate") &&
@@ -52,12 +56,12 @@ module HTTPX
52
56
  token = digest.generate_header(request, probe_response)
53
57
  request.headers["authorization"] = "Digest #{token}"
54
58
 
55
- super(request, options)
59
+ super(request)
56
60
  else
57
61
  probe_response
58
62
  end
59
63
  else
60
- super(request, options)
64
+ super(request)
61
65
  end
62
66
  end
63
67
  end
@@ -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
@@ -10,6 +10,7 @@ module HTTPX
10
10
  def initialize(response)
11
11
  @response = response
12
12
  @decoder = ->(z) { z }
13
+ @consumed = false
13
14
  end
14
15
 
15
16
  def inspect
@@ -25,7 +26,7 @@ module HTTPX
25
26
  end
26
27
 
27
28
  def trailing_metadata
28
- return unless @response.body.closed?
29
+ return unless @consumed
29
30
 
30
31
  @response.trailing_metadata
31
32
  end
@@ -40,8 +41,10 @@ module HTTPX
40
41
  Message.stream(@response).each do |message|
41
42
  y << @decoder.call(message)
42
43
  end
44
+ @consumed = true
43
45
  end
44
46
  else
47
+ @consumed = true
45
48
  @decoder.call(Message.unary(@response))
46
49
  end
47
50
  end
@@ -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