2008-07-27 11:52:18 -04:00
|
|
|
require 'net/http'
|
|
|
|
require 'net/https'
|
|
|
|
require 'uri'
|
2008-07-27 16:35:31 -04:00
|
|
|
require 'ostruct'
|
2008-07-27 11:52:18 -04:00
|
|
|
require 'rubygems'
|
|
|
|
require 'active_support'
|
|
|
|
|
2008-11-08 10:41:32 -05:00
|
|
|
directory = File.dirname(__FILE__)
|
|
|
|
$:.unshift(directory) unless $:.include?(directory) || $:.include?(File.expand_path(directory))
|
2008-09-19 19:31:06 -04:00
|
|
|
|
2008-07-28 10:49:53 -04:00
|
|
|
module HTTParty
|
2008-08-22 21:50:51 -04:00
|
|
|
class UnsupportedFormat < StandardError; end
|
2008-09-19 19:31:06 -04:00
|
|
|
class RedirectionTooDeep < StandardError; end
|
2008-08-22 21:50:51 -04:00
|
|
|
|
2008-07-27 11:52:18 -04:00
|
|
|
def self.included(base)
|
|
|
|
base.extend ClassMethods
|
|
|
|
end
|
|
|
|
|
2008-08-22 21:50:51 -04:00
|
|
|
AllowedFormats = {:xml => 'text/xml', :json => 'application/json'}
|
2008-11-08 12:05:59 -05:00
|
|
|
SupportedHTTPMethods = [Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Put, Net::HTTP::Delete]
|
2008-07-28 11:56:58 -04:00
|
|
|
|
|
|
|
module ClassMethods
|
2008-08-18 15:48:54 -04:00
|
|
|
#
|
|
|
|
# Set an http proxy
|
|
|
|
#
|
|
|
|
# class Twitter
|
|
|
|
# include HTTParty
|
|
|
|
# http_proxy http://myProxy, 1080
|
|
|
|
# ....
|
|
|
|
def http_proxy(addr=nil, port = nil)
|
|
|
|
@http_proxyaddr = addr
|
|
|
|
@http_proxyport = port
|
|
|
|
end
|
|
|
|
|
2008-07-27 11:52:18 -04:00
|
|
|
def base_uri(base_uri=nil)
|
2008-07-27 14:44:18 -04:00
|
|
|
return @base_uri unless base_uri
|
2008-07-28 11:56:58 -04:00
|
|
|
@base_uri = normalize_base_uri(base_uri)
|
2008-07-27 14:44:18 -04:00
|
|
|
end
|
|
|
|
|
2008-07-31 00:05:36 -04:00
|
|
|
# Warning: This is not thread safe most likely and
|
|
|
|
# only works if you use one set of credentials. I
|
|
|
|
# leave it because it is convenient on some occasions.
|
2008-07-27 14:44:18 -04:00
|
|
|
def basic_auth(u, p)
|
|
|
|
@auth = {:username => u, :password => p}
|
2008-07-27 11:52:18 -04:00
|
|
|
end
|
|
|
|
|
2008-07-31 00:05:36 -04:00
|
|
|
# Updates the default query string parameters
|
|
|
|
# that should be appended to each request.
|
2008-07-28 12:40:40 -04:00
|
|
|
def default_params(h={})
|
|
|
|
raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
|
2008-07-28 12:08:21 -04:00
|
|
|
@default_params ||= {}
|
|
|
|
return @default_params if h.blank?
|
|
|
|
@default_params.merge!(h)
|
|
|
|
end
|
|
|
|
|
|
|
|
def headers(h={})
|
|
|
|
raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
|
|
|
|
@headers ||= {}
|
|
|
|
return @headers if h.blank?
|
|
|
|
@headers.merge!(h)
|
|
|
|
end
|
|
|
|
|
2008-07-28 12:40:40 -04:00
|
|
|
def format(f)
|
2008-08-22 21:50:51 -04:00
|
|
|
raise UnsupportedFormat, "Must be one of: #{AllowedFormats.keys.join(', ')}" unless AllowedFormats.key?(f)
|
2008-07-28 12:40:40 -04:00
|
|
|
@format = f
|
|
|
|
end
|
|
|
|
|
|
|
|
# TODO: spec out this
|
2008-07-27 14:44:18 -04:00
|
|
|
def get(path, options={})
|
2008-11-08 12:05:59 -05:00
|
|
|
send_request Net::HTTP::Get, path, options
|
2008-07-27 14:44:18 -04:00
|
|
|
end
|
2008-07-28 12:40:40 -04:00
|
|
|
|
|
|
|
# TODO: spec out this
|
2008-07-27 14:44:18 -04:00
|
|
|
def post(path, options={})
|
2008-11-08 12:05:59 -05:00
|
|
|
send_request Net::HTTP::Post, path, options
|
2008-07-27 14:44:18 -04:00
|
|
|
end
|
2008-07-28 12:40:40 -04:00
|
|
|
|
|
|
|
# TODO: spec out this
|
2008-07-27 14:44:18 -04:00
|
|
|
def put(path, options={})
|
2008-11-08 12:05:59 -05:00
|
|
|
send_request Net::HTTP::Put, path, options
|
2008-07-27 14:44:18 -04:00
|
|
|
end
|
2008-07-28 12:40:40 -04:00
|
|
|
|
|
|
|
# TODO: spec out this
|
2008-07-27 14:44:18 -04:00
|
|
|
def delete(path, options={})
|
2008-11-08 12:05:59 -05:00
|
|
|
send_request Net::HTTP::Delete, path, options
|
2008-07-27 14:44:18 -04:00
|
|
|
end
|
|
|
|
|
2008-07-27 11:52:18 -04:00
|
|
|
private
|
2008-07-31 00:30:52 -04:00
|
|
|
def http(uri) #:nodoc:
|
2008-11-08 11:01:44 -05:00
|
|
|
http = Net::HTTP.new(uri.host, uri.port, @http_proxyaddr, @http_proxyport)
|
|
|
|
http.use_ssl = (uri.port == 443)
|
|
|
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
|
|
http
|
2008-07-28 15:47:05 -04:00
|
|
|
end
|
|
|
|
|
2008-07-31 00:05:36 -04:00
|
|
|
# FIXME: this method is doing way to much and needs to be split up
|
2008-07-27 14:44:18 -04:00
|
|
|
# options can be any or all of:
|
2008-07-31 00:22:59 -04:00
|
|
|
# query => hash of keys/values or a query string (foo=bar&baz=poo)
|
|
|
|
# body => hash of keys/values or a query string (foo=bar&baz=poo)
|
2008-07-31 00:05:36 -04:00
|
|
|
# headers => hash of headers to send request with
|
|
|
|
# basic_auth => :username and :password to use as basic http authentication (overrides @auth class instance variable)
|
2008-08-18 15:48:54 -04:00
|
|
|
# Raises exception Net::XXX (http error code) if an http error occured
|
2008-11-08 12:05:59 -05:00
|
|
|
def send_request(klass, path, options={}) #:nodoc:
|
2008-09-19 19:31:06 -04:00
|
|
|
options = {:limit => 5}.merge(options)
|
|
|
|
options[:limit] = 0 if options.delete(:no_follow)
|
2008-11-08 11:01:44 -05:00
|
|
|
|
2008-09-19 19:31:06 -04:00
|
|
|
raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
|
2008-11-08 12:05:59 -05:00
|
|
|
raise ArgumentError, 'only get, post, put and delete methods are supported' unless SupportedHTTPMethods.include?(klass)
|
2008-07-28 13:32:35 -04:00
|
|
|
raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
|
2008-07-31 00:05:36 -04:00
|
|
|
raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
|
2008-11-08 11:01:44 -05:00
|
|
|
|
2008-09-19 19:42:55 -04:00
|
|
|
path = URI.parse(path)
|
|
|
|
uri = path.relative? ? URI.parse("#{base_uri}#{path}") : path
|
2008-07-31 00:22:59 -04:00
|
|
|
existing_query = uri.query ? "#{uri.query}&" : ''
|
|
|
|
uri.query = if options[:query].blank?
|
2008-08-09 13:17:36 -04:00
|
|
|
existing_query + default_params.to_query
|
2008-07-31 00:22:59 -04:00
|
|
|
else
|
|
|
|
existing_query + (options[:query].is_a?(Hash) ? default_params.merge(options[:query]).to_query : options[:query])
|
|
|
|
end
|
2008-11-08 11:01:44 -05:00
|
|
|
|
2008-07-31 00:22:59 -04:00
|
|
|
request = klass.new(uri.request_uri)
|
|
|
|
request.body = options[:body].is_a?(Hash) ? options[:body].to_query : options[:body] unless options[:body].blank?
|
|
|
|
basic_auth = options.delete(:basic_auth) || @auth
|
2008-07-27 14:44:18 -04:00
|
|
|
request.initialize_http_header headers.merge(options[:headers] || {})
|
2008-07-31 00:05:36 -04:00
|
|
|
request.basic_auth(basic_auth[:username], basic_auth[:password]) if basic_auth
|
2008-07-31 00:22:59 -04:00
|
|
|
response = http(uri).request(request)
|
2008-08-22 21:50:51 -04:00
|
|
|
@format ||= format_from_mimetype(response['content-type'])
|
|
|
|
|
2008-08-18 15:48:54 -04:00
|
|
|
case response
|
2008-08-22 21:50:51 -04:00
|
|
|
when Net::HTTPSuccess
|
2008-08-18 15:48:54 -04:00
|
|
|
parse_response(response.body)
|
2008-09-19 19:31:06 -04:00
|
|
|
when Net::HTTPRedirection
|
|
|
|
options[:limit] -= 1
|
2008-11-08 12:05:59 -05:00
|
|
|
send_request(klass, response['location'], options)
|
2008-08-18 15:48:54 -04:00
|
|
|
else
|
|
|
|
response.instance_eval { class << self; attr_accessor :body_parsed; end }
|
|
|
|
begin; response.body_parsed = parse_response(response.body); rescue; end
|
|
|
|
response.error! # raises exception corresponding to http error Net::XXX
|
|
|
|
end
|
|
|
|
|
2008-07-27 18:05:31 -04:00
|
|
|
end
|
|
|
|
|
2008-07-31 00:30:52 -04:00
|
|
|
def parse_response(body) #:nodoc:
|
2008-08-27 16:38:19 -04:00
|
|
|
return nil if body.nil? or body.empty?
|
2008-07-27 18:05:31 -04:00
|
|
|
case @format
|
2008-08-22 21:50:51 -04:00
|
|
|
when :xml
|
2008-07-27 18:05:31 -04:00
|
|
|
Hash.from_xml(body)
|
2008-08-22 21:50:51 -04:00
|
|
|
when :json
|
2008-07-27 18:05:31 -04:00
|
|
|
ActiveSupport::JSON.decode(body)
|
2008-07-27 16:35:31 -04:00
|
|
|
else
|
2008-07-27 18:05:31 -04:00
|
|
|
body
|
2008-07-27 16:35:31 -04:00
|
|
|
end
|
2008-07-27 14:44:18 -04:00
|
|
|
end
|
|
|
|
|
2008-07-27 11:52:18 -04:00
|
|
|
# Makes it so uri is sure to parse stuff like google.com with the http
|
2008-11-08 11:18:25 -05:00
|
|
|
def normalize_base_uri(url) #:nodoc:
|
|
|
|
use_ssl = (url =~ /^https/) || url.include?(':443')
|
|
|
|
url.chop! if url.ends_with?('/')
|
|
|
|
url.gsub!(/^https?:\/\//i, '')
|
|
|
|
"http#{'s' if use_ssl}://#{url}"
|
2008-07-27 11:52:18 -04:00
|
|
|
end
|
2008-07-27 18:05:31 -04:00
|
|
|
|
2008-08-22 21:50:51 -04:00
|
|
|
# Uses the HTTP Content-Type header to determine the format of the response
|
|
|
|
# It compares the MIME type returned to the types stored in the AllowedFormats hash
|
|
|
|
def format_from_mimetype(mimetype) #:nodoc:
|
|
|
|
AllowedFormats.each { |k, v| return k if mimetype.include?(v) }
|
2008-07-27 18:05:31 -04:00
|
|
|
end
|
2008-07-27 11:52:18 -04:00
|
|
|
end
|
2008-11-08 12:05:59 -05:00
|
|
|
end
|