jnunemaker / httparty

Makes http fun! Also, makes consuming restful web services dead easy.

This URL has Read+Write access

httparty / lib / httparty.rb
df29a552 » jnunemaker 2008-07-27 Initial commit 1 require 'net/http'
2 require 'net/https'
3 require 'uri'
de9b4fb6 » jnunemaker 2008-07-27 Put in first wave of parsin... Comment 4 require 'ostruct'
df29a552 » jnunemaker 2008-07-27 Initial commit 5 require 'rubygems'
6 require 'active_support'
7
8 $:.unshift(File.dirname(__FILE__)) unless
9 $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
10
1d48da03 » jnunemaker 2008-07-28 Renamed to HTTParty which i... 11 dir = File.expand_path(File.join(File.dirname(__FILE__), 'httparty'))
13620e30 » jnunemaker 2008-07-27 Added Hash#to_struct for ma... 12 require dir + '/core_ext'
de9b4fb6 » jnunemaker 2008-07-27 Put in first wave of parsin... Comment 13
1d48da03 » jnunemaker 2008-07-28 Renamed to HTTParty which i... 14 module HTTParty
df29a552 » jnunemaker 2008-07-27 Initial commit 15 def self.included(base)
16 base.extend ClassMethods
17 end
18
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 19 class UnsupportedFormat < StandardError; end
20
21 AllowedFormats = %w[xml json]
22
23 module ClassMethods
df29a552 » jnunemaker 2008-07-27 Initial commit 24 def base_uri(base_uri=nil)
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 25 return @base_uri unless base_uri
a8e1e6ac » jnunemaker 2008-07-27 tweaked the way base uri wo... 26 # don't want this to ever end with /
27 base_uri = base_uri.ends_with?('/') ? base_uri.chop : base_uri
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 28 @base_uri = normalize_base_uri(base_uri)
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 29 end
30
3a8ad1da » jnunemaker 2008-07-30 Added :basic_auth as an opt... 31 # Warning: This is not thread safe most likely and
32 # only works if you use one set of credentials. I
33 # leave it because it is convenient on some occasions.
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 34 def basic_auth(u, p)
35 @auth = {:username => u, :password => p}
df29a552 » jnunemaker 2008-07-27 Initial commit 36 end
37
3a8ad1da » jnunemaker 2008-07-30 Added :basic_auth as an opt... 38 # Updates the default query string parameters
39 # that should be appended to each request.
236b3ad4 » jnunemaker 2008-07-28 Made it so default_params a... 40 def default_params(h={})
41 raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
0c05dcda » jnunemaker 2008-07-28 Added #default_params metho... 42 @default_params ||= {}
43 return @default_params if h.blank?
44 @default_params.merge!(h)
45 end
46
47 def headers(h={})
48 raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
49 @headers ||= {}
50 return @headers if h.blank?
51 @headers.merge!(h)
52 end
53
236b3ad4 » jnunemaker 2008-07-28 Made it so default_params a... 54 def format(f)
55 f = f.to_s
56 raise UnsupportedFormat, "Must be one of: #{AllowedFormats.join(', ')}" unless AllowedFormats.include?(f)
57 @format = f
58 end
59
60 # TODO: spec out this
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 61 def get(path, options={})
62 send_request 'get', path, options
63 end
236b3ad4 » jnunemaker 2008-07-28 Made it so default_params a... 64
65 # TODO: spec out this
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 66 def post(path, options={})
67 send_request 'post', path, options
68 end
236b3ad4 » jnunemaker 2008-07-28 Made it so default_params a... 69
70 # TODO: spec out this
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 71 def put(path, options={})
72 send_request 'put', path, options
73 end
236b3ad4 » jnunemaker 2008-07-28 Made it so default_params a... 74
75 # TODO: spec out this
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 76 def delete(path, options={})
77 send_request 'delete', path, options
78 end
79
df29a552 » jnunemaker 2008-07-27 Initial commit 80 private
f4570a46 » jnunemaker 2008-07-28 base_uri is no longer requi... 81 def http(uri)
82 if @http.blank?
83 @http = Net::HTTP.new(uri.host, uri.port)
84 @http.use_ssl = (uri.port == 443)
85 # so we can avoid ssl warnings
86 @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
87 end
88 @http
89 end
90
3a8ad1da » jnunemaker 2008-07-30 Added :basic_auth as an opt... 91 # FIXME: this method is doing way to much and needs to be split up
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 92 # options can be any or all of:
9b423a22 » jnunemaker 2008-07-30 :body and :query now both t... 93 # query => hash of keys/values or a query string (foo=bar&baz=poo)
94 # body => hash of keys/values or a query string (foo=bar&baz=poo)
3a8ad1da » jnunemaker 2008-07-30 Added :basic_auth as an opt... 95 # headers => hash of headers to send request with
96 # basic_auth => :username and :password to use as basic http authentication (overrides @auth class instance variable)
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 97 def send_request(method, path, options={})
a8ff3e36 » jnunemaker 2008-07-28 Added some argument errors ... 98 raise ArgumentError, 'only get, post, put and delete methods are supported' unless %w[get post put delete].include?(method.to_s)
99 raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
3a8ad1da » jnunemaker 2008-07-30 Added :basic_auth as an opt... 100 raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
a8e1e6ac » jnunemaker 2008-07-27 tweaked the way base uri wo... 101 # we always want path that begins with /
9b423a22 » jnunemaker 2008-07-30 :body and :query now both t... 102 path = path =~ /^(\/|https?:\/\/)/ ? path : "/#{path}"
103 @format ||= format_from_path(path)
104 uri = URI.parse("#{base_uri}#{path}")
105 existing_query = uri.query ? "#{uri.query}&" : ''
106 uri.query = if options[:query].blank?
107 existing_query
108 else
109 existing_query + (options[:query].is_a?(Hash) ? default_params.merge(options[:query]).to_query : options[:query])
110 end
111 klass = Net::HTTP.const_get method.to_s.downcase.capitalize
112 request = klass.new(uri.request_uri)
113 request.body = options[:body].is_a?(Hash) ? options[:body].to_query : options[:body] unless options[:body].blank?
114 basic_auth = options.delete(:basic_auth) || @auth
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 115 request.initialize_http_header headers.merge(options[:headers] || {})
3a8ad1da » jnunemaker 2008-07-30 Added :basic_auth as an opt... 116 # note to self: self, do not put basic auth above headers because it removes basic auth
117 request.basic_auth(basic_auth[:username], basic_auth[:password]) if basic_auth
9b423a22 » jnunemaker 2008-07-30 :body and :query now both t... 118 response = http(uri).request(request)
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 119 parse_response(response.body)
13620e30 » jnunemaker 2008-07-27 Added Hash#to_struct for ma... 120 end
121
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 122 def parse_response(body)
13620e30 » jnunemaker 2008-07-27 Added Hash#to_struct for ma... 123 case @format
124 when 'xml'
125 Hash.from_xml(body)
126 when 'json'
127 ActiveSupport::JSON.decode(body)
de9b4fb6 » jnunemaker 2008-07-27 Put in first wave of parsin... Comment 128 else
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 129 # just return the response if no format
13620e30 » jnunemaker 2008-07-27 Added Hash#to_struct for ma... 130 body
de9b4fb6 » jnunemaker 2008-07-27 Put in first wave of parsin... Comment 131 end
8e143785 » jnunemaker 2008-07-27 get, post, put and delete n... 132 end
133
df29a552 » jnunemaker 2008-07-27 Initial commit 134 # Makes it so uri is sure to parse stuff like google.com with the http
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 135 def normalize_base_uri(str)
df29a552 » jnunemaker 2008-07-27 Initial commit 136 str =~ /^https?:\/\// ? str : "http#{'s' if str.include?(':443')}://#{str}"
137 end
13620e30 » jnunemaker 2008-07-27 Added Hash#to_struct for ma... 138
139 # Returns a format that we can handle from the path if possible.
140 # Just does simple pattern matching on file extention:
141 # /foobar.xml => 'xml'
142 # /foobar.json => 'json'
143 def format_from_path(path)
0a1737e2 » jnunemaker 2008-07-28 Added several specs for htt... 144 ext = File.extname(path)[1..-1]
145 !ext.blank? && AllowedFormats.include?(ext) ? ext : nil
13620e30 » jnunemaker 2008-07-27 Added Hash#to_struct for ma... 146 end
df29a552 » jnunemaker 2008-07-27 Initial commit 147 end
de9b4fb6 » jnunemaker 2008-07-27 Put in first wave of parsin... Comment 148 end