module Faraday # A Builder that processes requests into responses by passing through an inner # middleware stack (heavily inspired by Rack). # # Faraday::Connection.new(:url => 'http://sushi.com') do |builder| # builder.request :url_encoded # Faraday::Request::UrlEncoded # builder.adapter :net_http # Faraday::Adapter::NetHttp # end class RackBuilder attr_accessor :handlers # Error raised when trying to modify the stack after calling `lock!` class StackLocked < RuntimeError; end # borrowed from ActiveSupport::Dependencies::Reference & # ActionDispatch::MiddlewareStack::Middleware class Handler @@constants_mutex = Mutex.new @@constants = Hash.new { |h, k| value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k) @@constants_mutex.synchronize { h[k] = value } } attr_reader :name def initialize(klass, *args, &block) @name = klass.to_s if klass.respond_to?(:name) @@constants_mutex.synchronize { @@constants[@name] = klass } end @args, @block = args, block end def klass() @@constants[@name] end def inspect() @name end def ==(other) if other.is_a? Handler self.name == other.name elsif other.respond_to? :name klass == other else @name == other.to_s end end def build(app) klass.new(app, *@args, &@block) end end def initialize(handlers = []) @handlers = handlers if block_given? build(&Proc.new) elsif @handlers.empty? # default stack, if nothing else is configured self.request :url_encoded self.adapter Faraday.default_adapter end end def build(options = {}) raise_if_locked @handlers.clear unless options[:keep] yield(self) if block_given? end def [](idx) @handlers[idx] end # Locks the middleware stack to ensure no further modifications are possible. def lock! @handlers.freeze end def locked? @handlers.frozen? end def use(klass, *args, &block) if klass.is_a? Symbol use_symbol(Faraday::Middleware, klass, *args, &block) else raise_if_locked @handlers << self.class::Handler.new(klass, *args, &block) end end def request(key, *args, &block) use_symbol(Faraday::Request, key, *args, &block) end def response(key, *args, &block) use_symbol(Faraday::Response, key, *args, &block) end def adapter(key, *args, &block) use_symbol(Faraday::Adapter, key, *args, &block) end ## methods to push onto the various positions in the stack: def insert(index, *args, &block) raise_if_locked index = assert_index(index) handler = self.class::Handler.new(*args, &block) @handlers.insert(index, handler) end alias_method :insert_before, :insert def insert_after(index, *args, &block) index = assert_index(index) insert(index + 1, *args, &block) end def swap(index, *args, &block) raise_if_locked index = assert_index(index) @handlers.delete_at(index) insert(index, *args, &block) end def delete(handler) raise_if_locked @handlers.delete(handler) end # Processes a Request into a Response by passing it through this Builder's # middleware stack. # # connection - Faraday::Connection # request - Faraday::Request # # Returns a Faraday::Response. def build_response(connection, request) app.call(build_env(connection, request)) end # The "rack app" wrapped in middleware. All requests are sent here. # # The builder is responsible for creating the app object. After this, # the builder gets locked to ensure no further modifications are made # to the middleware stack. # # Returns an object that responds to `call` and returns a Response. def app @app ||= begin lock! to_app(lambda { |env| response = Response.new response.finish(env) unless env.parallel? env.response = response }) end end def to_app(inner_app) # last added handler is the deepest and thus closest to the inner app @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) } end def ==(other) other.is_a?(self.class) && @handlers == other.handlers end def dup self.class.new(@handlers.dup) end # ENV Keys # :method - a symbolized request method (:get, :post) # :body - the request body that will eventually be converted to a string. # :url - URI instance for the current request. # :status - HTTP response status code # :request_headers - hash of HTTP Headers to be sent to the server # :response_headers - Hash of HTTP headers from the server # :parallel_manager - sent if the connection is in parallel mode # :request - Hash of options for configuring the request. # :timeout - open/read timeout Integer in seconds # :open_timeout - read timeout Integer in seconds # :proxy - Hash of proxy options # :uri - Proxy Server URI # :user - Proxy server username # :password - Proxy server password # :ssl - Hash of options for configuring SSL requests. def build_env(connection, request) Env.new(request.method, request.body, connection.build_exclusive_url(request.path, request.params), request.options, request.headers, connection.ssl, connection.parallel_manager) end private def raise_if_locked raise StackLocked, "can't modify middleware stack after making a request" if locked? end def use_symbol(mod, key, *args, &block) use(mod.lookup_middleware(key), *args, &block) end def assert_index(index) idx = index.is_a?(Integer) ? index : @handlers.index(index) raise "No such handler: #{index.inspect}" unless idx idx end end end