faraday 1.0.0.pre.rc1 → 1.0.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +276 -0
  3. data/README.md +4 -4
  4. data/Rakefile +7 -0
  5. data/UPGRADING.md +55 -0
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday.rb +4 -4
  9. data/lib/faraday/adapter.rb +46 -0
  10. data/lib/faraday/adapter/em_http.rb +5 -6
  11. data/lib/faraday/adapter/em_http_ssl_patch.rb +1 -1
  12. data/lib/faraday/adapter/excon.rb +24 -22
  13. data/lib/faraday/adapter/httpclient.rb +40 -39
  14. data/lib/faraday/adapter/net_http.rb +42 -38
  15. data/lib/faraday/adapter/net_http_persistent.rb +3 -1
  16. data/lib/faraday/adapter/patron.rb +42 -24
  17. data/lib/faraday/adapter/rack.rb +2 -1
  18. data/lib/faraday/connection.rb +10 -22
  19. data/lib/faraday/encoders/flat_params_encoder.rb +7 -3
  20. data/lib/faraday/error.rb +44 -12
  21. data/lib/faraday/{upload_io.rb → file_part.rb} +53 -3
  22. data/lib/faraday/logging/formatter.rb +28 -15
  23. data/lib/faraday/middleware.rb +8 -0
  24. data/lib/faraday/options.rb +1 -1
  25. data/lib/faraday/options/env.rb +1 -1
  26. data/lib/faraday/options/request_options.rb +3 -2
  27. data/lib/faraday/param_part.rb +53 -0
  28. data/lib/faraday/request/multipart.rb +9 -1
  29. data/lib/faraday/response.rb +2 -2
  30. data/lib/faraday/response/raise_error.rb +2 -0
  31. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  32. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  33. data/spec/faraday/adapter/excon_spec.rb +49 -0
  34. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  35. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  36. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  37. data/spec/faraday/adapter/patron_spec.rb +18 -0
  38. data/spec/faraday/adapter/rack_spec.rb +8 -0
  39. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  40. data/spec/faraday/adapter_registry_spec.rb +28 -0
  41. data/spec/faraday/adapter_spec.rb +55 -0
  42. data/spec/faraday/composite_read_io_spec.rb +80 -0
  43. data/spec/faraday/connection_spec.rb +691 -0
  44. data/spec/faraday/error_spec.rb +45 -0
  45. data/spec/faraday/middleware_spec.rb +26 -0
  46. data/spec/faraday/options/env_spec.rb +70 -0
  47. data/spec/faraday/options/options_spec.rb +297 -0
  48. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  49. data/spec/faraday/options/request_options_spec.rb +19 -0
  50. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  51. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  52. data/spec/faraday/rack_builder_spec.rb +196 -0
  53. data/spec/faraday/request/authorization_spec.rb +88 -0
  54. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  55. data/spec/faraday/request/multipart_spec.rb +274 -0
  56. data/spec/faraday/request/retry_spec.rb +242 -0
  57. data/spec/faraday/request/url_encoded_spec.rb +70 -0
  58. data/spec/faraday/request_spec.rb +109 -0
  59. data/spec/faraday/response/logger_spec.rb +220 -0
  60. data/spec/faraday/response/middleware_spec.rb +52 -0
  61. data/spec/faraday/response/raise_error_spec.rb +106 -0
  62. data/spec/faraday/response_spec.rb +75 -0
  63. data/spec/faraday/utils/headers_spec.rb +82 -0
  64. data/spec/faraday/utils_spec.rb +56 -0
  65. data/spec/faraday_spec.rb +37 -0
  66. data/spec/spec_helper.rb +132 -0
  67. data/spec/support/disabling_stub.rb +14 -0
  68. data/spec/support/fake_safe_buffer.rb +15 -0
  69. data/spec/support/helper_methods.rb +133 -0
  70. data/spec/support/shared_examples/adapter.rb +104 -0
  71. data/spec/support/shared_examples/params_encoder.rb +18 -0
  72. data/spec/support/shared_examples/request_method.rb +234 -0
  73. data/spec/support/streaming_response_checker.rb +35 -0
  74. data/spec/support/webmock_rack_app.rb +68 -0
  75. metadata +65 -11
