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
@@ -69,10 +69,10 @@ module HTTPX
69
69
  end
70
70
 
71
71
  @inflight = 0
72
- @keep_alive_timeout = options.timeout[:keep_alive_timeout]
72
+ @keep_alive_timeout = @options.timeout[:keep_alive_timeout]
73
73
  @keep_alive_timer = nil
74
74
 
75
- self.addresses = options.addresses if options.addresses
75
+ self.addresses = @options.addresses if @options.addresses
76
76
  end
77
77
 
78
78
  # this is a semi-private method, to be used by the resolver
@@ -313,7 +313,7 @@ module HTTPX
313
313
 
314
314
  # exit #consume altogether if all outstanding requests have been dealt with
315
315
  return if @pending.size.zero? && @inflight.zero?
316
- end unless (interests.nil? || interests == :w || @state == :closing) && !epiped
316
+ end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
317
317
 
318
318
  #
319
319
  # tight write loop.
@@ -360,19 +360,18 @@ module HTTPX
360
360
  break if interests == :r || @state == :closing || @state == :closed
361
361
 
362
362
  write_drained = false
363
- end unless interests == :r
363
+ end unless (ints = interests) == :r
364
364
 
365
365
  send_pending if @state == :open
366
366
 
367
367
  # return if socket is drained
368
- next unless (interests != :r || read_drained) &&
369
- (interests != :w || write_drained)
368
+ next unless (ints != :r || read_drained) && (ints != :w || write_drained)
370
369
 
371
370
  # gotta go back to the event loop. It happens when:
372
371
  #
373
372
  # * the socket is drained of bytes or it's not the interest of the conn to read;
374
373
  # * theres nothing more to write, or it's not in the interest of the conn to write;
375
- log(level: 3) { "(#{interests}): WAITING FOR EVENTS..." }
374
+ log(level: 3) { "(#{ints}): WAITING FOR EVENTS..." }
376
375
  return
377
376
  end
378
377
  end
data/lib/httpx/errors.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- Error = Class.new(StandardError)
4
+ class Error < StandardError; end
5
5
 
6
- UnsupportedSchemeError = Class.new(Error)
6
+ class UnsupportedSchemeError < Error; end
7
7
 
8
- TimeoutError = Class.new(Error) do
8
+ class TimeoutError < Error
9
9
  attr_reader :timeout
10
10
 
11
11
  def initialize(timeout, message)
@@ -20,17 +20,17 @@ module HTTPX
20
20
  end
21
21
  end
22
22
 
23
- TotalTimeoutError = Class.new(TimeoutError)
23
+ class TotalTimeoutError < TimeoutError; end
24
24
 
25
- ConnectTimeoutError = Class.new(TimeoutError)
25
+ class ConnectTimeoutError < TimeoutError; end
26
26
 
27
- SettingsTimeoutError = Class.new(TimeoutError)
27
+ class SettingsTimeoutError < TimeoutError; end
28
28
 
29
- ResolveTimeoutError = Class.new(TimeoutError)
29
+ class ResolveTimeoutError < TimeoutError; end
30
30
 
31
- ResolveError = Class.new(Error)
31
+ class ResolveError < Error; end
32
32
 
33
- NativeResolveError = Class.new(ResolveError) do
33
+ class NativeResolveError < ResolveError
34
34
  attr_reader :connection, :host
35
35
 
36
36
  def initialize(connection, host, message = "Can't resolve #{host}")
@@ -40,7 +40,7 @@ module HTTPX
40
40
  end
41
41
  end
42
42
 
43
- HTTPError = Class.new(Error) do
43
+ class HTTPError < Error
44
44
  attr_reader :response
45
45
 
46
46
  def initialize(response)
@@ -53,5 +53,5 @@ module HTTPX
53
53
  end
54
54
  end
55
55
 
56
- MisdirectedRequestError = Class.new(HTTPError)
56
+ class MisdirectedRequestError < HTTPError; end
57
57
  end
data/lib/httpx/headers.rb CHANGED
@@ -57,7 +57,7 @@ module HTTPX
57
57
  def merge(other)
58
58
  headers = dup
