faraday 0.7.6 → 0.8.0.rc2

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.
@@ -1,11 +1,21 @@
1
+ require 'uri'
2
+
1
3
  module Faraday
2
4
  class Adapter
3
5
  class EMSynchrony < Faraday::Adapter
6
+
4
7
  dependency do
5
8
  require 'em-synchrony/em-http'
9
+ require 'em-synchrony/em-multi'
6
10
  require 'fiber'
7
11
  end
8
12
 
13
+ self.supports_parallel = true
14
+
15
+ def self.setup_parallel_manager(options = {})
16
+ ParallelManager.new
17
+ end
18
+
9
19
  def call(env)
10
20
  super
11
21
  request = EventMachine::HttpRequest.new(URI::parse(env[:url].to_s))
@@ -22,10 +32,10 @@ module Faraday
22
32
 
23
33
  if req = env[:request]
24
34
  if proxy = req[:proxy]
25
- uri = Addressable::URI.parse(proxy[:uri])
35
+ uri = URI.parse(proxy[:uri])
26
36
  options[:proxy] = {
27
37
  :host => uri.host,
28
- :port => uri.inferred_port
38
+ :port => uri.port
29
39
  }
30
40
  if proxy[:username] && proxy[:password]
31
41
  options[:proxy][:authorization] = [proxy[:username], proxy[:password]]
@@ -38,22 +48,41 @@ module Faraday
38
48
  end
39
49
  end
40
50
 
41
- client = nil
42
- block = lambda { request.send env[:method].to_s.downcase.to_sym, options }
43
- if !EM.reactor_running?
44
- EM.run {
45
- Fiber.new do
46
- client = block.call
47
- EM.stop
48
- end.resume
49
- }
51
+ http_method = env[:method].to_s.downcase.to_sym
52
+
53
+ # Queue requests for parallel execution.
54
+ if env[:parallel_manager]
55
+ env[:parallel_manager].add(request, http_method, options) do |resp|
56
+ save_response(env, resp.response_header.status, resp.response) do |resp_headers|
57
+ resp.response_header.each do |name, value|
58
+ resp_headers[name.to_sym] = value
59
+ end
60
+ end
61
+
62
+ # Finalize the response object with values from `env`.
63
+ env[:response].finish(env)
64
+ end
65
+
66
+ # Execute single request.
50
67
  else
51
- client = block.call
52
- end
68
+ client = nil
69
+ block = lambda { request.send(http_method, options) }
53
70
 
54
- save_response(env, client.response_header.status, client.response) do |response_headers|
55
- client.response_header.each do |name, value|
56
- response_headers[name.to_sym] = value
71
+ if !EM.reactor_running?
72
+ EM.run do
73
+ Fiber.new {
74
+ client = block.call
75
+ EM.stop
76
+ }.resume
77
+ end
78
+ else
79
+ client = block.call
80
+ end
81
+
82
+ save_response(env, client.response_header.status, client.response) do |resp_headers|
83
+ client.response_header.each do |name, value|
84
+ resp_headers[name.to_sym] = value
85
+ end
57
86
  end
58
87
  end
59
88
 
@@ -64,3 +93,24 @@ module Faraday
64
93
  end
65
94
  end
66
95
  end