@@ -16,7 +16,7 @@ module Faraday
16
16
  if @connection_options.key?(:pool_size)
17
17
  options[:pool_size] = @connection_options[:pool_size]
18
18
  end
19
- Net::HTTP::Persistent.new(options)
19
+ Net::HTTP::Persistent.new(**options)
20
20
  else
21
21
  Net::HTTP::Persistent.new('Faraday')
22
22
  end
@@ -73,6 +73,8 @@ module Faraday
73
73
  }.freeze
74
74
 
75
75
  def configure_ssl(http, ssl)
76
+ return unless ssl
77
+
76
78
  http_set(http, :verify_mode, ssl_verify_mode(ssl))
77
79
  http_set(http, :cert_store, ssl_cert_store(ssl))
78
80
 
@@ -6,11 +6,7 @@ module Faraday
6
6
  class Patron < Faraday::Adapter
7
7
  dependency 'patron'
8
8
 
9
- def call(env)
10
- super
11
- # TODO: support streaming requests
12
- env[:body] = env[:body].read if env[:body].respond_to? :read
13
-
9
+ def build_connection(env)
14
10
  session = ::Patron::Session.new
15
11
  @config_block&.call(session)
16
12
  if (env[:url].scheme == 'https') && env[:ssl]
@@ -18,27 +14,26 @@ module Faraday
18
14
  end
19
15
 
20
16
  if (req = env[:request])
21
- if req[:timeout]
22
- session.timeout = session.connect_timeout = req[:timeout]
23
- end
24
- session.connect_timeout = req[:open_timeout] if req[:open_timeout]
25
-
26
- if (proxy = req[:proxy])
27
- proxy_uri = proxy[:uri].dup
28
- proxy_uri.user = proxy[:user] &&
29
- Utils.escape(proxy[:user]).gsub('+', '%20')
30
- proxy_uri.password = proxy[:password] &&
31
- Utils.escape(proxy[:password]).gsub('+', '%20')
32
- session.proxy = proxy_uri.to_s
33
- end
17
+ configure_timeouts(session, req)
18
+ configure_proxy(session, req[:proxy])
34
19
  end
35
20
 
36
- response = begin
37
- data = env[:body] ? env[:body].to_s : nil
38
- session.request(env[:method], env[:url].to_s,
39
- env[:request_headers], data: data)
40
- rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
41
- raise Faraday::ConnectionFailed, $ERROR_INFO
21
+ session
22
+ end
23
+
24
+ def call(env)
25
+ super
26
+ # TODO: support streaming requests
27
+ env[:body] = env[:body].read if env[:body].respond_to? :read
28
+
29
+ response = connection(env) do |session|
30
+ begin
31
+ data = env[:body] ? env[:body].to_s : nil
32
+ session.request(env[:method], env[:url].to_s,
33
+ env[:request_headers], data: data)
34
+ rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
35
+ raise Faraday::ConnectionFailed, $ERROR_INFO
36
+ end
42
37
  end
43
38
 
44
39
  if (req = env[:request]).stream_response?
@@ -91,6 +86,29 @@ module Faraday
91
86
  end
92
87
  end
93
88
 
89
+ def configure_timeouts(session, req)
90
+ return unless req
91
+
92
+ if (sec = request_timeout(:read, req))
93
+ session.timeout = sec
94
+ end
95
+
96
+ return unless (sec = request_timeout(:open, req))
97
+
98
+ session.connect_timeout = sec
99
+ end
100
+
101
+ def configure_proxy(session, proxy)
102
+ return unless proxy
103
+
104
+ proxy_uri = proxy[:uri].dup
105
+ proxy_uri.user = proxy[:user] &&
106
+ Utils.escape(proxy[:user]).gsub('+', '%20')
107
+ proxy_uri.password = proxy[:password] &&
108
+ Utils.escape(proxy[:password]).gsub('+', '%20')
109
+ session.proxy = proxy_uri.to_s
110
+ end
111
+
94
112
  private
95
113
 
