httpx 0.15.3 → 0.17.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 (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?