faraday 0.8.0.rc2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,17 +3,29 @@ module Faraday
3
3
  class Patron < Faraday::Adapter
4
4
  dependency 'patron'
5
5
 
6
+ def initialize(app, &block)
7
+ super(app)
8
+ @block = block if block_given?
9
+ end
10
+
6
11
  def call(env)
7
12
  super
8
13
 
9
14
  # TODO: support streaming requests
10
15
  env[:body] = env[:body].read if env[:body].respond_to? :read
11
16
 
12
- session = ::Patron::Session.new
17
+ session = @session ||= create_session
13
18
 
14
19
  if req = env[:request]
15
20
  session.timeout = session.connect_timeout = req[:timeout] if req[:timeout]
16
21
  session.connect_timeout = req[:open_timeout] if req[:open_timeout]
22
+
23
+ if proxy = req[:proxy]
24
+ session.proxy = proxy[:uri].to_s
25
+ if proxy[:user] && proxy[:password]
26
+ prepend_proxy_auth_string(proxy, session)
27
+ end
28
+ end
17
29
  end
18
30
 
19
31
  response = begin
@@ -38,6 +50,16 @@ module Faraday
38
50
  actions << :options unless actions.include? :options
39
51
  end
40
52
  end
53
+
54
+ def create_session
55
+ session = ::Patron::Session.new
56
+ @block.call(session) if @block
57
+ session
58
+ end
59
+ end
60
+
61
+ def prepend_proxy_auth_string(proxy, session)
62
+ session.proxy.insert(7, "#{proxy[:user]}:#{proxy[:password]}@")
41
63
  end
42
64
  end
43
65
  end
@@ -0,0 +1,59 @@
1
+ require 'timeout'
2
+
3
+ module Faraday
4
+ class Adapter
5
+ # Sends requests to a Rack app.
6
+ #
7
+ # Examples
8
+ #
9
+ # class MyRackApp
10
+ # def call(env)
11
+ # [200, {'Content-Type' => 'text/html'}, ["hello world"]]
12
+ # end
13
+ # end
14
+ #
15
+ # Faraday.new do |conn|
16
+ # conn.adapter :rack, MyRackApp
17
+ # end
18
+ class Rack < Faraday::Adapter
19
+ dependency 'rack/test'
20
+
21
+ # not prefixed with "HTTP_"
22
+ SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ]
23
+
24
+ def initialize(faraday_app, rack_app)
25
+ super(faraday_app)
26
+ mock_session = ::Rack::MockSession.new(rack_app)
27
+ @session = ::Rack::Test::Session.new(mock_session)
28
+ end
29
+
30
+ def call(env)
31
+ super
32
+ rack_env = {
33
+ :method => env[:method],
34
+ :input => env[:body].respond_to?(:read) ? env[:body].read : env[:body]
35
+ }
36
+
37
+ env[:request_headers].each do |name, value|
38
+ name = name.upcase.tr('-', '_')
39
+ name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name
40
+ rack_env[name] = value
41
+ end if env[:request_headers]
42
+
43
+ timeout = env[:request][:timeout] || env[:request][:open_timeout]
44
+ response = if timeout
45
+ Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
46
+ else
47
+ execute_request(env, rack_env)
48
+ end
49
+
50
+ save_response(env, response.status, response.body, response.headers)
51
+ @app.call env
52
+ end
53
+
54
+ def execute_request(env, rack_env)
55
+ @session.request(env[:url].to_s, rack_env)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -63,10 +63,11 @@ module Faraday
63
63
  def configure_ssl(req, env)
64
64
  ssl = env[:ssl]
65
65
 
66
- req.ssl_cert = ssl[:client_cert_file] if ssl[:client_cert_file]
67
- req.ssl_key = ssl[:client_key_file] if ssl[:client_key_file]
68
- req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
69
- req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
66
+ req.ssl_version = ssl[:version] if ssl[:version]
67
+ req.ssl_cert = ssl[:client_cert_file] if ssl[:client_cert_file]
68
+ req.ssl_key = ssl[:client_key_file] if ssl[:client_key_file]
69
+ req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
70
+ req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
70
71
  end