59
59
  other.each do |field, value|
60
- headers[field] = value
60
+ headers[downcased(field)] = value
61
61
  end
62
62
  headers
63
63
  end
data/lib/httpx/io/ssl.rb CHANGED
@@ -7,9 +7,9 @@ module HTTPX
7
7
 
8
8
  class SSL < TCP
9
9
  TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
10
- { alpn_protocols: %w[h2 http/1.1] }
10
+ { alpn_protocols: %w[h2 http/1.1].freeze }.freeze
11
11
  else
12
- {}
12
+ {}.freeze
13
13
  end
14
14
 
15
15
  def initialize(_, _, options)
data/lib/httpx/io/tls.rb CHANGED
@@ -4,7 +4,7 @@ require "openssl"
4
4
 
5
5
  module HTTPX
6
6
  class TLS < TCP
7
- Error = Class.new(StandardError)
7
+ class Error < StandardError; end
8
8
 
9
9
  def initialize(_, _, options)
10
10
  super
data/lib/httpx/options.rb CHANGED
@@ -30,6 +30,7 @@ module HTTPX
30
30
  :request_body_class => Class.new(Request::Body),
31
31
  :response_body_class => Class.new(Response::Body),
32
32
  :connection_class => Class.new(Connection),
33
+ :options_class => Class.new(self),
33
34
  :transport => nil,
34
35
  :transport_options => nil,
35
36
  :addresses => nil,
@@ -38,71 +39,100 @@ module HTTPX
38
39
  :resolver_options => { cache: true },
39
40
  }.freeze
40
41
 
42
+ begin
43
+ module HashExtensions
44
+ refine Hash do
45
+ def >=(other)
46
+ Hash[other] <= self
47
+ end
48
+
49
+ def <=(other)
50
+ other = Hash[other]
51
+ return false unless size <= other.size
52
+
53
+ each do |k, v|
54
+ v2 = other.fetch(k) { return false }
55
+ return false unless v2 == v
56
+ end
57
+ true
58
+ end
59
+ end
60
+ end
61
+ using HashExtensions
62
+ end unless Hash.method_defined?(:>=)
63
+
41
64
  class << self
42
65
  def new(options = {})
43
66
  # let enhanced options go through
44
- return options if self == Options && options.class > self
67
+ return options if self == Options && options.class < self
45
68
  return options if options.is_a?(self)
46
69
 
47
70
  super
48
71
  end
49
72
 
50
- def def_option(name, layout = nil, &interpreter)
51
- attr_reader name
73
+ def method_added(meth)
74
+ super
52
75
 
53
- if layout
54
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
55
- def #{name}=(value)
56
- return if value.nil?
76
+ return unless meth =~ /^option_(.+)$/
57
77
 
58
- value = begin
59
- #{layout}
60
- end
78
+ optname = Regexp.last_match(1).to_sym
61
79
 
62
- @#{name} = value
63
- end
80
+ attr_reader(optname)
81
+ end
82
+
83
+ def def_option(optname, *args, &block)
84
+ if args.size.zero? && !block_given?
85
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
86
+ def option_#{optname}(v); v; end
64
87
  OUT
88
+ return
89
+ end
65
90
 
66
- elsif interpreter
67
- define_method(:"#{name}=") do |value|
68
- return if value.nil?
91
+ deprecated_def_option(optname, *args, &block)
92
+ end
93
+
94
+ def deprecated_def_option(optname, layout = nil, &interpreter)
95
+ warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
96
+ "Define module OptionsMethods and `def option_#{optname}(val)` instead."
69
97
 
70
- instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
98
+ if layout
99
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
100
+ def option_#{optname}(value)
101
+ #{layout}
102
+ end
103
+ OUT
104
+ elsif block_given?
105
+ define_method(:"option_#{optname}") do |value|
106
+ instance_exec(value, &interpreter)
71
107
  end
72
- else
73
- attr_writer name
74
108
  end
75
-
76
- protected :"#{name}="
77
109
  end
78
110
  end
79
111
 
80
112
  def initialize(options = {})
81
113
  defaults = DEFAULT_OPTIONS.merge(options)