96
+
97
+ require 'faraday/adapter/em_synchrony/parallel_manager'
98
+
99
+ # add missing patch(), options() methods
100
+ EventMachine::HTTPMethods.module_eval do
101
+ ([:patch, :options] - instance_methods).each do |type|
102
+ module_eval %[
103
+ def #{type}(options = {}, &blk)
104
+ f = Fiber.current
105
+ conn = setup_request(:#{type}, options, &blk)
106
+ if conn.error.nil?
107
+ conn.callback { f.resume(conn) }
108
+ conn.errback { f.resume(conn) }
109
+ Fiber.yield
110
+ else
111
+ conn
112
+ end
113
+ end
114
+ ]
115
+ end
116
+ end
@@ -0,0 +1,66 @@
1
+ module Faraday
2
+ class Adapter
3
+ class EMSynchrony < Faraday::Adapter
4
+ class ParallelManager
5
+
6
+ # Add requests to queue. The `request` argument should be a
7
+ # `EM::HttpRequest` object.
8
+ def add(request, method, *args, &block)
9
+ queue << {
10
+ :request => request,
11
+ :method => method,
12
+ :args => args,
13
+ :block => block
14
+ }
15
+ end
16
+
17
+ # Run all requests on queue with `EM::Synchrony::Multi`, wrapping
18
+ # it in a reactor and fiber if needed.
19
+ def run
20
+ result = nil
21
+ if !EM.reactor_running?
22
+ EM.run {
23
+ Fiber.new do
24
+ result = perform
25
+ EM.stop
26
+ end.resume
27
+ }
28
+ else
29
+ result = perform
30
+ end
31
+ result
32
+ end
33
+
34
+
35
+ private
36
+
37
+ # The request queue.
38
+ def queue
39
+ @queue ||= []
40
+ end
41
+
42
+ # Main `EM::Synchrony::Multi` performer.
43
+ def perform
44
+ multi = ::EM::Synchrony::Multi.new
45
+
46
+ queue.each do |item|
47
+ method = "a#{item[:method]}".to_sym
48
+
49
+ req = item[:request].send(method, *item[:args])
50
+ req.callback(&item[:block])
51
+
52
+ req_name = "req_#{multi.requests.size}".to_sym
53
+ multi.add(req_name, req)
54
+ end
55
+
56
+ # Clear the queue, so parallel manager objects can be reused.
57
+ @queue = []
58
+
59
+ # Block fiber until all requests have returned.
60
+ multi.perform
61
+ end
62
+
63
+ end # ParallelManager
64
+ end # EMSynchrony
65
+ end # Adapter
66
+ end # Faraday
@@ -13,7 +13,7 @@ module Faraday
13
13
  url = env[:url]
14
14
  req = env[:request]
15
15
 
16
- http = net_http_class(env).new(url.host, url.inferred_port)
16
+ http = net_http_class(env).new(url.host, url.port)
17
17
 
18
18
  if http.use_ssl = (url.scheme == 'https' && (ssl = env[:ssl]) && true)
19
19
  http.verify_mode = ssl[:verify_mode] || begin
@@ -14,8 +14,6 @@ module Faraday
14
14
  class Test < Faraday::Adapter
15
15
  attr_accessor :stubs
16
16
 
17
- def self.loaded?() false end
18
-
19
17
  class Stubs
20
18
  class NotFound < StandardError
21
19
  end
@@ -34,6 +32,7 @@ module Faraday
34
32
  return false if !@stack.key?(request_method)
35
33
  stack = @stack[request_method]
36
34
  consumed = (@consumed[request_method] ||= [])
35
+ path = normalize_path(path)
37
36
 
38
37
  if stub = matches?(stack, path, body)
39
38
  consumed << stack.delete(stub)
@@ -87,19 +86,27 @@ module Faraday
87
86
  protected
88
87
 
89
88
  def new_stub(request_method, path, body=nil, &block)
90
- (@stack[request_method] ||= []) << Stub.new(path, body, block)
89
+ (@stack[request_method] ||= []) << Stub.new(normalize_path(path), body, block)
91
90
  end
92
91
 
93
92
  def matches?(stack, path, body)
94
93
  stack.detect { |stub| stub.matches?(path, body) }
95
94
  end
95
+
96
+ # ensure leading + trailing slash
97
+ def normalize_path(path)
98
+ path = '/' + path if path.index('/') != 0
99
+ path = path.sub('?', '/?')
100
+ path = path + '/' unless $&
101
+ path.gsub('//', '/')
102
+ end
96
103
  end
97
104
 
98
105
  class Stub < Struct.new(:path, :params, :body, :block)
99
106
  def initialize(full, body, block)
100
107
  path, query = full.split('?')
101
108
  params = query ?
102
- Rack::Utils.parse_nested_query(query) :
109
+ Faraday::Utils.parse_nested_query(query) :
103
110
  {}
104
111
  super path, params, body, block
105
112
  end
@@ -107,7 +114,7 @@ module Faraday
107
114
  def matches?(request_uri, request_body)
108
115
  request_path, request_query = request_uri.split('?')
109
116
  request_params = request_query ?
110
- Rack::Utils.parse_nested_query(request_query) :
117
+ Faraday::Utils.parse_nested_query(request_query) :
111
118
  {}
112
119
  request_path == path &&
113
120
  params_match?(request_params) &&
@@ -141,7 +148,7 @@ module Faraday
141
148
 
142
149
  if stub = stubs.match(env[:method], normalized_path, env[:body])
143
150
  env[:params] = (query = env[:url].query) ?
144
- Rack::Utils.parse_nested_query(query) :
151
+ Faraday::Utils.parse_nested_query(query) :
145
152
  {}
146
153
  status, headers, body = stub.block.call(env)
147
154
  save_response(env, status, body, headers)
@@ -1,7 +1,7 @@
1
1
  module Faraday
2
2
  class Adapter
3
3
  class Typhoeus < Faraday::Adapter
4
- self.supports_parallel_requests = true
4
+ self.supports_parallel = true
5
5
 
6
6
  def self.setup_parallel_manager(options = {})
7
7
  options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
@@ -11,53 +11,88 @@ module Faraday
11
11
 
12
12
  def call(env)
13
13
  super
14
+ perform_request env
15
+ @app.call env
16
+ end
17
+
18
+ def perform_request(env)
19
+ read_body env
20
+
21
+ hydra = env[:parallel_manager] || self.class.setup_parallel_manager
22
+ hydra.queue request(env)
23
+ hydra.run unless parallel?(env)
24
+ rescue Errno::ECONNREFUSED
25
+ raise Error::ConnectionFailed, $!
26
+ end
14
27
 
15
- # TODO: support streaming requests
28
+ # TODO: support streaming requests
29
+ def read_body(env)
16
30
  env[:body] = env[:body].read if env[:body].respond_to? :read
31
+ end
17
32
 
33
+ def request(env)
18
34
  req = ::Typhoeus::Request.new env[:url].to_s,
19
35
  :method => env[:method],
20
36
  :body => env[:body],
21
37
  :headers => env[:request_headers],
22
38
  :disable_ssl_peer_verification => (env[:ssl] && !env[:ssl].fetch(:verify, true))
23
39
 
24
- if ssl = env[:ssl]
25
- req.ssl_cert = ssl[:client_cert_file] if ssl[:client_cert_file]
26
- req.ssl_key = ssl[:client_key_file] if ssl[:client_key_file]
27
- req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
28
- req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
29
- end
40
+ configure_ssl req, env
41
+ configure_proxy req, env
42
+ configure_timeout req, env
30
43
 
31
- env_req = env[:request]
32
-
33
- if proxy = env_req[:proxy]
34
- req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}"
35
-
36
- if proxy[:username] && proxy[:password]
37
- req.proxy_username = proxy[:username]
38
- req.proxy_password = proxy[:password]
44
+ req.on_complete do |resp|
45
+ if resp.timed_out?
46
+ if parallel?(env)
47
+ # TODO: error callback in async mode
48
+ else
49
+ raise Faraday::Error::TimeoutError, "request timed out"
50
+ end
39
51
  end