71
72
 
72
73
  def configure_proxy(req, env)
@@ -1,4 +1,3 @@
1
- require 'base64'
2
1
  require 'cgi'
3
2
  require 'set'
4
3
  require 'forwardable'
@@ -11,8 +10,8 @@ module Faraday
11
10
  METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
12
11
  METHODS_WITH_BODIES = Set.new [:post, :put, :patch, :options]
13
12
 
14
- attr_reader :params, :headers, :url_prefix, :builder, :options, :ssl, :parallel_manager
15
- attr_writer :default_parallel_manager
13
+ attr_reader :params, :headers, :url_prefix, :builder, :options, :ssl, :parallel_manager
14
+ attr_writer :default_parallel_manager
16
15
 
17
16
  # :url
18
17
  # :params
@@ -103,11 +102,18 @@ module Faraday
103
102
  end
104
103
 
105
104
  def basic_auth(login, pass)
106
- @builder.insert(0, Faraday::Request::BasicAuthentication, login, pass)
105
+ headers[Faraday::Request::Authorization::KEY] =
106
+ Faraday::Request::BasicAuthentication.header(login, pass)
107
107
  end
108
108
 
109
- def token_auth(token, options = {})
110
- @builder.insert(0, Faraday::Request::TokenAuthentication, token, options)
109
+ def token_auth(token, options = nil)
110
+ headers[Faraday::Request::Authorization::KEY] =
111
+ Faraday::Request::TokenAuthentication.header(token, options)
112
+ end
113
+
114
+ def authorization(type, token)
115
+ headers[Faraday::Request::Authorization::KEY] =
116
+ Faraday::Request::Authorization.header(type, token)
111
117
  end
112
118
 
113
119
  # Internal: Traverse the middleware stack in search of a
@@ -118,9 +124,10 @@ module Faraday
118
124
  # Returns a parallel manager or nil if not found.
119
125
  def default_parallel_manager
120
126
  @default_parallel_manager ||= begin
121
- handler = @builder.handlers.find { |h|
127
+ handler = @builder.handlers.detect do |h|
122
128
  h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
123
- }
129
+ end
130
+
124
131
  if handler then handler.klass.setup_parallel_manager
125
132
  elsif block_given? then yield
126
133
  end
@@ -155,10 +162,14 @@ module Faraday
155
162
  end
156
163
 
157
164
  # normalize URI() behavior across Ruby versions
158
- def self.URI url
159
- url.respond_to?(:host) ? url :
160
- url.respond_to?(:to_str) ? Kernel.URI(url) :
161
- raise(ArgumentError, "bad argument (expected URI object or URI string)")
165
+ def self.URI(url)
166
+ if url.respond_to?(:host)
167
+ url
168
+ elsif url.respond_to?(:to_str)
169
+ Kernel.URI(url)
170
+ else
171
+ raise ArgumentError, "bad argument (expected URI object or URI string)"
172
+ end
162
173
  end
163
174
 
164
175
  def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
@@ -15,18 +15,20 @@ module Faraday
15
15
 
16
16
  autoload_all 'faraday/request',
17
17
  :UrlEncoded => 'url_encoded',
18
- :Multipart => 'multipart',
19
- :Retry => 'retry',
20
- :Timeout => 'timeout',
18
+ :Multipart => 'multipart',
19
+ :Retry => 'retry',
20
+ :Timeout => 'timeout',
21
+ :Authorization => 'authorization',
21
22
  :BasicAuthentication => 'basic_authentication',
22
23
  :TokenAuthentication => 'token_authentication'
23
24
 
24
25
  register_middleware \
25
26
  :url_encoded => :UrlEncoded,