82
- defaults.each do |(k, v)|
114
+ defaults.each do |k, v|
83
115
  next if v.nil?
84
116
 
85
117
  begin
86
- __send__(:"#{k}=", v)
118
+ value = __send__(:"option_#{k}", v)
119
+ instance_variable_set(:"@#{k}", value)
87
120
  rescue NoMethodError
88
121
  raise Error, "unknown option: #{k}"
89
122
  end
90
123
  end
124
+ freeze
91
125
  end
92
126
 
93
- def_option(:origin, <<-OUT)
127
+ def option_origin(value)
94
128
  URI(value)
95
- OUT
129
+ end
96
130
 
97
- def_option(:headers, <<-OUT)
98
- if self.headers
99
- self.headers.merge(value)
100
- else
101
- Headers.new(value)
102
- end
103
- OUT
131
+ def option_headers(value)
132
+ Headers.new(value)
133
+ end
104
134
 
105
- def_option(:timeout, <<-OUT)
135
+ def option_timeout(value)
106
136
  timeouts = Hash[value]
107
137
 
108
138
  if timeouts.key?(:loop_timeout)
@@ -111,42 +141,43 @@ module HTTPX
111
141
  end
112
142
 
113
143
  timeouts
114
- OUT
144
+ end
115
145
 
116
- def_option(:max_concurrent_requests, <<-OUT)
117
- raise Error, ":max_concurrent_requests must be positive" unless value.positive?
146
+ def option_max_concurrent_requests(value)
147
+ raise TypeError, ":max_concurrent_requests must be positive" unless value.positive?
118
148
 
119
149
  value
120
- OUT
150
+ end
121
151
 
122
- def_option(:max_requests, <<-OUT)
123
- raise Error, ":max_requests must be positive" unless value.positive?
152
+ def option_max_requests(value)
153
+ raise TypeError, ":max_requests must be positive" unless value.positive?
124
154
 
125
155
  value
126
- OUT
156
+ end
127
157
 
128
- def_option(:window_size, <<-OUT)
158
+ def option_window_size(value)
129
159
  Integer(value)
130
- OUT
160
+ end
131
161
 
132
- def_option(:body_threshold_size, <<-OUT)
162
+ def option_body_threshold_size(value)
133
163
  Integer(value)
134
- OUT
164
+ end
135
165
 
136
- def_option(:transport, <<-OUT)
166
+ def option_transport(value)
137
167
  transport = value.to_s
138
- raise Error, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
168
+ raise TypeError, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
139
169
 
140
170
  transport
141
- OUT
171
+ end
142
172
 
143
- def_option(:addresses, <<-OUT)
173
+ def option_addresses(value)
144
174
  Array(value)
145
- OUT
175
+ end
146
176
 
147
177
  %i[
148
178
  params form json body ssl http2_settings
149
- request_class response_class headers_class request_body_class response_body_class connection_class
179
+ request_class response_class headers_class request_body_class
180
+ response_body_class connection_class options_class
150
181
  io fallback_protocol debug debug_level transport_options resolver_class resolver_options
151
182
  persistent
152
183
  ].each do |method_name|
@@ -154,6 +185,7 @@ module HTTPX
154
185
  end
155
186
 
156
187
  REQUEST_IVARS = %i[@params @form @json @body].freeze
188
+ private_constant :REQUEST_IVARS
157
189
 
158
190
  def ==(other)
159
191
  ivars = instance_variables | other.instance_variables
@@ -171,18 +203,17 @@ module HTTPX
171
203
  end
172
204
 
173
205
  def merge(other)
174
- raise ArgumentError, "#{other.inspect} is not a valid set of options" unless other.respond_to?(:to_hash)
206
+ raise ArgumentError, "#{other} is not a valid set of options" unless other.respond_to?(:to_hash)
175
207
 
176
208
  h2 = other.to_hash
177
209
  return self if h2.empty?
178
210
 
179
211
  h1 = to_hash
180
212
 
181
- return self if h1 == h2
213
+ return self if h1 >= h2
182
214
 