96
114
  CURL_TIMEOUT_MESSAGES = [
@@ -37,7 +37,8 @@ module Faraday
37
37
  rack_env[name] = value
38
38
  end
39
39
 
40
- timeout = env[:request][:timeout] || env[:request][:open_timeout]
40
+ timeout = request_timeout(:open, env[:request])
41
+ timeout ||= request_timeout(:read, env[:request])
41
42
  response = if timeout
42
43
  Timer.timeout(timeout, Faraday::TimeoutError) do
43
44
  execute_request(env, rack_env)
@@ -14,7 +14,7 @@ module Faraday
14
14
  #
15
15
  class Connection
16
16
  # A Set of allowed HTTP verbs.
17
- METHODS = Set.new %i[get post put delete head patch options trace connect]
17
+ METHODS = Set.new %i[get post put delete head patch options trace]
18
18
 
19
19
  # @return [Hash] URI query unencoded key/value pairs.
20
20
  attr_reader :params
@@ -99,7 +99,6 @@ module Faraday
99
99
  else
100
100
  proxy_from_env(url)
101
101
  end
102
- @temp_proxy = @proxy
103
102
  end
104
103
 
105
104
  # Sets the Hash of URI query unencoded key/value pairs.
@@ -118,6 +117,13 @@ module Faraday
118
117
 
119
118
  def_delegators :builder, :build, :use, :request, :response, :adapter, :app
120
119
 
120
+ # Closes the underlying resources and/or connections. In the case of
121
+ # persistent connections, this closes all currently open connections
122
+ # but does not prevent new connections from being made.
123
+ def close
124
+ app.close
125
+ end
126
+
121
127
  # @!method get(url = nil, params = nil, headers = nil)
122
128
  # Makes a GET HTTP request without a body.
123
129
  # @!scope class
@@ -170,21 +176,6 @@ module Faraday
170
176
  # @yield [Faraday::Request] for further request customizations
171
177
  # @return [Faraday::Response]
172
178
 
173
- # @!method connect(url = nil, params = nil, headers = nil)
174
- # Makes a CONNECT HTTP request without a body.
175
- # @!scope class
176
- #
177
- # @param url [String] The optional String base URL to use as a prefix for
178
- # all requests. Can also be the options Hash.
179
- # @param params [Hash] Hash of URI query unencoded key/value pairs.
180
- # @param headers [Hash] unencoded HTTP header key/value pairs.
181
- #
182
- # @example
183
- # conn.connect '/items/1'
184
- #
185
- # @yield [Faraday::Request] for further request customizations
186
- # @return [Faraday::Response]
187
-
188
179
  # @!method trace(url = nil, params = nil, headers = nil)
189
180
  # Makes a TRACE HTTP request without a body.
190
181
  # @!scope class
@@ -490,11 +481,8 @@ module Faraday
490
481
  raise ArgumentError, "unknown http method: #{method}"
491
482
  end
492
483
 
493
- # Resets temp_proxy
494
- @temp_proxy = proxy_for_request(url)
495
-
496
484
  request = build_request(method) do |req|
497
- req.options = req.options.merge(proxy: @temp_proxy)
485
+ req.options.proxy = proxy_for_request(url)
498
486
  req.url(url) if url
499
487
  req.headers.update(headers) if headers
500
488
  req.body = body if body
@@ -514,7 +502,7 @@ module Faraday
514
502
  Request.create(method) do |req|
515
503
  req.params = params.dup
516
504
  req.headers = headers.dup
517
- req.options = options
505
+ req.options = options.dup
518
506
  yield(req) if block_given?
519
507
  end
520
508
  end
@@ -45,9 +45,13 @@ module Faraday
45
45
  if value.nil?
46
46
  buffer << "#{encoded_key}&"
47
47
  elsif value.is_a?(Array)
48
- value.each do |sub_value|
49
- encoded_value = escape(sub_value)
50
- buffer << "#{encoded_key}=#{encoded_value}&"
48
+ if value.empty?
49
+ buffer << "#{encoded_key}=&"
50
+ else
51
+ value.each do |sub_value|
52
+ encoded_value = escape(sub_value)
53
+ buffer << "#{encoded_key}=#{encoded_value}&"
54
+ end
51
55
  end
52
56
  else
53
57
  encoded_value = escape(value)
@@ -1,23 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Faraday namespace.
3
4
  module Faraday
4
5
  # Faraday error base class.
5
6
  class Error < StandardError
6
7
  attr_reader :response, :wrapped_exception
7
8
 
8
9
  def initialize(exc, response = nil)
9
- @wrapped_exception = nil
10
- @response = response
11
-
12
- if exc.respond_to?(:backtrace)
13
- super(exc.message)
14
- @wrapped_exception = exc
15
- elsif exc.respond_to?(:each_key)
16
- super("the server responded with status #{exc[:status]}")
17
- @response = exc
18
- else
19
- super(exc.to_s)
20
- end
10
+ @wrapped_exception = nil unless defined?(@wrapped_exception)
11
+ @response = nil unless defined?(@response)
12
+ super(exc_msg_and_response!(exc, response))
21
13
  end
22
14
 
23
15
  def backtrace
@@ -35,6 +27,38 @@ module Faraday
35
27
  inner << " #{super}" if inner.empty?
36
28
  %(#<#{self.class}#{inner}>)
37
29
  end
30
+
31
+ protected
32
+
33
+ # Pulls out potential parent exception and response hash, storing them in
34
+ # instance variables.
35
+ # exc - Either an Exception, a string message, or a response hash.
36
+ # response - Hash
37
+ # :status - Optional integer HTTP response status
38
+ # :headers - String key/value hash of HTTP response header
39
+ # values.
40
+ # :body - Optional string HTTP response body.
41
+ #
42
+ # If a subclass has to call this, then it should pass a string message
43
+ # to `super`. See NilStatusError.
44
+ def exc_msg_and_response!(exc, response = nil)
45
+ if @response.nil? && @wrapped_exception.nil?
46
+ @wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
47
+ return msg
48
+ end
49
+
50
+ exc.to_s
51
+ end
52
+
53
+ # Pulls out potential parent exception and response hash.
54
+ def exc_msg_and_response(exc, response = nil)
55
+ return [exc, exc.message, response] if exc.respond_to?(:backtrace)
56
+
57
+ return [nil, "the server responded with status #{exc[:status]}", exc] \
58
+ if exc.respond_to?(:each_key)
59
+
60
+ [nil, exc.to_s, response]
61
+ end
38
62
  end
39
63
 
40
64
  # Faraday client error class. Represents 4xx status responses.
@@ -80,6 +104,14 @@ module Faraday
80
104
  end
81
105
  end
82
106
 
107
+ # Raised by Faraday::Response::RaiseError in case of a nil status in response.
108
+ class NilStatusError < ServerError
109
+ def initialize(exc, response = nil)
110
+ exc_msg_and_response!(exc, response)
111
+ super('http status could not be derived from the server response')
112
+ end
113
+ end
114
+
83
115
  # A unified error for failed connections.
84
116
  class ConnectionFailed < Error
85
117
  end
@@ -7,6 +7,59 @@ require 'composite_io'
7
7
  require 'parts'
8
8
 
9
9
  module Faraday
10
+ # Multipart value used to POST a binary data from a file or
11
+ #
12
+ # @example
13
+ # payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") }
14
+ # http.post("/upload", payload)
15
+ #
16
+
17
+ # @!method initialize(filename_or_io, content_type, filename = nil, opts = {})
18
+ #
19
+ # @param filename_or_io [String, IO] Either a String filename to a local
20
+ # file or an open IO object.
21
+ # @param content_type [String] String content type of the file data.
22
+ # @param filename [String] Optional String filename, usually to add context
23
+ # to a given IO object.
24
+ # @param opts [Hash] Optional Hash of String key/value pairs to describethis
25
+ # this uploaded file. Expected Header keys include:
26
+ # * Content-Transfer-Encoding - Defaults to "binary"
27
+ # * Content-Disposition - Defaults to "form-data"
28
+ # * Content-Type - Defaults to the content_type argument.
29
+ # * Content-ID - Optional.
30
+ #
31
+ # @return [Faraday::FilePart]
32
+ #
33
+ # @!attribute [r] content_type
34
+ # The uploaded binary data's content type.
35
+ #
36
+ # @return [String]
37
+ #
38
+ # @!attribute [r] original_filename
39
+ # The base filename, taken either from the filename_or_io or filename
40
+ # arguments in #initialize.
41
+ #
42
+ # @return [String]
43
+ #
44
+ # @!attribute [r] opts
45
+ # Extra String key/value pairs to make up the header for this uploaded file.
46
+ #
47
+ # @return [Hash]
48
+ #
49
+ # @!attribute [r] io
50
+ # The open IO object for the uploaded file.
51
+ #
52
+ # @return [IO]
53
+ FilePart = ::UploadIO
54
+
55
+ # Multipart value used to POST a file.
56
+ #
57
+ # @deprecated Use FilePart instead of this class. It behaves identically, with
58
+ # a matching name to ParamPart.
59
+ UploadIO = ::UploadIO
60
+
61
+ Parts = ::Parts
62
+
10
63
  # Similar to, but not compatible with CompositeReadIO provided by the
11
64
  # multipart-post gem.
12
65
  # https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
@@ -72,7 +125,4 @@ module Faraday
72
125
  @index += 1
73
126
  end
74
127
  end
75
-
76
- UploadIO = ::UploadIO
77
- Parts = ::Parts
78
128
  end
@@ -7,7 +7,8 @@ module Faraday
7
7
  class Formatter
8
8
  extend Forwardable
9
9
 
10
- DEFAULT_OPTIONS = { headers: true, bodies: false }.freeze
10
+ DEFAULT_OPTIONS = { headers: true, bodies: false,
11
+ log_level: :info }.freeze
11
12
 
12
13
  def initialize(logger:, options:)
13
14
  @logger = logger
@@ -18,27 +19,21 @@ module Faraday
18
19
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
19
20
 
20
21
  def request(env)
21
- info('request') do
22
+ request_log = proc do
22
23
  "#{env.method.upcase} #{apply_filters(env.url.to_s)}"
23
24
  end
24
- if log_headers?(:request)
25
- debug('request') { apply_filters(dump_headers(env.request_headers)) }
26
- end
27
- return unless env[:body] && log_body?(:request)
25
+ public_send(log_level, 'request', &request_log)
28
26
 
29
- debug('request') { apply_filters(dump_body(env[:body])) }
27
+ log_headers('request', env.request_headers) if log_headers?(:request)
28
+ log_body('request', env[:body]) if env[:body] && log_body?(:request)
30
29
  end
31
30
 
32
31
  def response(env)
33
- info('response') { "Status #{env.status}" }
34
- if log_headers?(:response)
35
- debug('response') do
36
- apply_filters(dump_headers(env.response_headers))
37
- end
38
- end
39
- return unless env[:body] && log_body?(:response)
32
+ status = proc { "Status #{env.status}" }
33
+ public_send(log_level, 'response', &status)
40
34
 
41
- debug('response') { apply_filters(dump_body(env[:body])) }
35
+ log_headers('response', env.response_headers) if log_headers?(:response)
36
+ log_body('response', env[:body]) if env[:body] && log_body?(:response)
42
37
  end
43
38
 
44
39
  def filter(filter_word, filter_replacement)
@@ -87,6 +82,24 @@ module Faraday
87
82
  end
88
83
  output
89
84
  end
85
+
86
+ def log_level
87
+ unless %i[debug info warn error fatal].include?(@options[:log_level])
88
+ return :info
89
+ end
90
+
91
+ @options[:log_level]
92
+ end
93
+
94
+ def log_headers(type, headers)
95
+ headers_log = proc { apply_filters(dump_headers(headers)) }
96
+ public_send(log_level, type, &headers_log)
97
+ end
98
+
99
+ def log_body(type, body)
100
+ body_log = proc { apply_filters(dump_body(body)) }
101
+ public_send(log_level, type, &body_log)
102
+ end
90
103
  end
91
104
  end
92
105
  end
@@ -9,5 +9,13 @@ module Faraday
9
9
  def initialize(app = nil)
10
10
  @app = app
11
11
  end
12
+
13
+ def close
14
+ if @app.respond_to?(:close)
15
+ @app.close
16
+ else
17
+ warn "#{@app} does not implement \#close!"
18
+ end
19
+ end
12
20
  end
13
21
  end
@@ -79,7 +79,7 @@ module Faraday
79
79
  if !args.empty?
80
80
  send(key_setter, args.first)
81
81
  elsif block_given?
82
- send(key_setter, Proc.new.call(key))
82
+ send(key_setter, yield(key))
83
83
  else
84
84
  raise self.class.fetch_error_class, "key not found: #{key.inspect}"
85
85
  end