40
- end
41
52
 
42
- req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
43
- req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
44
-
45
- is_parallel = !!env[:parallel_manager]
46
- req.on_complete do |resp|
47
53
  save_response(env, resp.code, resp.body) do |response_headers|
48
54
  response_headers.parse resp.headers
49
55
  end
50
56
  # in async mode, :response is initialized at this point
51
- env[:response].finish(env) if is_parallel
57
+ env[:response].finish(env) if parallel?(env)
52
58
  end
53
59
 
54
- hydra = env[:parallel_manager] || self.class.setup_parallel_manager
55
- hydra.queue req
56
- hydra.run unless is_parallel
60
+ req
61
+ end
57
62
 
58
- @app.call env
59
- rescue Errno::ECONNREFUSED
60
- raise Error::ConnectionFailed, $!
63
+ def configure_ssl(req, env)
64
+ ssl = env[:ssl]
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]
70
+ end
71
+
72
+ def configure_proxy(req, env)
73
+ proxy = request_options(env)[:proxy]
74
+ return unless proxy
75
+
76
+ req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}"
77
+
78
+ if proxy[:username] && proxy[:password]
79
+ req.proxy_username = proxy[:username]
80
+ req.proxy_password = proxy[:password]
81
+ end
82
+ end
83
+
84
+ def configure_timeout(req, env)
85
+ env_req = request_options(env)
86
+ req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
87
+ req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
88
+ end
89
+
90
+ def request_options(env)
91
+ env[:request]
92
+ end
93
+
94
+ def parallel?(env)
95
+ !!env[:parallel_manager]
61
96
  end
