faraday 0.7.6 → 0.8.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,14 @@
1
1
  module Faraday
2
2
  class Middleware
3
- class << self
4
- attr_accessor :load_error, :supports_parallel_requests
5
- alias supports_parallel_requests? supports_parallel_requests
3
+ extend MiddlewareRegistry
6
4
 
7
- # valid parallel managers should respond to #run with no parameters.
8
- # otherwise, return a short wrapper around it.
9
- def setup_parallel_manager(options = {})
10
- nil
11
- end
5
+ class << self
6
+ attr_accessor :load_error
7
+ private :load_error=
12
8
  end
13
9
 
10
+ self.load_error = nil
11
+
14
12
  # Executes a block which should try to require and reference dependent libraries
15
13
  def self.dependency(lib = nil)
16
14
  lib ? require(lib) : yield
@@ -18,8 +16,18 @@ module Faraday
18
16
  self.load_error = error
19
17
  end
20
18
 
19
+ def self.new(*)
20
+ raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
21
+ super
22
+ end
23
+
21
24
  def self.loaded?
22
- !defined? @load_error or @load_error.nil?
25
+ load_error.nil?
26
+ end
27
+
28
+ def self.inherited(subclass)
29
+ super
30
+ subclass.send(:load_error=, self.load_error)
23
31
  end
24
32
 
25
33
  def initialize(app = nil)
@@ -9,20 +9,24 @@ module Faraday
9
9
  # req.body = 'abc'
10
10
  # end
11
11
  #
12
- class Request < Struct.new(:path, :params, :headers, :body, :options)
12
+ class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
13
13
  extend AutoloadHelper
14
+ extend MiddlewareRegistry
14
15
 
15
16
  autoload_all 'faraday/request',
16
- :JSON => 'json',
17
17
  :UrlEncoded => 'url_encoded',
18
- :Multipart => 'multipart'
18
+ :Multipart => 'multipart',
19
+ :Retry => 'retry',
20
+ :Timeout => 'timeout',
21
+ :BasicAuthentication => 'basic_authentication',
22
+ :TokenAuthentication => 'token_authentication'
19
23
 
20
- register_lookup_modules \
21
- :json => :JSON,
24
+ register_middleware \
22
25
  :url_encoded => :UrlEncoded,
23
- :multipart => :Multipart
24
-
25
- attr_reader :method
26
+ :multipart => :Multipart,
27
+ :retry => :Retry,
28
+ :basic_auth => :BasicAuthentication,
29
+ :token_auth => :TokenAuthentication
26
30
 
27
31
  def self.create(request_method)
28
32
  new(request_method).tap do |request|
@@ -30,16 +34,32 @@ module Faraday
30
34
  end
31
35
  end
32
36
 
33
- def initialize(request_method)
34
- @method = request_method
35
- self.params = {}
36
- self.headers = {}
37
- self.options = {}
37
+ # Public: Replace params, preserving the existing hash type
38
+ def params=(hash)
39
+ if params then params.replace hash
40
+ else super
41
+ end
38
42
  end
39
43
 
40
- def url(path, params = {})
41
- self.path = path
42
- self.params = params
44
+ # Public: Replace request headers, preserving the existing hash type
45
+ def headers=(hash)
46
+ if headers then headers.replace hash
47
+ else super
48
+ end
49
+ end
50
+
51
+ def url(path, params = nil)
52
+ if path.respond_to? :query
53
+ if query = path.query
54
+ path = path.dup
55
+ path.query = nil
56
+ end
57
+ else
58
+ path, query = path.split('?', 2)
59
+ end
60
+ self.path = path
61
+ self.params.merge_query query
62
+ self.params.update(params) if params
43
63
  end
44
64
 
45
65
  def [](key)
@@ -53,7 +73,7 @@ module Faraday
53
73
  # ENV Keys
54
74
  # :method - a symbolized request method (:get, :post)
55
75
  # :body - the request body that will eventually be converted to a string.
56
- # :url - Addressable::URI instance of the URI for the current request.
76
+ # :url - URI instance for the current request.
57
77
  # :status - HTTP response status code
58
78
  # :request_headers - hash of HTTP Headers to be sent to the server
59
79
  # :response_headers - Hash of HTTP headers from the server
@@ -67,17 +87,12 @@ module Faraday
67
87
  # :password - Proxy server password
68
88
  # :ssl - Hash of options for configuring SSL requests.
69
89
  def to_env(connection)
