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
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Requires Ruby with test-unit and faraday gems.
4
+ # ruby client_test.rb
5
+
6
+ require 'faraday'
7
+ require 'json'
8
+ require 'test/unit'
9
+
10
+ # Example API client
11
+ class Client
12
+ def initialize(conn)
13
+ @conn = conn
14
+ end
15
+
16
+ def sushi(jname)
17
+ res = @conn.get("/#{jname}")
18
+ data = JSON.parse(res.body)
19
+ data['name']
20
+ end
21
+ end
22
+
23
+ # Example API client test
24
+ class ClientTest < Test::Unit::TestCase
25
+ def test_sushi_name
26
+ stubs = Faraday::Adapter::Test::Stubs.new
27
+ stubs.get('/ebi') do |env|
28
+ # optional: you can inspect the Faraday::Env
29
+ assert_equal '/ebi', env.url.path
30
+ [
31
+ 200,
32
+ { 'Content-Type': 'application/javascript' },
33
+ '{"name": "shrimp"}'
34
+ ]
35
+ end
36
+
37
+ # uncomment to trigger stubs.verify_stubbed_calls failure
38
+ # stubs.get('/unused') { [404, {}, ''] }
39
+
40
+ cli = client(stubs)
41
+ assert_equal 'shrimp', cli.sushi('ebi')
42
+ stubs.verify_stubbed_calls
43
+ end
44
+
45
+ def test_sushi_404
46
+ stubs = Faraday::Adapter::Test::Stubs.new
47
+ stubs.get('/ebi') do
48
+ [
49
+ 404,
50
+ { 'Content-Type': 'application/javascript' },
51
+ '{}'
52
+ ]
53
+ end
54
+
55
+ cli = client(stubs)
56
+ assert_nil cli.sushi('ebi')
57
+ stubs.verify_stubbed_calls
58
+ end
59
+
60
+ def test_sushi_exception
61
+ stubs = Faraday::Adapter::Test::Stubs.new
62
+ stubs.get('/ebi') do
63
+ raise Faraday::ConnectionFailed, nil
64
+ end
65
+
66
+ cli = client(stubs)
67
+ assert_raise Faraday::ConnectionFailed do
68
+ cli.sushi('ebi')
69
+ end
70
+ stubs.verify_stubbed_calls
71
+ end
72
+
73
+ def client(stubs)
74
+ conn = Faraday.new do |builder|
75
+ builder.adapter :test, stubs
76
+ end
77
+ Client.new(conn)
78
+ end
79
+ end
@@ -19,8 +19,8 @@ require 'faraday/dependency_loader'
19
19
  # conn.get '/'
20
20
  #
21
21
  module Faraday
22
- VERSION = '1.0.0-rc1'
23
- METHODS_WITH_QUERY = %w[get head delete connect trace].freeze
22
+ VERSION = '1.0.0'
23
+ METHODS_WITH_QUERY = %w[get head delete trace].freeze
24
24
  METHODS_WITH_BODY = %w[post put patch].freeze
25
25
 
26
26
  class << self
@@ -159,8 +159,8 @@ module Faraday
159
159
  end
160
160
 
161
161
  require_libs 'utils', 'options', 'connection', 'rack_builder', 'parameters',
162
- 'middleware', 'adapter', 'request', 'response', 'upload_io',
163
- 'error'
162
+ 'middleware', 'adapter', 'request', 'response', 'error',
163
+ 'file_part', 'param_part'
164
164
 
165
165
  require_lib 'autoload' unless ENV['FARADAY_NO_AUTOLOAD']
166
166
  end
@@ -46,6 +46,27 @@ module Faraday
46
46
  @config_block = block
47
47
  end
48
48
 
