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
@@ -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
@@ -15,13 +15,15 @@ module HTTPX
15
15
  end
16
16
 
17
17
  def extra_options(options)
18
- Class.new(options.class) do
19
- def_option(:ntlm, <<-OUT)
20
- raise Error, ":ntlm must be a #{NTLMParams}" unless value.is_a?(#{NTLMParams})
18
+ options.merge(max_concurrent_requests: 1)
19
+ end
20
+ end
21
+
22
+ module OptionsMethods
23
+ def option_ntlm(value)
24
+ raise TypeError, ":ntlm must be a #{NTLMParams}" unless value.is_a?(NTLMParams)
21
25
 
22
- value
23
- OUT
24
- end.new(options).merge(max_concurrent_requests: 1)
26
+ value
25
27
  end
26
28
  end
27
29
 
@@ -64,13 +64,11 @@ module HTTPX
64
64
  klass.plugin(:"proxy/socks4")
65
65
  klass.plugin(:"proxy/socks5")
66
66
  end
67
+ end
67
68
 
68
- def extra_options(options)
69
- Class.new(options.class) do
70
- def_option(:proxy, <<-OUT)
71
- value.is_a?(#{Parameters}) ? value : Hash[value]
72
- OUT
73
- end.new(options)
69
+ module OptionsMethods
70
+ def option_proxy(value)
71
+ value.is_a?(Parameters) ? value : Hash[value]
74
72
  end
75
73
  end
76
74
 
@@ -4,7 +4,8 @@ require "resolv"
4
4
  require "ipaddr"
5
5
 
6
6
  module HTTPX
7
- Socks4Error = Class.new(Error)
7
+ class Socks4Error < Error; end
8
+
8
9
  module Plugins
9
10
  module Proxy
10
11
  module Socks4
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- Socks5Error = Class.new(Error)
4
+ class Socks5Error < Error; end
5
+
5
6
  module Plugins
6
7
  module Proxy
7
8
  module Socks5
@@ -6,16 +6,16 @@ module HTTPX
6
6
  module Plugins
7
7
  module Proxy
8
8
  module SSH
9
- def self.load_dependencies(*)
10
- require "net/ssh/gateway"
9
+ class << self
10
+ def load_dependencies(*)
11
+ require "net/ssh/gateway"
12
+ end
11
13
  end
12
14
 
13
- def self.extra_options(options)
14
- Class.new(options.class) do
15
- def_option(:proxy, <<-OUT)
16
- Hash[value]
17
- OUT
18
- end.new(options)
15
+ module OptionsMethods
16
+ def option_proxy(value)
17
+ Hash[value]
18
+ end
19
19
  end
20
20
 
21
21
  module InstanceMethods
@@ -65,7 +65,7 @@ module HTTPX
65
65
  when "http"
66
66
  TCPSocket.open("localhost", port)
67
67
  else
68
- raise Error, "unexpected scheme: #{request_uri.scheme}"
68
+ raise TypeError, "unexpected scheme: #{request_uri.scheme}"
69
69
  end
70
70
  end
71
71
  end
@@ -17,39 +17,43 @@ module HTTPX
17
17
  Errno::ECONNRESET,
18
18
  Errno::ECONNABORTED,
19
19
  Errno::EPIPE,
20
- (TLSError if defined?(TLSError)),
20
+ TLSError,
21
21
  TimeoutError,
22
22
  Parser::Error,
23
23
  Errno::EINVAL,
24
24
  Errno::ETIMEDOUT].freeze
25
25
 
26
26
  def self.extra_options(options)
27
- Class.new(options.class) do
28
- def_option(:retry_after, <<-OUT)
29
- # return early if callable
30
- unless value.respond_to?(:call)
31
- value = Integer(value)
32
- raise Error, ":retry_after must be positive" unless value.positive?
33
- end
27
+ options.merge(max_retries: MAX_RETRIES)
28
+ end
29
+
30
+ module OptionsMethods
31
+ def option_retry_after(value)
32
+ # return early if callable
33
+ unless value.respond_to?(:call)
34
+ value = Integer(value)
35
+ raise TypeError, ":retry_after must be positive" unless value.positive?
36
+ end
34
37
 
35
- value
36
- OUT
38
+ value
39
+ end
37
40
 
38
- def_option(:max_retries, <<-OUT)
39
- num = Integer(value)
40
- raise Error, ":max_retries must be positive" unless num.positive?
41
+ def option_max_retries(value)
42
+ num = Integer(value)
43
+ raise TypeError, ":max_retries must be positive" unless num.positive?
41
44
 
42
- num
43
- OUT
45
+ num
46
+ end
44
47
 
45
- def_option(:retry_change_requests)
48
+ def option_retry_change_requests(v)
49
+ v
50
+ end
46
51
 
47
- def_option(:retry_on, <<-OUT)
48
- raise ":retry_on must be called with the response" unless value.respond_to?(:call)
52
+ def option_retry_on(value)
53
+ raise ":retry_on must be called with the response" unless value.respond_to?(:call)
49
54
 
50
- value
51
- OUT
52
- end.new(options).merge(max_retries: MAX_RETRIES)
55
+ value
56
+ end
53
57
  end
54
58
 
55
59
  module InstanceMethods
@@ -18,14 +18,15 @@ module HTTPX
18
18
  upgrade_handlers = Module.new do
19
19
  extend Registry
20
20
  end
21
+ options.merge(upgrade_handlers: upgrade_handlers)
22
+ end
23
+ end
21
24
 
22
- Class.new(options.class) do
23
- def_option(:upgrade_handlers, <<-OUT)
24
- raise Error, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
25
+ module OptionsMethods
26
+ def option_upgrade_handlers(value)
27
+ raise TypeError, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
25
28
 
26
- value
27
- OUT
28
- end.new(options).merge(upgrade_handlers: upgrade_handlers)
29
+ value
29
30
  end
30
31
  end
31
32
 
@@ -31,7 +31,7 @@ module HTTPX
31
31
  #
32
32
  module Registry
33
33
  # Base Registry Error
34
- Error = Class.new(Error)
34
+ class Error < Error; end
35
35
 
36
36
  def self.extended(klass)
37
37
  super
data/lib/httpx/request.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
3
4
  require "forwardable"
4
5
 
5
6
  module HTTPX
@@ -155,7 +156,7 @@ module HTTPX
155
156
  end
156
157
  # :nocov:
157
158
 
158
- class Body
159
+ class Body < SimpleDelegator
159
160
  class << self
160
161
  def new(*, options)
161
162
  return options.body if options.body.is_a?(self)
@@ -177,6 +178,7 @@ module HTTPX
177
178
 
178
179
  @headers["content-type"] ||= @body.content_type
179
180
  @headers["content-length"] = @body.bytesize unless unbounded_body?
181
+ super(@body)
180
182
  end
181
183
 
182
184
  def each(&block)
@@ -214,7 +216,7 @@ module HTTPX
214
216
 
215
217
  def stream(body)
216
218
  encoded = body
217
- encoded = Transcoder.registry("chunker").encode(body) if chunked?
219
+ encoded = Transcoder.registry("chunker").encode(body.enum_for(:each)) if chunked?
218
220
  encoded
219
221
  end
220
222
 
@@ -238,16 +240,6 @@ module HTTPX
238
240
  "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
239
241
  end
240
242
  # :nocov:
241
-
242
- def respond_to_missing?(meth, *args)
243
- @body.respond_to?(meth, *args) || super
244
- end
245
-
246
- def method_missing(meth, *args, &block)
247
- return super unless @body.respond_to?(meth)
248
-
249
- @body.__send__(meth, *args, &block)
250
- end
251
243
  end
252
244
 
253
245
  def transition(nextstate)
@@ -24,8 +24,6 @@ module HTTPX
24
24
  record_types: RECORD_TYPES.keys,
25
25
  }.freeze