26
- :multipart => :Multipart,
27
- :retry => :Retry,
28
- :basic_auth => :BasicAuthentication,
29
- :token_auth => :TokenAuthentication
27
+ :multipart => :Multipart,
28
+ :retry => :Retry,
29
+ :authorization => :Authorization,
30
+ :basic_auth => :BasicAuthentication,
31
+ :token_auth => :TokenAuthentication
30
32
 
31
33
  def self.create(request_method)
32
34
  new(request_method).tap do |request|
@@ -0,0 +1,40 @@
1
+ module Faraday
2
+ class Request::Authorization < Faraday::Middleware
3
+ KEY = "Authorization".freeze
4
+
5
+ # Public
6
+ def self.header(type, token)
7
+ case token
8
+ when String, Symbol then "#{type} #{token}"
9
+ when Hash then build_hash(type.to_s, token)
10
+ else
11
+ raise ArgumentError, "Can't build an Authorization #{type} header from #{token.inspect}"
12
+ end
13
+ end
14
+
15
+ # Internal
16
+ def self.build_hash(type, hash)
17
+ offset = KEY.size + type.size + 3
18
+ comma = ",\n#{' ' * offset}"
19
+ values = []
20
+ hash.each do |key, value|
21
+ values << "#{key}=#{value.to_s.inspect}"
22
+ end
23
+ "#{type} #{values * comma}"
24
+ end
25
+
26
+ def initialize(app, type, token)
27
+ @header_value = self.class.header(type, token)
28
+ super(app)
29
+ end
30
+
31
+ # Public
32
+ def call(env)
33
+ unless env[:request_headers][KEY]
34
+ env[:request_headers][KEY] = @header_value
35
+ end
36
+ @app.call(env)
37
+ end
38
+ end
39
+ end
40
+
@@ -1,17 +1,13 @@
1
1
  require 'base64'
2
2
 
3
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)
4
+ class Request::BasicAuthentication < Request::Authorization
5
+ # Public
6
+ def self.header(login, pass)
7
+ value = Base64.encode64([login, pass].join(':'))
8
+ value.gsub!("\n", '')
9
+ super(:Basic, value)
15
10
  end
16
11
  end
17
12
  end
13
+
@@ -1,21 +1,15 @@
1
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}"
2
+ class Request::TokenAuthentication < Request::Authorization
3
+ # Public
4
+ def self.header(token, options = nil)
5
+ options ||= {}
6
+ options[:token] = token
7
+ super :Token, options
12
8
  end
13
9
 
14
- def call(env)
15
- unless env[:request_headers]['Authorization']
16
- env[:request_headers]['Authorization'] = @header_value
17
- end
18
- @app.call(env)
10
+ def initialize(app, token, options = nil)
11
+ super(app, token, options)
19
12
  end
20
13
  end
21
14
  end