49
+ # Yields or returns an adapter's configured connection. Depends on
50
+ # #build_connection being defined on this adapter.
51
+ #
52
+ # @param env [Faraday::Env, Hash] The env object for a faraday request.
53
+ #
54
+ # @return The return value of the given block, or the HTTP connection object
55
+ # if no block is given.
56
+ def connection(env)
57
+ conn = build_connection(env)
58
+ return conn unless block_given?
59
+
60
+ yield conn
61
+ end
62
+
63
+ # Close any persistent connections. The adapter should still be usable
64
+ # after calling close.
65
+ def close
66
+ # Possible implementation:
67
+ # @app.close if @app.respond_to?(:close)
68
+ end
69
+
49
70
  def call(env)
50
71
  env.clear_body if env.needs_body?
51
72
  env.response = Response.new
@@ -65,5 +86,30 @@ module Faraday
65
86
  env.response.finish(env) unless env.parallel?
66
87
  env.response
67
88
  end
89
+
90
+ # Fetches either a read, write, or open timeout setting. Defaults to the
91
+ # :timeout value if a more specific one is not given.
92
+ #
93
+ # @param type [Symbol] Describes which timeout setting to get: :read,
94
+ # :write, or :open.
95
+ # @param options [Hash] Hash containing Symbol keys like :timeout,
96
+ # :read_timeout, :write_timeout, :open_timeout, or
97
+ # :timeout
98
+ #
99
+ # @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
100
+ # has been set.
101
+ def request_timeout(type, options)
102
+ key = TIMEOUT_KEYS.fetch(type) do
103
+ msg = "Expected :read, :write, :open. Got #{type.inspect} :("
104
+ raise ArgumentError, msg
105
+ end
106
+ options[key] || options[:timeout]
107
+ end
108
+
109
+ TIMEOUT_KEYS = {
110
+ read: :read_timeout,
111
+ open: :open_timeout,
112
+ write: :write_timeout
113
+ }.freeze
68
114
  end
69
115
  end
@@ -70,10 +70,9 @@ module Faraday
70
70
 
71
71
  # Reads out timeout settings from env into options
72
72
  def configure_timeout(options, env)
73
- timeout, open_timeout = request_options(env)
74
- .values_at(:timeout, :open_timeout)
75
- options[:connect_timeout] = options[:inactivity_timeout] = timeout
76
- options[:connect_timeout] = open_timeout if open_timeout
73
+ req = request_options(env)
74
+ options[:inactivity_timeout] = request_timeout(:read, req)
75
+ options[:connect_timeout] = request_timeout(:open, req)
77
76
  end
78
77
 
79
78
  # Reads out compression header settings from env into options
@@ -229,11 +228,11 @@ module Faraday
229
228
  @running
230
229
  end
231
230
 
232
- def add
231
+ def add(&block)
233
232
  if running?
234
233
  perform_request { yield }
235
234
  else
236
- @registered_procs << Proc.new
235
+ @registered_procs << block
237
236
  end
238
237
  @num_registered += 1
239
238
  end
@@ -59,4 +59,4 @@ module EmHttpSslPatch
59
59
  end
60
60
  end
61
61
 
62
- EventMachine::HttpStubConnection.send(:include, EmHttpSslPatch)
62
+ EventMachine::HttpStubConnection.include(EmHttpSslPatch)
@@ -6,22 +6,29 @@ module Faraday
6
6
  class Excon < Faraday::Adapter
7
7
  dependency 'excon'
8
8
 
9
+ def build_connection(env)
10
+ opts = opts_from_env(env)
11
+ ::Excon.new(env[:url].to_s, opts.merge(@connection_options))
12
+ end
13
+
9
14
  def call(env)
10
15
  super
11
16
 
12
- opts = opts_from_env(env)
13
- conn = create_connection(env, opts)
14
-
15
- resp = conn.request(method: env[:method].to_s.upcase,
16
- headers: env[:request_headers],
17
- body: read_body(env))
17
+ req_opts = {
18
+ method: env[:method].to_s.upcase,
19
+ headers: env[:request_headers],
20
+ body: read_body(env)
21
+ }
18
22
 
19
23
  req = env[:request]
20
24
  if req&.stream_response?
