faraday 0.16.2 → 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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +347 -18
  4. data/lib/faraday.rb +175 -93
  5. data/lib/faraday/adapter.rb +22 -36
  6. data/lib/faraday/adapter/em_http.rb +99 -142
  7. data/lib/faraday/adapter/em_http_ssl_patch.rb +17 -23
  8. data/lib/faraday/adapter/em_synchrony.rb +60 -104
  9. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +15 -18
  10. data/lib/faraday/adapter/excon.rb +55 -100
  11. data/lib/faraday/adapter/httpclient.rb +39 -61
  12. data/lib/faraday/adapter/net_http.rb +51 -104
  13. data/lib/faraday/adapter/net_http_persistent.rb +27 -48
  14. data/lib/faraday/adapter/patron.rb +35 -54
  15. data/lib/faraday/adapter/rack.rb +12 -28
  16. data/lib/faraday/adapter/test.rb +53 -86
  17. data/lib/faraday/adapter/typhoeus.rb +1 -4
  18. data/lib/faraday/autoload.rb +36 -47
  19. data/lib/faraday/connection.rb +179 -321
  20. data/lib/faraday/error.rb +32 -80
  21. data/lib/faraday/middleware.rb +28 -4
  22. data/lib/faraday/options.rb +186 -35
  23. data/lib/faraday/parameters.rb +197 -4
  24. data/lib/faraday/rack_builder.rb +56 -67
  25. data/lib/faraday/request.rb +36 -68
  26. data/lib/faraday/request/authorization.rb +30 -42
  27. data/lib/faraday/request/basic_authentication.rb +7 -14
  28. data/lib/faraday/request/instrumentation.rb +27 -45
  29. data/lib/faraday/request/multipart.rb +48 -79
  30. data/lib/faraday/request/retry.rb +170 -197
  31. data/lib/faraday/request/token_authentication.rb +10 -15
  32. data/lib/faraday/request/url_encoded.rb +23 -41
  33. data/lib/faraday/response.rb +16 -23
  34. data/lib/faraday/response/logger.rb +69 -22
  35. data/lib/faraday/response/raise_error.rb +14 -36
  36. data/lib/faraday/upload_io.rb +67 -0
  37. data/lib/faraday/utils.rb +245 -28
  38. metadata +5 -22
  39. data/lib/faraday/adapter_registry.rb +0 -28
  40. data/lib/faraday/dependency_loader.rb +0 -37
  41. data/lib/faraday/deprecated_class.rb +0 -28
  42. data/lib/faraday/encoders/flat_params_encoder.rb +0 -94
  43. data/lib/faraday/encoders/nested_params_encoder.rb +0 -171
  44. data/lib/faraday/file_part.rb +0 -128
  45. data/lib/faraday/logging/formatter.rb +0 -92
  46. data/lib/faraday/middleware_registry.rb +0 -129
  47. data/lib/faraday/options/connection_options.rb +0 -22
  48. data/lib/faraday/options/env.rb +0 -181
  49. data/lib/faraday/options/proxy_options.rb +0 -28
  50. data/lib/faraday/options/request_options.rb +0 -21
  51. data/lib/faraday/options/ssl_options.rb +0 -59
  52. data/lib/faraday/param_part.rb +0 -53
  53. data/lib/faraday/utils/headers.rb +0 -139
  54. data/lib/faraday/utils/params_hash.rb +0 -61
  55. data/spec/external_adapters/faraday_specs_setup.rb +0 -14
@@ -1,9 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'forwardable'
4
2
 
5
3
  module Faraday
6
- # Response represents an HTTP response from making an HTTP request.
7
4
  class Response
8
5
  # Used for simple response middleware.
9
6
  class Middleware < Faraday::Middleware
@@ -23,9 +20,9 @@ module Faraday
23
20
  extend Forwardable
24
21
  extend MiddlewareRegistry
25
22
 