70
- env_params = connection.params.merge(params)
71
- env_headers = connection.headers.merge(headers)
72
- request_options = Utils.deep_merge(connection.options, options)
73
- Utils.deep_merge!(request_options, :proxy => connection.proxy)
74
-
75
90
  { :method => method,
76
91
  :body => body,
77
- :url => connection.build_url(path, env_params),
78
- :request_headers => env_headers,
92
+ :url => connection.build_exclusive_url(path, params),
93
+ :request_headers => headers,
79
94
  :parallel_manager => connection.parallel_manager,
80
- :request => request_options,
95
+ :request => options,
81
96
  :ssl => connection.ssl}
82
97
  end
83
98
  end
@@ -0,0 +1,17 @@
1
+ require 'base64'
2
+
3
+ module Faraday
4
+ class Request::BasicAuthentication < Faraday::Middleware
5
+ def initialize(app, login, pass)
6
+ super(app)
7
+ @header_value = "Basic #{Base64.encode64([login, pass].join(':')).gsub("\n", '')}"
8
+ end
9
+
10
+ def call(env)
11
+ unless env[:request_headers]['Authorization']
12
+ env[:request_headers]['Authorization'] = @header_value
13
+ end
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Faraday
2
+ class Request::Retry < Faraday::Middleware
3
+ def initialize(app, retries = 2)
4
+ @retries = retries
5
+ super(app)
6
+ end
7
+
8
+ def call(env)
9
+ retries = @retries
10
+ begin
11
+ @app.call(env)
12
+ rescue StandardError, Timeout::Error
13
+ if retries > 0
14
+ retries -= 1
15
+ retry
16
+ end
17
+ raise
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Faraday
2
+ class Request::TokenAuthentication < Faraday::Middleware
3
+ def initialize(app, token, options={})
4
+ super(app)
5
+
6
+ values = ["token=#{token.to_s.inspect}"]
7
+ options.each do |key, value|
8
+ values << "#{key}=#{value.to_s.inspect}"
9
+ end
10
+ comma = ",\n#{' ' * ('Authorization: Token '.size)}"
11
+ @header_value = "Token #{values * comma}"
12
+ end
13
+
14
+ def call(env)
15
+ unless env[:request_headers]['Authorization']
16
+ env[:request_headers]['Authorization'] = @header_value
17
+ end
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
@@ -21,12 +21,13 @@ module Faraday
21
21
 
22
22
  extend Forwardable
23
23
  extend AutoloadHelper
24
+ extend MiddlewareRegistry
24
25
 
25
26
  autoload_all 'faraday/response',
26
27
  :RaiseError => 'raise_error',
27
28
  :Logger => 'logger'
28
29
 
29
- register_lookup_modules \
30
+ register_middleware \
30
31
  :raise_error => :RaiseError,
31
32
  :logger => :Logger
32
33
 
@@ -1,13 +1,17 @@
1
- require 'rack/utils'
1
+ require 'cgi'
2
2
 
3
3
  module Faraday
4
4
  module Utils
5
- include Rack::Utils
6
-
7
- extend Rack::Utils
8
5
  extend self
9
6
 
10
- class Headers < HeaderHash
7
+ # Adapted from Rack::Utils::HeaderHash
8
+ class Headers < ::Hash
9
+ def initialize(hash={})
10
+ super()
11
+ @names = {}
12
+ self.update hash
13
+ end
14
+
11
15
  # symbol -> string mapper + cache
12
16
  KeyMap = Hash.new do |map, key|
13
17
  map[key] = if key.respond_to?(:to_str) then key
@@ -20,17 +24,53 @@ module Faraday
20
24
  KeyMap[:etag] = "ETag"
21
25
 
22
26
  def [](k)
23
- super(KeyMap[k])
27
+ k = KeyMap[k]
28
+ super(k) || super(@names[k.downcase])
24
29
  end
25
30
 
26
31
  def []=(k, v)
32
+ k = KeyMap[k]
33
+ k = (@names[k.downcase] ||= k)
27
34
  # join multiple values with a comma
28
35
  v = v.to_ary.join(', ') if v.respond_to? :to_ary
29
- super(KeyMap[k], v)
36
+ super k, v
37
+ end
38
+
39
+ def delete(k)
40
+ k = KeyMap[k]
41
+ if k = @names[k.downcase]
42
+ @names.delete k.downcase
43
+ super k
44
+ end
30
45
  end
31
46
 
47
+ def include?(k)
48
+ @names.include? k.downcase
49
+ end
50
+
51
+ alias_method :has_key?, :include?
52
+ alias_method :member?, :include?
53
+ alias_method :key?, :include?
54
+
55
+ def merge!(other)
56
+ other.each { |k, v| self[k] = v }
57
+ self
58
+ end
32
59
  alias_method :update, :merge!
33
60
 
61
+ def merge(other)
62
+ hash = dup
63
+ hash.merge! other
64
+ end
65
+
66
+ def replace(other)
67
+ clear
68
+ self.update other
69
+ self
70
+ end
71
+
72
+ def to_hash() ::Hash.new.update(self) end
73
+
34
74
  def parse(header_string)