62
97
  end
63
98
  end
@@ -8,10 +8,6 @@ module Faraday
8
8
  class Builder
9
9
  attr_accessor :handlers
10
10
 
11
- def self.create
12
- new { |builder| yield builder }
13
- end
14
-
15
11
  # Error raised when trying to modify the stack after calling `lock!`
16
12
  class StackLocked < RuntimeError; end
17
13
 
@@ -91,24 +87,24 @@ module Faraday
91
87
  @handlers.frozen?
92
88
  end
93
89
 
94
- def use(klass, *args)
95
- raise_if_locked
96
- block = block_given? ? Proc.new : nil
97
- @handlers << self.class::Handler.new(klass, *args, &block)
90
+ def use(klass, *args, &block)
91
+ if klass.is_a? Symbol
92
+ use_symbol(Faraday::Middleware, klass, *args, &block)
93
+ else
94
+ raise_if_locked
95
+ @handlers << self.class::Handler.new(klass, *args, &block)
96
+ end
98
97
  end
99
98
 
100
- def request(key, *args)
101
- block = block_given? ? Proc.new : nil
99
+ def request(key, *args, &block)
102
100
  use_symbol(Faraday::Request, key, *args, &block)
103
101
  end
104
102
 
105
- def response(key, *args)
106
- block = block_given? ? Proc.new : nil
103
+ def response(key, *args, &block)
107
104
  use_symbol(Faraday::Response, key, *args, &block)
108
105
  end
109
106
 
110
- def adapter(key, *args)
111
- block = block_given? ? Proc.new : nil
107
+ def adapter(key, *args, &block)
112
108
  use_symbol(Faraday::Adapter, key, *args, &block)
113
109
  end
114
110
 
@@ -146,9 +142,8 @@ module Faraday
146
142
  raise StackLocked, "can't modify middleware stack after making a request" if locked?
147
143
  end
148
144
 
149
- def use_symbol(mod, key, *args)
150
- block = block_given? ? Proc.new : nil
151
- use(mod.lookup_module(key), *args, &block)
145
+ def use_symbol(mod, key, *args, &block)
146
+ use(mod.lookup_middleware(key), *args, &block)
152
147
  end
153
148
 
154
149
  def assert_index(index)
@@ -1,21 +1,18 @@
1
- require 'addressable/uri'
2
1
  require 'base64'
3
2
  require 'cgi'
4
3
  require 'set'
5
- require 'faraday/builder'
6
- require 'faraday/request'
7
- require 'faraday/response'
8
- require 'faraday/utils'
4
+ require 'forwardable'
5
+ require 'uri'
6
+
7
+ Faraday.require_libs 'builder', 'request', 'response', 'utils'
9
8
 
10
9
  module Faraday
11
10
  class Connection
12
- include Addressable
13
-
14
11
  METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
