faraday 1.0.0.pre.rc1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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