35
75
  return unless header_string && !header_string.empty?
36
76
  header_string.split(/\r\n/).
@@ -92,7 +132,7 @@ module Faraday
92
132
  end
93
133
 
94
134
  def to_query
95
- Utils.build_query(self)
135
+ Utils.build_nested_query(self)
96
136
  end
97
137
 
98
138
  private
@@ -102,10 +142,18 @@ module Faraday
102
142
  end
103
143
  end
104
144
 
105
- # Make Rack::Utils methods public.
106
- public :build_query, :parse_query
145
+ # Copied from Rack
146
+ def build_query(params)
147
+ params.map { |k, v|
148
+ if v.class == Array
149
+ build_query(v.map { |x| [k, x] })
150
+ else
151
+ v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
152
+ end
153
+ }.join("&")
154
+ end
107
155
 
108
- # Override Rack's version since it doesn't handle non-String values
156
+ # Rack's version modified to handle non-String values
109
157
  def build_nested_query(value, prefix = nil)
110
158
  case value
111
159
  when Array
@@ -122,14 +170,70 @@ module Faraday
122
170
  end
123
171
  end
124
172
 
125
- # Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
126
- # as spaces.
127
- def escape(s)
128
- s = s.to_s
129
- s = s.dup.force_encoding('binary') if s.respond_to? :force_encoding
130
- s.gsub(/([^a-zA-Z0-9_.-]+)/n) do |match|
131
- '%' << match.unpack('H2'*bytesize(match)).join('%').tap { |c| c.upcase! }
173
+ def escape(s) CGI.escape s.to_s end
174
+
175
+ def unescape(s) CGI.unescape s.to_s end
176
+
177
+ DEFAULT_SEP = /[&;] */n
178
+
179
+ # Adapted from Rack
180
+ def parse_query(qs)
181
+ params = {}
182
+
183
+ (qs || '').split(DEFAULT_SEP).each do |p|
184
+ k, v = p.split('=', 2).map { |x| unescape(x) }
185
+
186
+ if cur = params[k]
187
+ if cur.class == Array then params[k] << v
188
+ else params[k] = [cur, v]
189
+ end
190
+ else
191
+ params[k] = v
192
+ end
193
+ end
194
+ params
195
+ end
196
+
197
+ def parse_nested_query(qs)
198
+ params = {}
199
+
200
+ (qs || '').split(DEFAULT_SEP).each do |p|
201
+ k, v = p.split('=', 2).map { |s| unescape(s) }
202
+ normalize_params(params, k, v)
132
203
  end
204
+ params
205
+ end
206
+
207
+ # Stolen from Rack
208
+ def normalize_params(params, name, v = nil)
209
+ name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
210
+ k = $1 || ''
211
+ after = $' || ''
212
+
213
+ return if k.empty?
214
+
215
+ if after == ""
216
+ params[k] = v
217
+ elsif after == "[]"
218
+ params[k] ||= []
219
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
220
+ params[k] << v
221
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
222
+ child_key = $1
223
+ params[k] ||= []
224
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
225
+ if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
226
+ normalize_params(params[k].last, child_key, v)
227
+ else
228
+ params[k] << normalize_params({}, child_key, v)
229
+ end
230
+ else
231
+ params[k] ||= {}
232
+ raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
233
+ params[k] = normalize_params(params[k], after, v)
234
+ end
235
+
236
+ return params
133
237
  end
134
238
 
135
239
  # Receives a URL and returns just the path with the query string sorted.
@@ -6,10 +6,10 @@ else
6
6
  module Adapters
7
7
  class LiveTest < Faraday::TestCase
8
8
  adapters = if ENV['ADAPTER']
9
- ENV['ADAPTER'].split(':').map { |name| Faraday::Adapter.lookup_module name.to_sym }
9
+ ENV['ADAPTER'].split(':').map { |name| Faraday::Adapter.lookup_middleware name.to_sym }
10
10
  else
11
11
  loaded_adapters = Faraday::Adapter.all_loaded_constants
12
- loaded_adapters -= [Faraday::Adapter::ActionDispatch]
12
+ loaded_adapters -= [Faraday::Adapter::Test, Faraday::Adapter::ActionDispatch]
13
13
  # https://github.com/geemus/excon/issues/98
14
14
  loaded_adapters -= [Faraday::Adapter::Excon] if defined? RUBY_ENGINE and "rbx" == RUBY_ENGINE
15
15
  loaded_adapters << :default
@@ -108,18 +108,16 @@ else
108
108
  end
109
109
 
110
110
  # https://github.com/toland/patron/issues/34