15
+
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class DefaultTest < Faraday::TestCase
5
+
6
+ def adapter() :default end
7
+
8
+ Integration.apply(self, :NonParallel) do
9
+ # default stack is not configured with Multipart
10
+ undef :test_POST_sends_files
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class EMHttpTest < Faraday::TestCase
5
+
6
+ def adapter() :em_http end
7
+
8
+ Integration.apply(self, :Parallel) do
9
+ # https://github.com/eventmachine/eventmachine/pull/289
10
+ undef :test_timeout
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class EMSynchronyTest < Faraday::TestCase
5
+
6
+ def adapter() :em_synchrony end
7
+
8
+ Integration.apply(self, :Parallel) do
9
+ # https://github.com/eventmachine/eventmachine/pull/289
10
+ undef :test_timeout
11
+ end
12
+
13
+ end unless RUBY_VERSION < '1.9' or (defined? RUBY_ENGINE and 'jruby' == RUBY_ENGINE)
14
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class ExconTest < Faraday::TestCase
5
+
6
+ def adapter() :excon end
7
+
8
+ # https://github.com/geemus/excon/issues/98
9
+ if defined?(RUBY_ENGINE) and "rbx" == RUBY_ENGINE
10
+ warn "Warning: Skipping Excon tests on Rubinius"
11
+ else
12
+ Integration.apply(self, :NonParallel) do
13
+ # https://github.com/eventmachine/eventmachine/pull/289
14
+ undef :test_timeout
15
+
16
+ # FIXME: this test fails on Travis with
17
+ # "Faraday::Error::ClientError: the server responded with status 400"
18
+ undef :test_POST_sends_files if ENV['CI']
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,190 @@
1
+ require 'forwardable'
2
+ require File.expand_path("../../helper", __FILE__)
3
+
4
+ module Adapters
5
+ # Adapter integration tests. To use, implement two methods:
6
+ #
7
+ # `#adapter` required. returns a symbol for the adapter middleware name
8
+ # `#adapter_options` optional. extra arguments for building an adapter
9
+ module Integration
10
+ def self.apply(base, *extras)
11
+ if Faraday::TestCase::LIVE_SERVER
12
+ ([:Common] + extras).each {|name| base.send(:include, self.const_get(name)) }
13
+ yield if block_given?
14
+ elsif !defined? @warned
15
+ warn "Warning: Not running integration tests against a live server."
16
+ warn "Start the server `ruby test/live_server.rb` and set the LIVE=1 env variable."
17
+ @warned = true
18
+ end
19
+ end
20
+
21
+ module Parallel
22
+ def test_in_parallel
23
+ resp1, resp2 = nil, nil
24
+
25
+ connection = create_connection
26
+ connection.in_parallel do
27
+ resp1 = connection.get('echo?a=1')
28
+ resp2 = connection.get('echo?b=2')
29
+ assert connection.in_parallel?
30
+ assert_nil resp1.body
31
+ assert_nil resp2.body
32
+ end
33
+ assert !connection.in_parallel?
34
+ assert_equal 'get ?{"a"=>"1"}', resp1.body
35
+ assert_equal 'get ?{"b"=>"2"}', resp2.body
36
+ end
37
+ end
38
+
39
+ module NonParallel
40
+ def test_no_parallel_support
41
+ connection = create_connection
42
+ response = nil
43
+
44
+ err = capture_warnings do
45
+ connection.in_parallel do
46
+ response = connection.get('echo').body
47
+ end
48
+ end
49
+ assert response
50
+ assert_match "no parallel-capable adapter on Faraday stack", err
51
+ assert_match __FILE__, err
52
+ end
53
+ end
54
+
55
+ module Compression
56
+ def test_GET_handles_compression
57
+ res = get('echo_header', :name => 'accept-encoding')
58
+ assert_match /gzip;.+\bdeflate\b/, res.body
59
+ end
60
+ end
61
+
62
+ module Common
63
+ extend Forwardable
64
+ def_delegators :create_connection, :get, :head, :put, :post, :patch, :delete, :run_request
65
+
66
+ def test_GET_retrieves_the_response_body
67
+ assert_equal 'get', get('echo').body
68
+ end
69
+
70
+ def test_GET_send_url_encoded_params
71
+ assert_equal %(get ?{"name"=>"zack"}), get('echo', :name => 'zack').body
72
+ end
73
+
74
+ def test_GET_retrieves_the_response_headers
75
+ response = get('echo')
76
+ assert_match(/text\/plain/, response.headers['Content-Type'], 'original case fail')
77
+ assert_match(/text\/plain/, response.headers['content-type'], 'lowercase fail')
78
+ end
79
+
80
+ def test_GET_handles_headers_with_multiple_values
81
+ assert_equal 'one, two', get('multi').headers['set-cookie']
82
+ end
83
+
84
+ def test_GET_with_body
85
+ response = get('echo') do |req|
86
+ req.body = {'bodyrock' => true}
87
+ end
88
+ assert_equal %(get {"bodyrock"=>"true"}), response.body
89
+ end
90
+
91
+ def test_GET_sends_user_agent
92
+ response = get('echo_header', {:name => 'user-agent'}, :user_agent => 'Agent Faraday')
93
+ assert_equal 'Agent Faraday', response.body
94
+ end
95
+
96
+ def test_POST_send_url_encoded_params
97
+ assert_equal %(post {"name"=>"zack"}), post('echo', :name => 'zack').body
98
+ end
99
+
100
+ def test_POST_send_url_encoded_nested_params
101
+ resp = post('echo', 'name' => {'first' => 'zack'})
102
+ assert_equal %(post {"name"=>{"first"=>"zack"}}), resp.body
103
+ end
104
+
105
+ def test_POST_retrieves_the_response_headers
106
+ assert_match(/text\/plain/, post('echo').headers['content-type'])
107
+ end
108
+
109
+ def test_POST_sends_files
110
+ resp = post('file') do |req|
111
+ req.body = {'uploaded_file' => Faraday::UploadIO.new(__FILE__, 'text/x-ruby')}
112
+ end
113
+ assert_equal "file integration.rb text/x-ruby", resp.body
114
+ end
115
+
116
+ def test_PUT_send_url_encoded_params
117
+ assert_equal %(put {"name"=>"zack"}), put('echo', :name => 'zack').body
118
+ end
119
+
120
+ def test_PUT_send_url_encoded_nested_params
121
+ resp = put('echo', 'name' => {'first' => 'zack'})
122
+ assert_equal %(put {"name"=>{"first"=>"zack"}}), resp.body
123
+ end
124
+
125
+ def test_PUT_retrieves_the_response_headers
126
+ assert_match(/text\/plain/, put('echo').headers['content-type'])
127
+ end
128
+
129
+ def test_PATCH_send_url_encoded_params
130
+ assert_equal %(patch {"name"=>"zack"}), patch('echo', :name => 'zack').body
131
+ end
132
+
133
+ def test_OPTIONS
134
+ resp = run_request(:options, 'echo', nil, {})
135
+ assert_equal 'options', resp.body
136
+ end
137
+
138
+ def test_HEAD_retrieves_no_response_body
139
+ # FIXME: some adapters return empty string, some nil
140
+ assert_equal '', head('echo').body.to_s
141
+ end
142
+
143
+ def test_HEAD_retrieves_the_response_headers
144
+ assert_match(/text\/plain/, head('echo').headers['content-type'])
145
+ end
146
+
147
+ def test_DELETE_retrieves_the_response_headers
148
+ assert_match(/text\/plain/, delete('echo').headers['content-type'])
149
+ end
150
+
151
+ def test_DELETE_retrieves_the_body
152
+ assert_equal %(delete), delete('echo').body
153
+ end
154
+
155
+ def test_timeout
156
+ conn = create_connection(:request => {:timeout => 1, :open_timeout => 1})
157
+ assert_raise Faraday::Error::TimeoutError do
158
+ conn.get '/slow'
159
+ end
160
+ end
161
+
162
+ def adapter
163
+ raise NotImplementedError.new("Need to override #adapter")
164
+ end
165
+
166
+ # extra options to pass when building the adapter
167
+ def adapter_options
168
+ []
169
+ end
170
+
171
+ def create_connection(options = {})
172
+ if adapter == :default
173
+ builder_block = nil
174
+ else
175
+ builder_block = Proc.new do |b|
176
+ b.request :multipart
177
+ b.request :url_encoded
178
+ b.adapter adapter, *adapter_options
179
+ end
180
+ end
181
+
182
+ Faraday::Connection.new(Faraday::TestCase::LIVE_SERVER, options, &builder_block).tap do |conn|
183
+ conn.headers['X-Faraday-Adapter'] = adapter.to_s
184
+ adapter_handler = conn.builder.handlers.last
185
+ conn.builder.insert_before adapter_handler, Faraday::Response::RaiseError
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end