15
12
  METHODS_WITH_BODIES = Set.new [:post, :put, :patch, :options]
16
13
 
17
- attr_accessor :host, :port, :scheme, :params, :headers, :parallel_manager
18
- attr_reader :path_prefix, :builder, :options, :ssl
14
+ attr_reader :params, :headers, :url_prefix, :builder, :options, :ssl, :parallel_manager
15
+ attr_writer :default_parallel_manager
19
16
 
20
17
  # :url
21
18
  # :params
@@ -27,47 +24,43 @@ module Faraday
27
24
  options = url
28
25
  url = options[:url]
29
26
  end
30
- @headers = Utils::Headers.new
31
- @params = Utils::ParamsHash.new
32
- @options = options[:request] || {}
33
- @ssl = options[:ssl] || {}
34
- @parallel_manager = options[:parallel]
27
+ @headers = Utils::Headers.new
28
+ @params = Utils::ParamsHash.new
29
+ @options = options[:request] || {}
30
+ @ssl = options[:ssl] || {}
31
+
32
+ @parallel_manager = nil
33
+ @default_parallel_manager = options[:parallel_manager]
35
34
 
36
- @path_prefix = @host = @port = @scheme = nil
37
- self.url_prefix = url if url
35
+ @builder = options[:builder] || begin
36
+ # pass an empty block to Builder so it doesn't assume default middleware
37
+ block = block_given?? Proc.new {|b| } : nil
38
+ Builder.new(&block)
39
+ end
38
40
 
39
- @proxy = nil
40
- proxy(options[:proxy])
41
+ self.url_prefix = url || 'http:/'
41
42
 
42
43
  @params.update options[:params] if options[:params]
43
44
  @headers.update options[:headers] if options[:headers]
44
45
 
45
- if block_given?
46
- @builder = Builder.create { |b| yield b }
47
- else
48
- @builder = options[:builder] || Builder.new
49
- end
50
- end
51
-
52
- def use(klass, *args, &block)
53
- @builder.use(klass, *args, &block)
54
- end
46
+ @proxy = nil
47
+ proxy(options.fetch(:proxy) { ENV['http_proxy'] })
55
48
 
56
- def request(key, *args, &block)
57
- @builder.request(key, *args, &block)
49
+ yield self if block_given?
58
50
  end
59
51
 
60
- def response(key, *args, &block)
61
- @builder.response(key, *args, &block)
52
+ # Public: Replace default query parameters.
53
+ def params=(hash)
54
+ @params.replace hash
62
55
  end
63
56
 
64
- def adapter(key, *args, &block)
65
- @builder.adapter(key, *args, &block)
57
+ # Public: Replace default request headers.
58
+ def headers=(hash)
59
+ @headers.replace hash
66
60
  end
67
61
 
68
- def build(options = {}, &block)
69
- @builder.build(options, &block)
70
- end
62
+ extend Forwardable
63
+ def_delegators :builder, :build, :use, :request, :response, :adapter
71
64
 
72
65
  # The "rack app" wrapped in middleware. All requests are sent here.
73
66
  #
@@ -88,58 +81,62 @@ module Faraday
88
81
  end
89
82
  end
90
83
 
91
- def get(url = nil, headers = nil)
92
- block = block_given? ? Proc.new : nil
93
- run_request(:get, url, nil, headers, &block)
94
- end
95
-
96
- def post(url = nil, body = nil, headers = nil)
97
- block = block_given? ? Proc.new : nil
98
- run_request(:post, url, body, headers, &block)
99
- end
100
-
101
- def put(url = nil, body = nil, headers = nil)
102
- block = block_given? ? Proc.new : nil
103
- run_request(:put, url, body, headers, &block)
104
- end
105
-
106
- def patch(url = nil, body = nil, headers = nil)
107
- block = block_given? ? Proc.new : nil
108
- run_request(:patch, url, body, headers, &block)
109
- end
110
-
111
- def head(url = nil, headers = nil)
112
- block = block_given? ? Proc.new : nil
113
- run_request(:head, url, nil, headers, &block)
84
+ # get/head/delete(url, params, headers)
85
+ %w[get head delete].each do |method|
86
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
87
+ def #{method}(url = nil, params = nil, headers = nil)
88
+ run_request(:#{method}, url, nil, headers) { |request|
89
+ request.params.update(params) if params
90
+ yield request if block_given?
91
+ }
92
+ end
93
+ RUBY
114
94
  end