111
- unless %w[Faraday::Adapter::Patron Faraday::Adapter::EMSynchrony].include? adapter.to_s
111
+ unless %w[Faraday::Adapter::Patron].include? adapter.to_s
112
112
  define_method "test_#{adapter}_PATCH_send_url_encoded_params" do
113
113
  resp = create_connection(adapter).patch('echo_name', 'name' => 'zack')
114
114
  assert_equal %("zack"), resp.body
115
115
  end
116
116
  end
117
117
 
118
- unless %[Faraday::Adapter::EMSynchrony] == adapter.to_s
119
- define_method "test_#{adapter}_OPTIONS" do
120
- resp = create_connection(adapter).run_request(:options, '/options', nil, {})
121
- assert_equal "hi", resp.body
122
- end
118
+ define_method "test_#{adapter}_OPTIONS" do
119
+ resp = create_connection(adapter).run_request(:options, '/options', nil, {})
120
+ assert_equal "hi", resp.body
123
121
  end
124
122
 
125
123
  define_method "test_#{adapter}_HEAD_send_url_encoded_params" do
@@ -145,32 +143,38 @@ else
145
143
  assert_match(/deleted/, create_connection(adapter).delete('delete_with_json').body)
146
144
  end
147
145
 
148
- define_method "test_#{adapter}_async_requests_clear_parallel_manager_after_running_a_single_request" do
149
- connection = create_connection(adapter)
150
- assert !connection.in_parallel?
151
- connection.get('hello_world')
152
- assert !connection.in_parallel?
153
- assert_equal 'hello world', connection.get('hello_world').body
154
- end
155
-
156
- define_method "test_#{adapter}_async_requests_uses_parallel_manager_to_run_multiple_json_requests" do
157
- resp1, resp2 = nil, nil
146
+ if :default != adapter and adapter.supports_parallel?
147
+ define_method "test_#{adapter}_in_parallel" do
148
+ resp1, resp2 = nil, nil
158
149
 
159
- connection = create_connection(adapter)
160
- adapter = real_adapter_for(adapter)
150
+ connection = create_connection(adapter)
151
+ klass = real_adapter_for(adapter)
161
152
 
162
- connection.in_parallel(adapter.setup_parallel_manager) do
163
- resp1 = connection.get('json')
164
- resp2 = connection.get('json')
165
- if adapter.supports_parallel_requests?
153
+ connection.in_parallel do
154
+ resp1 = connection.get('echo?a=1')
155
+ resp2 = connection.get('echo?b=2')
166
156
  assert connection.in_parallel?
167
157
  assert_nil resp1.body
168
158
  assert_nil resp2.body
169
159
  end
160
+ assert !connection.in_parallel?
161
+ assert_equal 'get ?{"a"=>"1"}', resp1.body
162
+ assert_equal 'get ?{"b"=>"2"}', resp2.body
163
+ end
164
+ else
165
+ define_method "test_#{adapter}_no_parallel_support" do
166
+ connection = create_connection(adapter)
167
+ response = nil
168
+
169
+ err = capture_warnings do
170
+ connection.in_parallel do
171
+ response = connection.get('echo').body
172
+ end
173
+ end
174
+ assert response
175
+ assert_match "no parallel-capable adapter on Faraday stack", err
176
+ assert_match __FILE__, err
170
177
  end
171
- assert !connection.in_parallel?
172
- assert_equal '[1,2,3]', resp1.body
173
- assert_equal '[1,2,3]', resp2.body
174
178
  end
175
179
 
176
180
  if adapter.to_s == "Faraday::Adapter::EMSynchrony"
@@ -187,9 +191,10 @@ else
187
191
  end
188
192
  end
189
193
 
190
- if %w[Faraday::Adapter::Patron Faraday::Adapter::NetHttp].include?(adapter.to_s)
194
+ # https://github.com/eventmachine/eventmachine/pull/289
195
+ unless %w[Faraday::Adapter::EMHttp Faraday::Adapter::EMSynchrony Faraday::Adapter::Excon].include?(adapter.to_s)
191
196
  define_method "test_#{adapter}_timeout" do
192
- conn = create_connection(adapter, :request => {:timeout => 1, :read_timeout => 1})
197
+ conn = create_connection(adapter, :request => {:timeout => 1, :open_timeout => 1})
193
198
  assert_raise Faraday::Error::TimeoutError do
194
199
  conn.get '/slow'
195
200
  end
@@ -217,7 +222,7 @@ else
217
222
 
218
223
  def real_adapter_for(adapter)
219
224
  if adapter == :default
220
- Faraday::Adapter.lookup_module(Faraday.default_adapter)
225
+ Faraday::Adapter.lookup_middleware(Faraday.default_adapter)
221
226
  else
222
227
  adapter
223
228
  end