26
- register_middleware File.expand_path('response', __dir__),
27
- raise_error: [:RaiseError, 'raise_error'],
28
- logger: [:Logger, 'logger']
23
+ register_middleware File.expand_path('../response', __FILE__),
24
+ :raise_error => [:RaiseError, 'raise_error'],
25
+ :logger => [:Logger, 'logger']
29
26
 
30
27
  def initialize(env = nil)
31
28
  @env = Env.from(env) if env
@@ -34,6 +31,8 @@ module Faraday
34
31
 
35
32
  attr_reader :env
36
33
 
34
+ def_delegators :env, :to_hash
35
+
37
36
  def status
38
37
  finished? ? env.status : nil
39
38
  end
@@ -55,37 +54,32 @@ module Faraday
55
54
  !!env
56
55
  end
57
56
 
58
- def on_complete(&block)
59
- if !finished?
60
- @on_complete_callbacks << block
57
+ def on_complete
58
+ if not finished?
59
+ @on_complete_callbacks << Proc.new
61
60
  else
62
61
  yield(env)
63
62
  end
64
- self
63
+ return self
65
64
  end
66
65
 
67
66
  def finish(env)
68
- raise 'response already finished' if finished?
69
-
67
+ raise "response already finished" if finished?
70
68
  @env = env.is_a?(Env) ? env : Env.from(env)
71
69
  @on_complete_callbacks.each { |callback| callback.call(@env) }
72
- self
70
+ return self
73
71
  end
74
72
 
75
73
  def success?
76
74
  finished? && env.success?
77
75
  end
78
76
 
79
- def to_hash
80
- {
81
- status: env.status, body: env.body,
82
- response_headers: env.response_headers
83
- }
84
- end
85
-
86
77
  # because @on_complete_callbacks cannot be marshalled
87
78
  def marshal_dump
88
- finished? ? to_hash : nil
79
+ !finished? ? nil : {
80
+ :status => @env.status, :body => @env.body,
81
+ :response_headers => @env.response_headers
82
+ }
89
83
  end
90
84
 
91
85
  def marshal_load(env)
@@ -96,9 +90,8 @@ module Faraday
96
90
  # Useful for applying request params after restoring a marshalled Response.
97
91
  def apply_request(request_env)
98
92
  raise "response didn't finish yet" unless finished?
99
-
100
93
  @env = Env.from(request_env).update(@env)
101
- self
94
+ return self
102
95
  end
103
96
  end
104
97
  end
@@ -1,33 +1,80 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'forwardable'
4
- require 'faraday/logging/formatter'
5
2
 
6
3
  module Faraday
7
- class Response
8
- # Logger is a middleware that logs internal events in the HTTP request
9
- # lifecycle to a given Logger object. By default, this logs to STDOUT. See
10
- # Faraday::Logging::Formatter to see specifically what is logged.
11
- class Logger < Middleware
12
- def initialize(app, logger = nil, options = {})
13
- super(app)
14
- logger ||= begin
15
- require 'logger'
16
- ::Logger.new($stdout)
17
- end
18
- formatter_class = options.delete(:formatter) || Logging::Formatter
19
- @formatter = formatter_class.new(logger: logger, options: options)
20
- yield @formatter if block_given?
4
+ class Response::Logger < Response::Middleware
5
+ extend Forwardable
6
+
7
+ DEFAULT_OPTIONS = { :headers => true, :bodies => false }
8
+
9
+ def initialize(app, logger = nil, options = {})
10
+ super(app)
11
+ @logger = logger || begin
12
+ require 'logger'
13
+ ::Logger.new($stdout)
14
+ end
15
+ @filter = []
16
+ @options = DEFAULT_OPTIONS.merge(options)
17
+ yield self if block_given?
18
+ end
19
+
20
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
21
+
22
+ def call(env)
23
+ info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
24
+ debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
+ debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
26
+ super
27
+ end
28
+
29
+ def on_complete(env)
30
+ info('response') { "Status #{env.status.to_s}" }
31
+ debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
+ debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
+ end
34
+
35
+ def filter(filter_word, filter_replacement)
36
+ @filter.push([ filter_word, filter_replacement ])
37
+ end
38
+
39
+ private
40
+
41
+ def dump_headers(headers)
42
+ headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
43
+ end
44
+
45
+ def dump_body(body)
46
+ if body.respond_to?(:to_str)
47
+ body.to_str
48
+ else
49
+ pretty_inspect(body)
50
+ end
51
+ end
52
+
53
+ def pretty_inspect(body)
54
+ require 'pp' unless body.respond_to?(:pretty_inspect)
55
+ body.pretty_inspect
56
+ end
57
+
58
+ def log_headers?(type)
59
+ case @options[:headers]
60
+ when Hash then @options[:headers][type]
61
+ else @options[:headers]
21
62
  end
