lib/faraday/connection.rb



require 'addressable/uri'
require 'base64'
require 'set'
require 'faraday/builder'
require 'faraday/request'
require 'faraday/response'
require 'faraday/utils'

module Faraday
  class Connection
    include Addressable

    METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
    METHODS_WITH_BODIES = Set.new [:post, :put, :patch, :options]

    attr_accessor :host, :port, :scheme, :params, :headers, :parallel_manager
    attr_reader   :path_prefix, :builder, :options, :ssl

    # :url
    # :params
    # :headers
    # :request
    # :ssl
    def initialize(url = nil, options = {})
      if url.is_a?(Hash)
        options = url
        url     = options[:url]
      end
      @headers          = Utils::Headers.new
      @params           = Utils::ParamsHash.new
      @options          = options[:request] || {}
      @ssl              = options[:ssl]     || {}
      @parallel_manager = options[:parallel]

      self.url_prefix = url if url
      proxy(options[:proxy])

      @params.update options[:params]   if options[:params]
      @headers.update options[:headers] if options[:headers]

      if block_given?
        @builder = Builder.create { |b| yield b }
      else
        @builder = options[:builder] || Builder.new
      end
    end

    def use(klass, *args, &block)
      @builder.use(klass, *args, &block)
    end

    def request(key, *args, &block)
      @builder.request(key, *args, &block)
    end

    def response(key, *args, &block)
      @builder.response(key, *args, &block)
    end

    def adapter(key, *args, &block)
      @builder.adapter(key, *args, &block)
    end

    def build(options = {}, &block)
      @builder.build(options, &block)
    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
        builder.lock!
        builder.to_app(lambda { |env|
          # the inner app that creates and returns the Response object
          response = Response.new
          response.finish(env) unless env[:parallel_manager]
          env[:response] = response
        })
      end
    end

    def get(url = nil, headers = nil)
      block = block_given? ? Proc.new : nil
      run_request(:get, url, nil, headers, &block)
    end

    def post(url = nil, body = nil, headers = nil)
      block = block_given? ? Proc.new : nil
      run_request(:post, url, body, headers, &block)
    end

    def put(url = nil, body = nil, headers = nil)
      block = block_given? ? Proc.new : nil
      run_request(:put, url, body, headers, &block)
    end

    def patch(url = nil, body = nil, headers = nil)
      block = block_given? ? Proc.new : nil
      run_request(:patch, url, body, headers, &block)
    end

    def head(url = nil, headers = nil)
      block = block_given? ? Proc.new : nil
      run_request(:head, url, nil, headers, &block)
    end

    def delete(url = nil, headers = nil)
      block = block_given? ? Proc.new : nil
      run_request(:delete, url, nil, headers, &block)
    end

    def basic_auth(login, pass)
      auth = Base64.encode64("#{login}:#{pass}")
      auth.gsub!("\n", "")
      @headers['authorization'] = "Basic #{auth}"
    end

    def token_auth(token, options = {})
      values = ["token=#{token.to_s.inspect}"]
      options.each do |key, value|
        values << "#{key}=#{value.to_s.inspect}"
      end
      # 21 = "Authorization: Token ".size
      comma = ",\n#{' ' * 21}"
      @headers['authorization'] = "Token #{values * comma}"
    end

    def in_parallel?
      !!@parallel_manager
    end

    def in_parallel(manager)
      @parallel_manager = manager
      yield
      @parallel_manager && @parallel_manager.run
    ensure
      @parallel_manager = nil
    end

    def proxy(arg = nil)
      return @proxy if arg.nil?

      @proxy =
        case arg
          when String then {:uri => proxy_arg_to_uri(arg)}
          when URI    then {:uri => arg}
          when Hash
            if arg[:uri] = proxy_arg_to_uri(arg[:uri])
              arg
            else
              raise ArgumentError, "no :uri option."
            end
        end
    end

    # Parses the giving url with Addressable::URI and stores the individual
    # components in this connection.  These components serve as defaults for
    # requests made by this connection.
    #
    #   conn = Faraday::Connection.new { ... }
    #   conn.url_prefix = "https://sushi.com/api"
    #   conn.scheme      # => https
    #   conn.path_prefix # => "/api"
    #
    #   conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
    #
    def url_prefix=(url)
      uri              = URI.parse(url)
      self.scheme      = uri.scheme
      self.host        = uri.host
      self.port        = uri.port
      self.path_prefix = uri.path

      @params.merge_query(uri.query)
      basic_auth(uri.user, uri.password) if uri.user && uri.password

      uri
    end

    # Ensures that the path prefix always has a leading / and no trailing /
    def path_prefix=(value)
      if value
        value.chomp!  "/"
        value.replace "/#{value}" if value !~ /^\//
      end
      @path_prefix = value
    end

    def run_request(method, url, body, headers)
      if !METHODS.include?(method)
        raise ArgumentError, "unknown http method: #{method}"
      end

      request = Request.create(method) do |req|
        req.url(url)                if url
        req.headers.update(headers) if headers
        req.body = body             if body
        yield req if block_given?
      end

      env = request.to_env(self)
      self.app.call(env)
    end

    # Takes a relative url for a request and combines it with the defaults
    # set on the connection instance.
    #
    #   conn = Faraday::Connection.new { ... }
    #   conn.url_prefix = "https://sushi.com/api?token=abc"
    #   conn.scheme      # => https
    #   conn.path_prefix # => "/api"
    #
    #   conn.build_url("nigiri?page=2")      # => https://sushi.com/api/nigiri?token=abc&page=2
    #   conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
    #
    def build_url(url, extra_params = nil)
      uri          = URI.parse(url.to_s)
      if @path_prefix && uri.path !~ /^\//
        new_path = @path_prefix.size > 1 ? @path_prefix.dup : ''
        new_path << "/#{uri.path}" unless uri.path.empty?
        uri.path = new_path
      end
      uri.host   ||= @host
      uri.port   ||= @port
      uri.scheme ||= @scheme

      params = @params.dup.merge_query(uri.query)
      params.update extra_params if extra_params
      uri.query = params.empty? ? nil : params.to_query

      uri
    end

    def dup
      self.class.new(build_url(''), :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
    end

    def proxy_arg_to_uri(arg)
      case arg
        when String then URI.parse(arg)
        when URI    then arg
      end
    end
  end
end