26
26
 
27
- def_delegator :@connections, :empty?
28
-
29
27
  def_delegators :@resolver_connection, :connecting?, :to_io, :call, :close
30
28
 
31
29
  def initialize(options)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "objspace"
3
4
  require "stringio"
4
5
  require "tempfile"
5
6
  require "fileutils"
@@ -92,6 +93,16 @@ module HTTPX
92
93
  @length = 0
93
94
  @buffer = nil
94
95
  @state = :idle
96
+ ObjectSpace.define_finalizer(self, self.class.finalize(@buffer))
97
+ end
98
+
99
+ def self.finalize(buffer)
100
+ proc {
101
+ return unless buffer
102
+
103
+ @buffer.close
104
+ @buffer.unlink if @buffer.respond_to?(:unlink)
105
+ }
95
106
  end
96
107
 
97
108
  def closed?
@@ -134,18 +145,26 @@ module HTTPX
134
145
  end
135
146
 
136
147
  def to_s
137
- rewind
138
- if @buffer
139
- content = @buffer.read
148
+ case @buffer
149
+ when StringIO
150
+ begin
151
+ @buffer.string.force_encoding(@encoding)
152
+ rescue ArgumentError
153
+ @buffer.string
154
+ end
155
+ when Tempfile, File
156
+ rewind
157
+ content = _with_same_buffer_pos { @buffer.read }
140
158
  begin