21
- warn "Streaming downloads for #{self.class.name} are not yet " \
22
- ' implemented.'
23
- req.on_data.call(resp.body, resp.body.bytesize)
25
+ total = 0
26
+ req_opts[:response_block] = lambda do |chunk, _remain, _total|
27
+ req.on_data.call(chunk, total += chunk.size)
28
+ end
24
29
  end
30
+
31
+ resp = connection(env) { |http| http.request(req_opts) }
25
32
  save_response(env, resp.status.to_i, resp.body, resp.headers,
26
33
  resp.reason_phrase)
27
34
 
@@ -36,11 +43,6 @@ module Faraday
36
43
  raise Faraday::TimeoutError, e
37
44
  end
38
45
 
39
- # @return [Excon]
40
- def create_connection(env, opts)
41
- ::Excon.new(env[:url].to_s, opts.merge(@connection_options))
42
- end
43
-
44
46
  # TODO: support streaming requests
45
47
  def read_body(env)
46
48
  env[:body].respond_to?(:read) ? env[:body].read : env[:body]
@@ -90,17 +92,17 @@ module Faraday
90
92
  end
91
93
 
92
94
  def amend_opts_with_timeouts!(opts, req)
93
- timeout = req[:timeout]
94
- return unless timeout
95
+ if (sec = request_timeout(:read, req))
96
+ opts[:read_timeout] = sec
97
+ end
95
98
 
96
- opts[:read_timeout] = timeout
97
- opts[:connect_timeout] = timeout
98
- opts[:write_timeout] = timeout
99
+ if (sec = request_timeout(:write, req))
100
+ opts[:write_timeout] = sec
101
+ end
99
102
 
100
- open_timeout = req[:open_timeout]
101
- return unless open_timeout
103
+ return unless (sec = request_timeout(:open, req))
102
104
 
103
- opts[:connect_timeout] = open_timeout
105
+ opts[:connect_timeout] = sec
104
106
  end
105
107
 
106
108
  def amend_opts_with_proxy_settings!(opts, req)
@@ -6,51 +6,54 @@ module Faraday
6
6
  class HTTPClient < Faraday::Adapter
7
7
  dependency 'httpclient'
8
8
 
9
- # @return [HTTPClient]
10
- def client
11
- @client ||= ::HTTPClient.new
12
- end
13
-
14
- def call(env)
15
- super
16
-
17
- # enable compression
18
- client.transparent_gzip_decompression = true
9
+ def build_connection(env)
10
+ @client ||= ::HTTPClient.new.tap do |cli|
11
+ # enable compression
12
+ cli.transparent_gzip_decompression = true
13
+ end
19
14
 
20
15
  if (req = env[:request])
21
16
  if (proxy = req[:proxy])
22
- configure_proxy proxy
17
+ configure_proxy @client, proxy
23
18
  end
24
19
 
25
20
  if (bind = req[:bind])
26
- configure_socket bind
21
+ configure_socket @client, bind
27
22
  end
28
23
 
29
- configure_timeouts req
24
+ configure_timeouts @client, req
30
25
  end
31
26
 
32
27
  if env[:url].scheme == 'https' && (ssl = env[:ssl])
33
- configure_ssl ssl
28
+ configure_ssl @client, ssl
34
29
  end
35
30
 
36
- configure_client
31
+ configure_client @client
32
+
33
+ @client
34
+ end
35
+
36
+ def call(env)
37
+ super
37
38
 
38
39
  # TODO: Don't stream yet.
39
40
  # https://github.com/nahi/httpclient/pull/90
40
41
  env[:body] = env[:body].read if env[:body].respond_to? :read
41
42
 
42
- resp = client.request env[:method], env[:url],
43
+ connection(env) do |http|
44
+ resp = http.request env[:method], env[:url],
43
45
  body: env[:body],
44
46
  header: env[:request_headers]
45
47
 