63
+ end
22
64
 
23
- def call(env)
24
- @formatter.request(env)
25
- super
65
+ def log_body?(type)
66
+ case @options[:bodies]
67
+ when Hash then @options[:bodies][type]
68
+ else @options[:bodies]
26
69
  end
70
+ end
27
71
 
28
- def on_complete(env)
29
- @formatter.response(env)
72
+ def apply_filters(output)
73
+ @filter.each do |pattern, replacement|
74
+ output = output.to_s.gsub(pattern, replacement)
30
75
  end
76
+ output
31
77
  end
78
+
32
79
  end
33
80
  end
@@ -1,43 +1,21 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Faraday
4
- class Response
5
- # RaiseError is a Faraday middleware that raises exceptions on common HTTP
6
- # client or server error responses.
7
- class RaiseError < Middleware
8
- # rubocop:disable Naming/ConstantName
9
- ClientErrorStatuses = (400...500).freeze
10
- ServerErrorStatuses = (500...600).freeze
11
- # rubocop:enable Naming/ConstantName
2
+ class Response::RaiseError < Response::Middleware
3
+ ClientErrorStatuses = 400...600
12
4
 
13
- def on_complete(env)
14
- case env[:status]
15
- when 400
16
- raise Faraday::BadRequestError, response_values(env)
17
- when 401
18
- raise Faraday::UnauthorizedError, response_values(env)
19
- when 403
20
- raise Faraday::ForbiddenError, response_values(env)
21
- when 404
22
- raise Faraday::ResourceNotFound, response_values(env)
23
- when 407
24
- # mimic the behavior that we get with proxy requests with HTTPS
25
- msg = %(407 "Proxy Authentication Required")
26
- raise Faraday::ProxyAuthError.new(msg, response_values(env))
27
- when 409
28
- raise Faraday::ConflictError, response_values(env)
29
- when 422
30
- raise Faraday::UnprocessableEntityError, response_values(env)
31
- when ClientErrorStatuses
32
- raise Faraday::ClientError, response_values(env)
33
- when ServerErrorStatuses
34
- raise Faraday::ServerError, response_values(env)
35
- end
5
+ def on_complete(env)
6
+ case env[:status]
7
+ when 404
8
+ raise Faraday::Error::ResourceNotFound, response_values(env)
9
+ when 407
10
+ # mimic the behavior that we get with proxy requests with HTTPS
11
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
12
+ when ClientErrorStatuses
13
+ raise Faraday::Error::ClientError, response_values(env)
36
14
  end
15
+ end
37
16
 
38
- def response_values(env)
39
- { status: env.status, headers: env.response_headers, body: env.body }
40
- end
17
+ def response_values(env)
18
+ {:status => env.status, :headers => env.response_headers, :body => env.body}
41
19
  end
42
20
  end
43
21
  end