183
- merged = h1.merge(h2) do |k, v1, v2|
184
- case k
185
- when :headers, :ssl, :http2_settings, :timeout
215
+ merged = h1.merge(h2) do |_k, v1, v2|
216
+ if v1.respond_to?(:merge) && v2.respond_to?(:merge)
186
217
  v1.merge(v2)
187
218
  else
188
219
  v2
@@ -193,34 +224,30 @@ module HTTPX
193
224
  end
194
225
 
195
226
  def to_hash
196
- hash_pairs = instance_variables.map do |ivar|
197
- [ivar[1..-1].to_sym, instance_variable_get(ivar)]
227
+ instance_variables.each_with_object({}) do |ivar, hs|
228
+ hs[ivar[1..-1].to_sym] = instance_variable_get(ivar)
229
+ end
230
+ end
231
+
232
+ if RUBY_VERSION > "2.4.0"
233
+ def initialize_dup(other)
234
+ instance_variables.each do |ivar|
235
+ instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
236
+ end
237
+ end
238
+ else
239
+ def initialize_dup(other)
240
+ instance_variables.each do |ivar|
241
+ value = other.instance_variable_get(ivar)
242
+ value = case value
243
+ when Symbol, Fixnum, TrueClass, FalseClass # rubocop:disable Lint/UnifiedInteger
244
+ value
245
+ else
246
+ value.dup
247
+ end
248
+ instance_variable_set(ivar, value)
249
+ end
198
250
  end
199
- Hash[hash_pairs]
200
- end
201
-
202
- def initialize_dup(other)
203
- self.headers = other.headers.dup
204
- self.ssl = other.ssl.dup
205
- self.request_class = other.request_class.dup
206
- self.response_class = other.response_class.dup
207
- self.headers_class = other.headers_class.dup
208
- self.request_body_class = other.request_body_class.dup
209
- self.response_body_class = other.response_body_class.dup
210
- self.connection_class = other.connection_class.dup
211
- end
212
-
213
- def freeze
214
- super
215
-
216
- headers.freeze
217
- ssl.freeze
218
- request_class.freeze
219
- response_class.freeze
220
- headers_class.freeze
221
- request_body_class.freeze
222
- response_body_class.freeze
223
- connection_class.freeze
224
251
  end
225
252
  end
226
253
  end
@@ -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
@@ -60,7 +60,7 @@ module HTTPX
60
60
  (m = %r{\AHTTP(?:/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?}in.match(@buffer)) ||
61
61
  raise(Error, "wrong head line format")
62
62
  version, code, _ = m.captures
63
- raise(Error, "unsupported HTTP version (HTTP/#{version})") unless VERSIONS.include?(version)
63
+ raise(Error, "unsupported HTTP version (HTTP/#{version})") unless version && VERSIONS.include?(version)
64
64
 
65
65
  @http_version = version.split(".").map(&:to_i)
66
66
  @status_code = code.to_i
@@ -72,9 +72,14 @@ module HTTPX
72
72
 
73
73
  def parse_headers
74
74
  headers = @headers
75
- while (idx = @buffer.index("\n"))
76
- line = @buffer.byteslice(0..idx).sub(/\s+\z/, "")
77
- @buffer = @buffer.byteslice((idx + 1)..-1)
75
+ buffer = @buffer
76
+
77
+ while (idx = buffer.index("\n"))
78
+ line = buffer.byteslice(0..idx)
79
+ raise Error, "wrong header format" if line.start_with?("\s", "\t")
80
+
81
+ line.lstrip!
82
+ buffer = @buffer = buffer.byteslice((idx + 1)..-1)
78
83
  if line.empty?
79
84
  case @state
80
85
  when :headers
@@ -97,9 +102,8 @@ module HTTPX
97
102
  raise Error, "wrong header format" unless separator_index
98
103
 
99
104
  key = line.byteslice(0..(separator_index - 1))
100
- raise Error, "wrong header format" if key.start_with?("\s", "\t")
101
105
 
102
- key.strip!
106
+ key.rstrip! # was lstripped previously!
103
107
  value = line.byteslice((separator_index + 1)..-1)
104
108
  value.strip!
105
109
  raise Error, "wrong header format" if value.nil?