46
- if (req = env[:request]).stream_response?
47
- warn "Streaming downloads for #{self.class.name} " \
48
- 'are not yet implemented.'
49
- req.on_data.call(resp.body, resp.body.bytesize)
50
- end
51
- save_response env, resp.status, resp.body, resp.headers, resp.reason
48
+ if (req = env[:request]).stream_response?
49
+ warn "Streaming downloads for #{self.class.name} " \
50
+ 'are not yet implemented.'
51
+ req.on_data.call(resp.body, resp.body.bytesize)
52
+ end
53
+ save_response env, resp.status, resp.body, resp.headers, resp.reason
52
54
 
53
- @app.call env
55
+ @app.call env
56
+ end
54
57
  rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
55
58
  raise Faraday::TimeoutError, $ERROR_INFO
56
59
  rescue ::HTTPClient::BadResponseError => e
@@ -71,7 +74,7 @@ module Faraday
71
74
  end
72
75
 
73
76
  # @param bind [Hash]
74
- def configure_socket(bind)
77
+ def configure_socket(client, bind)
75
78
  client.socket_local.host = bind[:host]
76
79
  client.socket_local.port = bind[:port]
77
80
  end
@@ -79,7 +82,7 @@ module Faraday
79
82
  # Configure proxy URI and any user credentials.
80
83
  #
81
84
  # @param proxy [Hash]
82
- def configure_proxy(proxy)
85
+ def configure_proxy(client, proxy)
83
86
  client.proxy = proxy[:uri]
84
87
  return unless proxy[:user] && proxy[:password]
85
88
 
@@ -87,7 +90,7 @@ module Faraday
87
90
  end
88
91
 
89
92
  # @param ssl [Hash]
90
- def configure_ssl(ssl)
93
+ def configure_ssl(client, ssl)
91
94
  ssl_config = client.ssl_config
92
95
  ssl_config.verify_mode = ssl_verify_mode(ssl)
93
96
  ssl_config.cert_store = ssl_cert_store(ssl)
@@ -100,23 +103,21 @@ module Faraday
100
103
  end
101
104
 
102
105
  # @param req [Hash]
103
- def configure_timeouts(req)
104
- configure_timeout(req) if req[:timeout]
105
- configure_open_timeout(req) if req[:open_timeout]
106
- end
106
+ def configure_timeouts(client, req)
107
+ if (sec = request_timeout(:open, req))
108
+ client.connect_timeout = sec
109
+ end
107
110
 
108
- def configure_timeout(req)
109
- client.connect_timeout = req[:timeout]
110
- client.receive_timeout = req[:timeout]
111
- client.send_timeout = req[:timeout]
112
- end
111
+ if (sec = request_timeout(:write, req))
112
+ client.send_timeout = sec
113
+ end
114
+
115
+ return unless (sec = request_timeout(:read, req))
113
116
 
114
- def configure_open_timeout(req)
115
- client.connect_timeout = req[:open_timeout]
116
- client.send_timeout = req[:open_timeout]
117
+ client.receive_timeout = sec
117
118
  end
118
119
 
119
- def configure_client
120
+ def configure_client(client)
120
121
  @config_block&.call(client)
121
122
  end
122
123
 
@@ -40,16 +40,32 @@ module Faraday
40
40
  super(app, opts, &block)
41
41
  end
42
42
 
43
- def call(env)
44
- super
45
- with_net_http_connection(env) do |http|
46
- if (env[:url].scheme == 'https') && env[:ssl]
47
- configure_ssl(http, env[:ssl])
43
+ def build_connection(env)
44
+ net_http_connection(env).tap do |http|
45
+ if http.respond_to?(:use_ssl=)
46
+ http.use_ssl = env[:url].scheme == 'https'
48
47
  end
48
+ configure_ssl(http, env[:ssl])
49
49
  configure_request(http, env[:request])
50
+ end
51
+ end
50
52
 
