lib/fastly/client.rb



require 'net/http'
require 'net/https'
require 'json'
require 'cgi'
require 'pp'
require 'uri'

class Fastly
  # The UserAgent to communicate with the API
  class Client #:nodoc: all
    begin
      require 'curb-fu'
      CURB_FU=true
    rescue LoadError
      CURB_FU=false
    end

    attr_accessor :http, :api_key, :user, :password, :cookie, :customer

    def initialize(opts)
      [:api_key, :user, :password].each do |key|
        self.send("#{key}=", opts[key]) if opts.has_key?(key)
      end
      base      = opts[:base_url]      || "https://api.fastly.com"
      uri       = URI.parse(base)
      scheme    = uri.scheme
      host      = uri.host
      curb      = opts.has_key?(:use_curb)  ? !!opts[:use_curb] && CURB_FU : CURB_FU
      port      = opts.has_key?(:base_port) ?   opts[:base_port]           : (scheme == "https") ? 443 : 80
      self.http = curb ? Fastly::Client::Curl.new(host, port) : Net::HTTP.new(host, port)
      self.http.use_ssl = (scheme == "https")
      return self unless fully_authed?

      # If we're fully authed (i.e username and password ) then we need to log in
      resp = self.http.post('/login', make_params(:user => user, :password => password))
      raise Fastly::Unauthorized unless resp.success?
      self.cookie = resp['set-cookie']
    end

    def require_key!
      raise Fastly::AuthRequired.new("This request requires an API key") if api_key.nil?

      @require_key = true
    end

    def require_key?
      !!@require_key
    end

    def authed?
      !api_key.nil? || fully_authed?
    end

    # Some methods require full username and password rather than just auth token
    def fully_authed?
      !(user.nil? || password.nil?)
    end

    def set_customer(id)

    end

    def get(path, params={})
      path += "?"+make_params(params) unless params.empty?
      resp  = self.http.get(path, headers)
      return nil if 404 == resp.status
      raise Fastly::Error, resp.message unless resp.success?
      JSON.parse(resp.body)
    end

    def get_stats(path, params={})
      content = get(path, params)
      raise Fastly::Error, content["message"] unless content["status"] == 'success'
      content["data"]
    end

    def post(path, params={})
      post_and_put(:post, path, params)
    end

    def put(path, params={})
      post_and_put(:put, path, params)
    end

    def delete(path)
      resp  = self.http.delete(path, headers)
      return resp.success?
    end

    private

    def post_and_put(method, path, params={})
      query = make_params(params)
      resp  = self.http.send(method, path, query, headers.merge( 'Content-Type' =>  "application/x-www-form-urlencoded"))
      raise Fastly::Error, resp.message unless resp.success?
      JSON.parse(resp.body)
    end

    def headers
      headers = if require_key?
                  api_key_header
                else
                  fully_authed? ? { 'Cookie' => cookie } : api_key_header
                end
      headers.merge!('Fastly-Explicit-Customer' => customer) if customer
      headers.merge!('Content-Accept' => 'application/json')
    ensure
      @require_key = nil
    end

    def api_key_header
      { 'X-Fastly-Key' => api_key }
    end

    def make_params(params)
      params.map { |key,val|
        next if val.nil?
        unless val.is_a?(Hash)
          "#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"
        else
          val.map { |sub_key, sub_val|
            new_key = "#{key}[#{sub_key}]"
            "#{CGI.escape(new_key)}=#{CGI.escape(sub_val.to_s)}"
          }
        end
      }.flatten.delete_if { |v| v.nil? }.join("&")
    end

    # :nodoc: all
    class Curl
      attr_accessor :host, :port, :protocol

      def initialize(host, port=443)
        self.host     = host
        self.port     = port
        self.protocol = 'https'
      end

      def get(path, headers={})
        CurbFu.get({ :host => host, :port => port, :path => path, :headers => headers, :protocol => protocol })
      end

      def post(path, params, headers={})
        CurbFu.post({ :host => host, :port => port, :path => path, :headers => headers, :protocol => protocol }, params)
      end

      def put(path, params, headers={})
        CurbFu.put({ :host => host, :port => port, :path => path, :headers => headers, :params => params, :protocol => protocol }, params)
      end

      def delete(path, headers={})
        CurbFu.delete({ :host => host, :port => port, :path => path, :headers => headers, :protocol => protocol })
      end

      def use_ssl=(ssl)
        self.protocol = ssl ? 'https' : 'http'
      end
    end
  end
end

# :nodoc: all
class Net::HTTPResponse
  def success?
    return Net::HTTPSuccess === self
  end

  def status
    return self.code.to_i
  end
end



# :nodoc: all
class CurbFu::Response::Base
  def get_fields(key)
    if ( match = @headers.find{|k,v| k.downcase == key.downcase} )
      [match.last].flatten
    else
      []
    end
  end

  def [](key)
    get_fields(key).last
  end
end