115
95
 
116
- def delete(url = nil, headers = nil)
117
- block = block_given? ? Proc.new : nil
118
- run_request(:delete, url, nil, headers, &block)
96
+ # post/put/patch(url, body, headers)
97
+ %w[post put patch].each do |method|
98
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
99
+ def #{method}(url = nil, body = nil, headers = nil, &block)
100
+ run_request(:#{method}, url, body, headers, &block)
101
+ end
102
+ RUBY
119
103
  end
120
104
 
121
105
  def basic_auth(login, pass)
122
- auth = Base64.encode64("#{login}:#{pass}")
123
- auth.gsub!("\n", "")
124
- @headers['authorization'] = "Basic #{auth}"
106
+ @builder.insert(0, Faraday::Request::BasicAuthentication, login, pass)
125
107
  end
126
108
 
127
109
  def token_auth(token, options = {})
128
- values = ["token=#{token.to_s.inspect}"]
129
- options.each do |key, value|
130
- values << "#{key}=#{value.to_s.inspect}"
110
+ @builder.insert(0, Faraday::Request::TokenAuthentication, token, options)
111
+ end
112
+
113
+ # Internal: Traverse the middleware stack in search of a
114
+ # parallel-capable adapter.
115
+ #
116
+ # Yields in case of not found.
117
+ #
118
+ # Returns a parallel manager or nil if not found.
119
+ def default_parallel_manager
120
+ @default_parallel_manager ||= begin
121
+ handler = @builder.handlers.find { |h|
122
+ h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
123
+ }
124
+ if handler then handler.klass.setup_parallel_manager
125
+ elsif block_given? then yield
126
+ end
131
127
  end
132
- # 21 = "Authorization: Token ".size
133
- comma = ",\n#{' ' * 21}"
134
- @headers['authorization'] = "Token #{values * comma}"
135
128
  end
136
129
 
137
130
  def in_parallel?
138
131
  !!@parallel_manager
139
132
  end
140
133
 
141
- def in_parallel(manager)
142
- @parallel_manager = manager
134
+ def in_parallel(manager = nil)
135
+ @parallel_manager = manager || default_parallel_manager {
136
+ warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
137
+ warn caller[2,10].join("\n")
138
+ nil
139
+ }
143
140
  yield
144
141
  @parallel_manager && @parallel_manager.run
145
142
  ensure
@@ -151,15 +148,23 @@ module Faraday
151
148
 
152
149
  @proxy = if arg.is_a? Hash
153
150
  uri = arg.fetch(:uri) { raise ArgumentError, "no :uri option" }
154
- arg.merge :uri => URI.parse(uri)
151
+ arg.merge :uri => self.class.URI(uri)
155
152
  else
156
- {:uri => URI.parse(arg)}
153
+ {:uri => self.class.URI(arg)}
157
154
  end
158
- rescue TypeError
159
- raise ArgumentError, "bad uri"
160
155
  end
161
156
 
162
- # Parses the giving url with Addressable::URI and stores the individual
157
+ # 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)")
162
+ end
163
+
164
+ def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
165
+ def_delegator :url_prefix, :path, :path_prefix
166
+
167
+ # Parses the giving url with URI and stores the individual
163
168
  # components in this connection. These components serve as defaults for
164
169
  # requests made by this connection.
165
170
  #
@@ -171,27 +176,27 @@ module Faraday
171
176
  # conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
172
177
  #
173
178
  def url_prefix=(url)
174
- uri = URI.parse(url)
175
- self.scheme = uri.scheme
176
- self.host = uri.host
177
- self.port = uri.port
179
+ uri = @url_prefix = self.class.URI(url)
178
180
  self.path_prefix = uri.path
179
181
 
180
- @params.merge_query(uri.query)
182
+ params.merge_query(uri.query)
183
+ uri.query = nil
184
+
181
185
  if uri.user && uri.password
182
186
  basic_auth(CGI.unescape(uri.user), CGI.unescape(uri.password))
187
+ uri.user = uri.password = nil
183
188
  end
184
189
 
185
190
  uri
186
191
  end
187
192
 
188
- # Ensures that the path prefix always has a leading / and no trailing /
193
+ # Ensures that the path prefix always has a leading but no trailing slash
189
194
  def path_prefix=(value)
190
- if value
191
- value.chomp! "/"
192
- value.replace "/#{value}" if value !~ /^\//
195
+ url_prefix.path = if value
196
+ value = value.chomp '/'
197
+ value = '/' + value unless value[0,1] == '/'
198
+ value
193
199
  end
194
- @path_prefix = value
195
200
  end
196
201
 
197
202
  def run_request(method, url, body, headers)
@@ -199,7 +204,7 @@ module Faraday
199
204
  raise ArgumentError, "unknown http method: #{method}"
200
205
  end
201
206
 
202
- request = Request.create(method) do |req|
207
+ request = build_request(method) do |req|
203
208
  req.url(url) if url
204
209
  req.headers.update(headers) if headers
205
210
  req.body = body if body
@@ -210,6 +215,18 @@ module Faraday
210
215
  self.app.call(env)
211
216
  end
212
217
 
218
+ # Internal: Creates and configures the request object.
219
+ #
220
+ # Returns the new Request.
221
+ def build_request(method)
222
+ Request.create(method) do |req|
223
+ req.params = self.params.dup
224
+ req.headers = self.headers.dup
225
+ req.options = self.options.merge(:proxy => self.proxy)
226
+ yield req if block_given?
227
+ end
228
+ end
229
+
213
230
  # Takes a relative url for a request and combines it with the defaults
214
231
  # set on the connection instance.
215
232
  #
@@ -222,20 +239,32 @@ module Faraday
222
239
  # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
223
240
  #
224
241
  def build_url(url, extra_params = nil)
225
- uri = URI.parse(url.to_s)
226
- if @path_prefix && uri.path !~ /^\//
227
- new_path = @path_prefix.size > 1 ? @path_prefix.dup : ''
228
- new_path << "/#{uri.path}" unless uri.path.empty?
229
- uri.path = new_path
230
- end
231
- uri.host ||= @host
232
- uri.port ||= @port
233
- uri.scheme ||= @scheme
242
+ uri = build_exclusive_url(url)
243
+
244
+ query_values = self.params.dup.merge_query(uri.query)
245
+ query_values.update extra_params if extra_params
246
+ uri.query = query_values.empty? ? nil : query_values.to_query
234
247
 
235
- params = @params.dup.merge_query(uri.query)
236
- params.update extra_params if extra_params
237
- uri.query = params.empty? ? nil : params.to_query
248
+ uri
249
+ end
238
250
 
251
+ # Internal: Build an absolute URL based on url_prefix.
252
+ #
253
+ # url - A String or URI-like object
254
+ # params - A Faraday::Utils::ParamsHash to replace the query values
255
+ # of the resulting url (default: nil).
256
+ #
257
+ # Returns the resulting URI instance.
258
+ def build_exclusive_url(url, params = nil)
259
+ url = nil if url.respond_to?(:empty?) and url.empty?
260
+ base = url_prefix
261
+ if url and base.path and base.path !~ /\/$/
262
+ base = base.dup
263
+ base.path = base.path + '/' # ensure trailing slash
264
+ end
265
+ uri = url ? base + url : base
266
+ uri.query = params.to_query if params
267
+ uri.query = nil if uri.query and uri.query.empty?
239
268
  uri
240
269
  end
241
270