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,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