@@ -0,0 +1,67 @@
1
+ begin
2
+ require 'composite_io'
3
+ require 'parts'
4
+ require 'stringio'
5
+ rescue LoadError
6
+ $stderr.puts "Install the multipart-post gem."
7
+ raise
8
+ end
9
+
10
+ module Faraday
11
+ # Similar but not compatible with ::CompositeReadIO provided by multipart-post.
12
+ class CompositeReadIO
13
+ def initialize(*parts)
14
+ @parts = parts.flatten
15
+ @ios = @parts.map { |part| part.to_io }
16
+ @index = 0
17
+ end
18
+
19
+ def length
20
+ @parts.inject(0) { |sum, part| sum + part.length }
21
+ end
22
+
23
+ def rewind
24
+ @ios.each { |io| io.rewind }
25
+ @index = 0
26
+ end
27
+
28
+ # Read from IOs in order until `length` bytes have been received.
29
+ def read(length = nil, outbuf = nil)
30
+ got_result = false
31
+ outbuf = outbuf ? outbuf.replace("") : ""
32
+
33
+ while io = current_io
34
+ if result = io.read(length)
35
+ got_result ||= !result.nil?
36
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
37
+ outbuf << result
38
+ length -= result.length if length
39
+ break if length == 0
40
+ end
41
+ advance_io
42
+ end
43
+ (!got_result && length) ? nil : outbuf
44
+ end
45
+
46
+ def close
47
+ @ios.each { |io| io.close }
48
+ end
49
+
50
+ def ensure_open_and_readable
51
+ # Rubinius compatibility
52
+ end
53
+
54
+ private
55
+
56
+ def current_io
57
+ @ios[@index]
58
+ end
59
+
60
+ def advance_io
61
+ @index += 1
62
+ end
63
+ end
64
+
65
+ UploadIO = ::UploadIO
66
+ Parts = ::Parts
67
+ end
@@ -1,12 +1,193 @@
1
- # frozen_string_literal: true
2
-
3
- require 'faraday/utils/headers'
4
- require 'faraday/utils/params_hash'
1
+ require 'thread'
5
2
 
6
3
  module Faraday
7
- # Utils contains various static helper methods.
8
4
  module Utils