141
- return content.force_encoding(@encoding)
159
+ content.force_encoding(@encoding)
142
160
  rescue ArgumentError # ex: unknown encoding name - utf
143
- return content
161
+ content
144
162
  end
163
+ when nil
164
+ "".b
165
+ else
166
+ @buffer
145
167
  end
146
- "".b
147
- ensure
148
- close
149
168
  end
150
169
  alias_method :to_str, :to_s
151
170
 
@@ -177,7 +196,11 @@ module HTTPX
177
196
  end
178
197
 
179
198
  def ==(other)
180
- to_s == other.to_s
199
+ if other.respond_to?(:read)
200
+ _with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
201
+ else
202
+ to_s == other.to_s
203
+ end
181
204
  end
182
205
 
183
206
  # :nocov:
@@ -204,7 +227,7 @@ module HTTPX
204
227
  @buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
205
228
  else
206
229
  @state = :memory
207
- @buffer = StringIO.new("".b, File::RDWR)
230
+ @buffer = StringIO.new("".b)
208
231
  end
209
232
  when :memory
210
233
  if @length > @threshold_size
@@ -222,6 +245,18 @@ module HTTPX
222
245
 
223
246
  return unless %i[memory buffer].include?(@state)
224
247
  end
248
+
249
+ def _with_same_buffer_pos
250
+ return yield unless @buffer && @buffer.respond_to?(:pos)
251
+
252
+ current_pos = @buffer.pos
253
+ @buffer.rewind
254
+ begin
255
+ yield
256
+ rescue StandardError
257
+ @buffer.pos = current_pos
258
+ end
259
+ end
225
260
  end
226
261
  end
227
262
 
@@ -286,14 +321,6 @@ module HTTPX
286
321
  def raise_for_status
287
322
  raise @error
288
323
  end
289
-
290
- # rubocop:disable Style/MissingRespondToMissing
291
- def method_missing(meth, *, &block)
292
- raise NoMethodError, "undefined response method `#{meth}' for error response" if @options.response_class.public_method_defined?(meth)
293
-
294
- super
295
- end
296
- # rubocop:enable Style/MissingRespondToMissing
297
324
  end
298
325
  end
299
326
 
@@ -43,9 +43,6 @@ class HTTPX::Selector
43
43
 
44
44
  private
45
45
 
46
- READ_INTERESTS = %i[r rw].freeze
47
- WRITE_INTERESTS = %i[w rw].freeze
48
-
49
46
  def select_many(interval, &block)
50
47
  selectables, r, w = nil
51
48
 
@@ -64,8 +61,8 @@ class HTTPX::Selector
64
61
  selectables.each do |io|
65
62
  interests = io.interests
66
63
 
67
- (r ||= []) << io if READ_INTERESTS.include?(interests)
68
- (w ||= []) << io if WRITE_INTERESTS.include?(interests)
64
+ (r ||= []) << io if READABLE.include?(interests)
65
+ (w ||= []) << io if WRITABLE.include?(interests)
69
66
  end
70
67
 
71
68
  if @selectables.empty?
@@ -77,7 +74,7 @@ class HTTPX::Selector
77
74
 
78
75
  break
79
76
  else
80
- @selectables = [*selectables, @selectables]
77
+ @selectables.concat(selectables)
81
78
  end
82
79
  rescue StandardError
83
80
  @selectables = selectables if selectables
@@ -95,14 +92,18 @@ class HTTPX::Selector
95
92
  retry
96
93
  end
97
94
 
98
- readers.each do |io|
99
- yield io
95
+ if writers
96
+ readers.each do |io|
97
+ yield io
100
98
 
101
- # so that we don't yield 2 times
102
- writers.delete(io)
103
- end if readers
99
+ # so that we don't yield 2 times
100
+ writers.delete(io)
101
+ end if readers
104
102
 
105
- writers.each(&block) if writers
103
+ writers.each(&block)
104
+ else
105
+ readers.each(&block) if readers
106
+ end
106
107
  end
107
108
 
108
109
  def select_one(interval)