1
0
Fork 0
mirror of https://github.com/jnunemaker/httparty synced 2023-03-27 23:23:07 -04:00
httparty/lib/httparty.rb
Alex Vollmer a279df8f70 Be a little smarter with the paths that are passed in.
This is especially important for the redirect-handling in which we
may be pointed to a whole 'nother scheme/host that does not
match our base URI.
2008-10-25 00:34:44 -04:00

174 lines
No EOL
6.3 KiB
Ruby

require 'net/http'
require 'net/https'
require 'uri'
require 'ostruct'
require 'rubygems'
require 'active_support'
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
dir = File.expand_path(File.join(File.dirname(__FILE__), 'httparty'))
require dir + '/core_ext'
module HTTParty
class UnsupportedFormat < StandardError; end
class RedirectionTooDeep < StandardError; end
def self.included(base)
base.extend ClassMethods
end
AllowedFormats = {:xml => 'text/xml', :json => 'application/json'}
module ClassMethods
#
# 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
def base_uri(base_uri=nil)
return @base_uri unless base_uri
# don't want this to ever end with /
base_uri = base_uri.ends_with?('/') ? base_uri.chop : base_uri
@base_uri = normalize_base_uri(base_uri)
end
# 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.
def basic_auth(u, p)
@auth = {:username => u, :password => p}
end
# Updates the default query string parameters
# that should be appended to each request.
def default_params(h={})
raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
@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
def format(f)
raise UnsupportedFormat, "Must be one of: #{AllowedFormats.keys.join(', ')}" unless AllowedFormats.key?(f)
@format = f
end
# TODO: spec out this
def get(path, options={})
send_request 'get', path, options
end
# TODO: spec out this
def post(path, options={})
send_request 'post', path, options
end
# TODO: spec out this
def put(path, options={})
send_request 'put', path, options
end
# TODO: spec out this
def delete(path, options={})
send_request 'delete', path, options
end
private
def http(uri) #:nodoc:
if @http.blank?
@http = Net::HTTP.new(uri.host, uri.port, @http_proxyaddr, @http_proxyport)
@http.use_ssl = (uri.port == 443)
# so we can avoid ssl warnings
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
@http
end
# FIXME: this method is doing way to much and needs to be split up
# options can be any or all of:
# 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)
# headers => hash of headers to send request with
# basic_auth => :username and :password to use as basic http authentication (overrides @auth class instance variable)
# Raises exception Net::XXX (http error code) if an http error occured
def send_request(method, path, options={}) #:nodoc:
options = {:limit => 5}.merge(options)
options[:limit] = 0 if options.delete(:no_follow)
raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
raise ArgumentError, 'only get, post, put and delete methods are supported' unless %w[get post put delete].include?(method.to_s)
raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
path = URI.parse(path)
uri = path.relative? ? URI.parse("#{base_uri}#{path}") : path
existing_query = uri.query ? "#{uri.query}&" : ''
uri.query = if options[:query].blank?
existing_query + default_params.to_query
else
existing_query + (options[:query].is_a?(Hash) ? default_params.merge(options[:query]).to_query : options[:query])
end
klass = Net::HTTP.const_get method.to_s.downcase.capitalize
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
request.initialize_http_header headers.merge(options[:headers] || {})
# note to self: self, do not put basic auth above headers because it removes basic auth
request.basic_auth(basic_auth[:username], basic_auth[:password]) if basic_auth
response = http(uri).request(request)
@format ||= format_from_mimetype(response['content-type'])
case response
when Net::HTTPSuccess
parse_response(response.body)
when Net::HTTPRedirection
options[:limit] -= 1
send_request(method, response['location'], options)
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
end
def parse_response(body) #:nodoc:
return nil if body.nil? or body.empty?
case @format
when :xml
Hash.from_xml(body)
when :json
ActiveSupport::JSON.decode(body)
else
# just return the response if no format
body
end
end
# Makes it so uri is sure to parse stuff like google.com with the http
def normalize_base_uri(str) #:nodoc:
str =~ /^https?:\/\// ? str : "http#{'s' if str.include?(':443')}://#{str}"
end
# 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) }
end
end
end