9
- module_function
5
+ extend self
6
+
7
+ # Adapted from Rack::Utils::HeaderHash
8
+ class Headers < ::Hash
9
+ def self.from(value)
10
+ new(value)
11
+ end
12
+
13
+ def self.allocate
14
+ new_self = super
15
+ new_self.initialize_names
16
+ new_self
17
+ end
18
+
19
+ def initialize(hash = nil)
20
+ super()
21
+ @names = {}
22
+ self.update(hash || {})
23
+ end
24
+
25
+ def initialize_names
26
+ @names = {}
27
+ end
28
+
29
+ # on dup/clone, we need to duplicate @names hash
30
+ def initialize_copy(other)
31
+ super
32
+ @names = other.names.dup
33
+ end
34
+
35
+ # need to synchronize concurrent writes to the shared KeyMap
36
+ keymap_mutex = Mutex.new
37
+
38
+ # symbol -> string mapper + cache
39
+ KeyMap = Hash.new do |map, key|
40
+ value = if key.respond_to?(:to_str)
41
+ key
42
+ else
43
+ key.to_s.split('_'). # :user_agent => %w(user agent)
44
+ each { |w| w.capitalize! }. # => %w(User Agent)
45
+ join('-') # => "User-Agent"
46
+ end
47
+ keymap_mutex.synchronize { map[key] = value }
48
+ end
49
+ KeyMap[:etag] = "ETag"
50
+
51
+ def [](k)
52
+ k = KeyMap[k]
53
+ super(k) || super(@names[k.downcase])
54
+ end
55
+
56
+ def []=(k, v)
57
+ k = KeyMap[k]
58
+ k = (@names[k.downcase] ||= k)
59
+ # join multiple values with a comma
60
+ v = v.to_ary.join(', ') if v.respond_to? :to_ary
61
+ super(k, v)
62
+ end
63
+
64
+ def fetch(k, *args, &block)
65
+ k = KeyMap[k]
66
+ key = @names.fetch(k.downcase, k)
67
+ super(key, *args, &block)
68
+ end
69
+
70
+ def delete(k)
71
+ k = KeyMap[k]
72
+ if k = @names[k.downcase]
73
+ @names.delete k.downcase
74
+ super(k)
75
+ end
76
+ end
77
+
78
+ def include?(k)
79
+ @names.include? k.downcase
80
+ end
81
+
82
+ alias_method :has_key?, :include?
83
+ alias_method :member?, :include?
84
+ alias_method :key?, :include?
85
+
86
+ def merge!(other)
87
+ other.each { |k, v| self[k] = v }
88
+ self
89
+ end
90
+ alias_method :update, :merge!
91
+
92
+ def merge(other)
93
+ hash = dup
94
+ hash.merge! other
95
+ end
96
+
97
+ def replace(other)
98
+ clear
99
+ @names.clear
100
+ self.update other
101
+ self
102
+ end
103
+
104
+ def to_hash() ::Hash.new.update(self) end
105
+
106
+ def parse(header_string)
107
+ return unless header_string && !header_string.empty?
108
+
109
+ headers = header_string.split(/\r\n/)
110
+
111
+ # Find the last set of response headers.
112
+ start_index = headers.rindex { |x| x.match(/^HTTP\//) } || 0
113
+ last_response = headers.slice(start_index, headers.size)
114
+
115
+ last_response.
116
+ tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
117
+ map { |h| h.split(/:\s*/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
118
+ each { |key, value|
119
+ # join multiple values with a comma
120
+ if self[key]
121
+ self[key] << ', ' << value
122
+ else
123
+ self[key] = value
124
+ end
125
+ }
126
+ end
127
+
128
+ protected
129
+
130
+ def names
131
+ @names
132
+ end
133
+ end
134
+
135
+ # hash with stringified keys
136
+ class ParamsHash < Hash
137
+ def [](key)
138
+ super(convert_key(key))
139
+ end
140
+
141
+ def []=(key, value)
142
+ super(convert_key(key), value)
143
+ end
144
+
145
+ def delete(key)
146
+ super(convert_key(key))
147
+ end
148
+
149
+ def include?(key)
150
+ super(convert_key(key))
151
+ end
152
+
153
+ alias_method :has_key?, :include?
154
+ alias_method :member?, :include?
155
+ alias_method :key?, :include?
156
+
157
+ def update(params)
158
+ params.each do |key, value|
159
+ self[key] = value
160
+ end
161
+ self
162
+ end
163
+ alias_method :merge!, :update
164
+
165
+ def merge(params)
166
+ dup.update(params)
167
+ end
168
+
169
+ def replace(other)
170
+ clear
171
+ update(other)
172
+ end
173
+
174
+ def merge_query(query, encoder = nil)
175
+ if query && !query.empty?
176
+ update((encoder || Utils.default_params_encoder).decode(query))
177
+ end
178
+ self
179
+ end
180
+
181
+ def to_query(encoder = nil)
182
+ (encoder || Utils.default_params_encoder).encode(self)
183
+ end
184
+
185
+ private
186
+
187
+ def convert_key(key)
188
+ key.to_s
189
+ end
190
+ end
10
191
 
11
192
  def build_query(params)
12
193
  FlatParamsEncoder.encode(params)
@@ -16,19 +197,17 @@ module Faraday
16
197
  NestedParamsEncoder.encode(params)
17
198
  end
18
199
 
19
- ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/.freeze
200
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
20
201
 
21
- def escape(str)
22
- str.to_s.gsub(ESCAPE_RE) do |match|
202
+ def escape(s)
203
+ s.to_s.gsub(ESCAPE_RE) {|match|
23
204
  '%' + match.unpack('H2' * match.bytesize).join('%').upcase
24
- end.tr(' ', '+')
205
+ }.tr(' ', '+')
25
206
  end
26
207
 
27
- def unescape(str)
28
- CGI.unescape str.to_s
29
- end
208
+ def unescape(s) CGI.unescape s.to_s end
30
209
 
31
- DEFAULT_SEP = /[&;] */n.freeze
210
+ DEFAULT_SEP = /[&;] */n
32
211
 
33
212
  # Adapted from Rack
34
213
  def parse_query(query)
@@ -47,18 +226,55 @@ module Faraday
47
226
  attr_writer :default_params_encoder
48
227
  end
49
228
 
229
+ # Stolen from Rack
230
+ def normalize_params(params, name, v = nil)
231
+ name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
232
+ k = $1 || ''
233
+ after = $' || ''
234
+
235
+ return if k.empty?
236
+
237
+ if after == ""
238
+ if params[k]
239
+ params[k] = Array[params[k]] unless params[k].kind_of?(Array)
240
+ params[k] << v
241
+ else
242
+ params[k] = v
243
+ end
244
+ elsif after == "[]"
245
+ params[k] ||= []
246
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
247
+ params[k] << v
248
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
249
+ child_key = $1
250
+ params[k] ||= []
251
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
252
+ if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
253
+ normalize_params(params[k].last, child_key, v)
254
+ else
255
+ params[k] << normalize_params({}, child_key, v)
256
+ end
257
+ else
258
+ params[k] ||= {}
259
+ raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
260
+ params[k] = normalize_params(params[k], after, v)
261
+ end
262
+
263
+ return params
264
+ end
265
+
50
266
  # Normalize URI() behavior across Ruby versions
51
267
  #
52
268
  # url - A String or URI.
53
269
  #
54
270
  # Returns a parsed URI.
55
- def URI(url) # rubocop:disable Naming/MethodName
271
+ def URI(url)
56
272
  if url.respond_to?(:host)
57
273
  url
58
274
  elsif url.respond_to?(:to_str)
59
275
  default_uri_parser.call(url)
60
276
  else
61
- raise ArgumentError, 'bad argument (expected URI object or URI string)'
277
+ raise ArgumentError, "bad argument (expected URI object or URI string)"
62
278
  end
63
279
  end
64
280
 
@@ -71,28 +287,27 @@ module Faraday
71
287
 
72
288
  def default_uri_parser=(parser)
73
289
  @default_uri_parser = if parser.respond_to?(:call) || parser.nil?
74
- parser
75
- else
76
- parser.method(:parse)
77
- end
290
+ parser
291
+ else
292
+ parser.method(:parse)
293
+ end
78
294
  end
79
295
 
80
- # Receives a String or URI and returns just
81
- # the path with the query string sorted.
296
+ # Receives a String or URI and returns just the path with the query string sorted.
82
297
  def normalize_path(url)
83
298
  url = URI(url)
84
299
  (url.path.start_with?('/') ? url.path : '/' + url.path) +
85
- (url.query ? "?#{sort_query_params(url.query)}" : '')
300
+ (url.query ? "?#{sort_query_params(url.query)}" : "")
86
301
  end
87
302
 
88
303
  # Recursive hash update
89
304
  def deep_merge!(target, hash)
90
305
  hash.each do |key, value|
91
- target[key] = if value.is_a?(Hash) && target[key].is_a?(Hash)
92
- deep_merge(target[key], value)
93
- else
94
- value
95
- end
306
+ if Hash === value and Hash === target[key]
307
+ target[key] = deep_merge(target[key], value)
308
+ else
309
+ target[key] = value
310
+ end
96
311
  end
97
312
  target
98
313
  end
@@ -102,6 +317,8 @@ module Faraday
102
317
  deep_merge!(source.dup, hash)
103
318
  end
104
319
 
320
+ protected
321
+
105
322
  def sort_query_params(query)
106
323
  query.split('&').sort.join('&')
107
324
  end