53
+ def net_http_connection(env)
54
+ klass = if (proxy = env[:request][:proxy])
55
+ Net::HTTP::Proxy(proxy[:uri].hostname, proxy[:uri].port,
56
+ proxy[:user], proxy[:password])
57
+ else
58
+ Net::HTTP
59
+ end
60
+ port = env[:url].port || (env[:url].scheme == 'https' ? 443 : 80)
61
+ klass.new(env[:url].hostname, port)
62
+ end
63
+
64
+ def call(env)
65
+ super
66
+ http_response = connection(env) do |http|
51
67
  begin
52
- http_response = perform_request(http, env)
68
+ perform_request(http, env)
53
69
  rescue *NET_HTTP_EXCEPTIONS => e
54
70
  if defined?(OpenSSL) && e.is_a?(OpenSSL::SSL::SSLError)
55
71
  raise Faraday::SSLError, e
@@ -57,13 +73,13 @@ module Faraday
57
73
 
58
74
  raise Faraday::ConnectionFailed, e
59
75
  end
76
+ end
60
77
 
61
- save_response(env, http_response.code.to_i,
62
- http_response.body || '', nil,
63
- http_response.message) do |response_headers|
64
- http_response.each_header do |key, value|
65
- response_headers[key] = value
66
- end
78
+ save_response(env, http_response.code.to_i,
79
+ http_response.body || +'', nil,
80
+ http_response.message) do |response_headers|
81
+ http_response.each_header do |key, value|
82
+ response_headers[key] = value
67
83
  end
68
84
  end
69
85
 
@@ -101,7 +117,7 @@ module Faraday
101
117
  env[:request].on_data.call(chunk, size)
102
118
  end
103
119
  end
104
- env[:request].on_data.call('', 0) unless yielded
120
+ env[:request].on_data.call(+'', 0) unless yielded
105
121
  # Net::HTTP returns something,
106
122
  # but it's not meaningful according to the docs.
107
123
  http_response.body = nil
@@ -134,23 +150,9 @@ module Faraday
134
150
  end
135
151
  end
136
152
 
137
- def with_net_http_connection(env)
138
- yield net_http_connection(env)
139
- end
140
-
141
- def net_http_connection(env)
142
- klass = if (proxy = env[:request][:proxy])
143
- Net::HTTP::Proxy(proxy[:uri].hostname, proxy[:uri].port,
144
- proxy[:user], proxy[:password])
145
- else
146
- Net::HTTP
147
- end
148
- port = env[:url].port || (env[:url].scheme == 'https' ? 443 : 80)
149
- klass.new(env[:url].hostname, port)
150
- end
151
-
152
153
  def configure_ssl(http, ssl)
153
- http.use_ssl = true
154
+ return unless ssl
155
+
154
156
  http.verify_mode = ssl_verify_mode(ssl)
155
157
  http.cert_store = ssl_cert_store(ssl)
156
158
 
@@ -165,17 +167,19 @@ module Faraday
165
167
  end
166
168
 
167
169
  def configure_request(http, req)
168
- if req[:timeout]
169
- http.read_timeout = req[:timeout]
170
- http.open_timeout = req[:timeout]
171
- if http.respond_to?(:write_timeout=)
172
- http.write_timeout = req[:timeout]
173
- end
170
+ if (sec = request_timeout(:read, req))
171
+ http.read_timeout = sec
174
172
  end
175
- http.open_timeout = req[:open_timeout] if req[:open_timeout]
176
- if req[:write_timeout] && http.respond_to?(:write_timeout=)
177
- http.write_timeout = req[:write_timeout]
173
+
174
+ if (sec = http.respond_to?(:write_timeout=) &&
175
+ request_timeout(:write, req))
176
+ http.write_timeout = sec
177
+ end
178
+
179
+ if (sec = request_timeout(:open, req))
180
+ http.open_timeout = sec
178
181
  end
182
+
179
183
  # Only set if Net::Http supports it, since Ruby 2.5.
180
184
  http.max_retries = 0 if http.respond_to?(:max_retries=)
181
185