alexvollmer-httparty 0.2.6 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/History +34 -0
  2. data/Manifest +25 -2
  3. data/README +2 -2
  4. data/Rakefile +6 -2
  5. data/bin/httparty +39 -44
  6. data/cucumber.yml +1 -0
  7. data/examples/basic.rb +6 -1
  8. data/examples/delicious.rb +1 -0
  9. data/examples/rubyurl.rb +1 -1
  10. data/features/basic_authentication.feature +20 -0
  11. data/features/command_line.feature +7 -0
  12. data/features/deals_with_http_error_codes.feature +26 -0
  13. data/features/handles_multiple_formats.feature +34 -0
  14. data/features/steps/env.rb +15 -0
  15. data/features/steps/httparty_response_steps.rb +26 -0
  16. data/features/steps/httparty_steps.rb +15 -0
  17. data/features/steps/mongrel_helper.rb +55 -0
  18. data/features/steps/remote_service_steps.rb +47 -0
  19. data/features/supports_redirection.feature +22 -0
  20. data/httparty.gemspec +4 -7
  21. data/lib/core_extensions.rb +48 -222
  22. data/lib/httparty/cookie_hash.rb +9 -0
  23. data/lib/httparty/exceptions.rb +3 -0
  24. data/lib/httparty/module_inheritable_attributes.rb +25 -0
  25. data/lib/httparty/parsers/json.rb +74 -0
  26. data/lib/httparty/parsers/xml.rb +209 -0
  27. data/lib/httparty/parsers.rb +4 -0
  28. data/lib/httparty/request.rb +63 -76
  29. data/lib/httparty/response.rb +17 -0
  30. data/lib/httparty/version.rb +2 -2
  31. data/lib/httparty.rb +108 -19
  32. data/spec/fixtures/empty.xml +0 -0
  33. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  34. data/spec/hash_spec.rb +49 -0
  35. data/spec/httparty/cookie_hash_spec.rb +38 -0
  36. data/spec/httparty/parsers/json_spec.rb +42 -0
  37. data/spec/httparty/parsers/xml_spec.rb +445 -0
  38. data/spec/httparty/request_spec.rb +219 -80
  39. data/spec/httparty/response_spec.rb +53 -0
  40. data/spec/httparty_spec.rb +125 -64
  41. data/spec/spec_helper.rb +5 -8
  42. data/spec/string_spec.rb +27 -0
  43. metadata +34 -14
  44. data/lib/module_level_inheritable_attributes.rb +0 -25
  45. data/spec/as_buggery_spec.rb +0 -16
@@ -11,8 +11,7 @@ module HTTParty
11
11
  self.path = path
12
12
  self.options = {
13
13
  :limit => o.delete(:no_follow) ? 0 : 5,
14
- :default_params => {},
15
- :keep_body => false
14
+ :default_params => {}
16
15
  }.merge(o)
17
16
  end
18
17
 
@@ -21,9 +20,13 @@ module HTTParty
21
20
  end
22
21
 
23
22
  def uri
24
- uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
25
- uri.query = query_string(uri)
26
- uri
23
+ new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
24
+
25
+ if Net::HTTP::Get === http_method and !@redirect
26
+ new_uri.query = query_string(new_uri)
27
+ end
28
+
29
+ new_uri
27
30
  end
28
31
 
29
32
  def format
@@ -31,38 +34,47 @@ module HTTParty
31
34
  end
32
35
 
33
36
  def perform
34
- validate!
35
- handle_response!(get_response(uri))
37
+ validate
38
+ setup_raw_request
39
+ handle_response(get_response)
36
40
  end
37
41
 
38
42
  private
39
- def http(uri) #:nodoc:
43
+ def http
40
44
  http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
41
45
  http.use_ssl = (uri.port == 443)
42
46
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
43
47
  http
44
48
  end
45
49
 
46
- def get_response(uri) #:nodoc:
47
- request = http_method.new(uri.request_uri)
50
+ def configure_basic_auth
51
+ @raw_request.basic_auth(options[:basic_auth][:username], options[:basic_auth][:password])
52
+ end
53
+
54
+ def setup_raw_request
55
+ @raw_request = http_method.new(uri.request_uri)
48
56
 
49
57
  if post? && options[:query]
50
- request.set_form_data(options[:query])
58
+ @raw_request.set_form_data(options[:query])
51
59
  end
60
+
61
+ @raw_request.body = options[:body].is_a?(Hash) ? options[:body].to_params : options[:body] unless options[:body].blank?
62
+ @raw_request.initialize_http_header options[:headers]
63
+
64
+ configure_basic_auth if options[:basic_auth]
65
+ end
52
66
 
53
- request.body = options[:body].is_a?(Hash) ? options[:body].to_params : options[:body] unless options[:body].blank?
54
- request.initialize_http_header options[:headers]
55
-
56
- if options[:basic_auth]
57
- request.basic_auth(options[:basic_auth][:username], options[:basic_auth][:password])
58
- end
67
+ def perform_actual_request
68
+ http.request(@raw_request)
69
+ end
59
70
 
60
- response = http(uri).request(request)
71
+ def get_response
72
+ response = perform_actual_request
61
73
  options[:format] ||= format_from_mimetype(response['content-type'])
62
74
  response
63
75
  end
64
-
65
- def query_string(uri) #:nodoc:
76
+
77
+ def query_string(uri)
66
78
  query_string_parts = []
67
79
  query_string_parts << uri.query unless uri.query.blank?
68
80
 
@@ -75,48 +87,47 @@ module HTTParty
75
87
 
76
88
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
77
89
  end
78
-
90
+
79
91
  # Raises exception Net::XXX (http error code) if an http error occured
80
- def handle_response!(response) #:nodoc:
92
+ def handle_response(response)
81
93
  case response
82
- when Net::HTTPSuccess
83
- original_resp = parse_response(response.body)
84
- final_resp = Response.new(response.body, nil, original_resp)
85
- response.each_header { |n,v| final_resp.add_header(n, v) }
86
- final_resp
87
- when Net::HTTPRedirection
88
- options[:limit] -= 1
89
- self.path = response['location']
90
- perform
91
- else
92
- response.instance_eval { class << self; attr_accessor :body_parsed; end }
93
- begin; response.body_parsed = parse_response(response.body); rescue; end
94
- response.error! # raises exception corresponding to http error Net::XXX
95
- end
94
+ when Net::HTTPRedirection
95
+ options[:limit] -= 1
96
+ if options[:limit].to_i <= 0
97
+ raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep'
98
+ end
99
+ self.http_method = Net::HTTP::Get
100
+ self.path = response['location']
101
+ @redirect = true
102
+ perform
103
+ else
104
+ parsed_response = parse_response(response.body)
105
+ Response.new(parsed_response, response.body, response.code, response.to_hash)
106
+ end
96
107
  end
97
-
98
- def parse_response(body) #:nodoc:
108
+
109
+ def parse_response(body)
99
110
  return nil if body.nil? or body.empty?
100
- response = case format
101
- when :xml
102
- ToHashParser.from_xml(body)
103
- when :json
104
- JSON.parse(body)
105
- else
106
- body
107
- end
108
- response
111
+ case format
112
+ when :xml
113
+ HTTParty::Parsers::XML.parse(body)
114
+ when :json
115
+ HTTParty::Parsers::JSON.decode(body)
116
+ when :yaml
117
+ YAML::load(body)
118
+ else
119
+ body
120
+ end
109
121
  end
110
122
 
111
123
  # Uses the HTTP Content-Type header to determine the format of the response
112
124
  # It compares the MIME type returned to the types stored in the AllowedFormats hash
113
- def format_from_mimetype(mimetype) #:nodoc:
125
+ def format_from_mimetype(mimetype)
114
126
  return nil if mimetype.nil?
115
- AllowedFormats.each { |k, v| return k if mimetype.include?(v) }
127
+ AllowedFormats.each { |k, v| return v if mimetype.include?(k) }
116
128
  end
117
-
118
- def validate! #:nodoc:
119
- raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
129
+
130
+ def validate
120
131
  raise ArgumentError, 'only get, post, put and delete methods are supported' unless SupportedHTTPMethods.include?(http_method)
121
132
  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
122
133
  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
@@ -127,28 +138,4 @@ module HTTParty
127
138
  Net::HTTP::Post == http_method
128
139
  end
129
140
  end
130
-
131
- class Response
132
- attr_accessor :original_body, :format, :headers
133
-
134
- def initialize(original_body, format, delegate)
135
- self.original_body = original_body
136
- self.format = format
137
- self.headers = { }
138
- @delegate = delegate
139
- end
140
-
141
- def add_header(name, value)
142
- @headers[name] = value
143
- end
144
-
145
- def method_missing(sym, *args)
146
- @delegate.send(sym, *args)
147
- end
148
-
149
- def pretty_print(pp)
150
- @delegate.pretty_print(pp)
151
- end
152
-
153
- end
154
141
  end
@@ -0,0 +1,17 @@
1
+ module HTTParty
2
+ class Response < BlankSlate #:nodoc:
3
+ attr_accessor :body, :code, :headers
4
+ attr_reader :delegate
5
+
6
+ def initialize(delegate, body, code, headers={})
7
+ @delegate = delegate
8
+ @body = body
9
+ @code = code
10
+ @headers = headers
11
+ end
12
+
13
+ def method_missing(name, *args, &block)
14
+ @delegate.send(name, *args, &block)
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
- module HTTParty
2
- Version = '0.2.6'
1
+ module HTTParty #:nodoc:
2
+ Version = '0.3.1'
3
3
  end
data/lib/httparty.rb CHANGED
@@ -2,63 +2,135 @@ $:.unshift(File.dirname(__FILE__))
2
2
 
3
3
  require 'net/http'
4
4
  require 'net/https'
5
- require 'rubygems'
6
- gem 'json', '>= 1.1.3'
7
- require 'json'
8
-
9
- require 'module_level_inheritable_attributes'
10
5
  require 'core_extensions'
6
+ require 'httparty/module_inheritable_attributes'
11
7
 
12
- module HTTParty
13
- AllowedFormats = {:xml => 'text/xml', :json => 'application/json', :html => 'text/html'}
8
+ module HTTParty
9
+
10
+ AllowedFormats = {
11
+ 'text/xml' => :xml,
12
+ 'application/xml' => :xml,
13
+ 'application/json' => :json,
14
+ 'text/json' => :json,
15
+ 'application/javascript' => :json,
16
+ 'text/javascript' => :json,
17
+ 'text/html' => :html,
18
+ 'application/x-yaml' => :yaml,
19
+ 'text/yaml' => :yaml
20
+ } unless defined?(AllowedFormats)
14
21
 
15
22
  def self.included(base)
16
23
  base.extend ClassMethods
17
- base.send :include, ModuleLevelInheritableAttributes
24
+ base.send :include, HTTParty::ModuleInheritableAttributes
18
25
  base.send(:mattr_inheritable, :default_options)
19
26
  base.instance_variable_set("@default_options", {})
20
27
  end
21
28
 
22
29
  module ClassMethods
23
- def default_options
24
- @default_options
25
- end
26
-
30
+ # Allows setting http proxy information to be used
31
+ #
32
+ # class Foo
33
+ # include HTTParty
34
+ # http_proxy 'http://foo.com', 80
35
+ # end
27
36
  def http_proxy(addr=nil, port = nil)
28
37
  default_options[:http_proxyaddr] = addr
29
38
  default_options[:http_proxyport] = port
30
39
  end
31
-
40
+
41
+ # Allows setting a base uri to be used for each request.
42
+ # Will normalize uri to include http, etc.
43
+ #
44
+ # class Foo
45
+ # include HTTParty
46
+ # base_uri 'twitter.com'
47
+ # end
32
48
  def base_uri(uri=nil)
33
49
  return default_options[:base_uri] unless uri
34
50
  default_options[:base_uri] = HTTParty.normalize_base_uri(uri)
35
51
  end
36
-
52
+
53
+ # Allows setting basic authentication username and password.
54
+ #
55
+ # class Foo
56
+ # include HTTParty
57
+ # basic_auth 'username', 'password'
58
+ # end
37
59
  def basic_auth(u, p)
38
60
  default_options[:basic_auth] = {:username => u, :password => p}
39
61
  end
40
62
 
63
+ # Allows setting default parameters to be appended to each request.
64
+ # Great for api keys and such.
65
+ #
66
+ # class Foo
67
+ # include HTTParty
68
+ # default_params :api_key => 'secret', :another => 'foo'
69
+ # end
41
70
  def default_params(h={})
42
71
  raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
43
72
  default_options[:default_params] ||= {}
44
73
  default_options[:default_params].merge!(h)
45
74
  end
46
-
75
+
76
+ # Allows setting a base uri to be used for each request.
77
+ #
78
+ # class Foo
79
+ # include HTTParty
80
+ # headers 'Accept' => 'text/html'
81
+ # end
47
82
  def headers(h={})
48
83
  raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
49
84
  default_options[:headers] ||= {}
50
85
  default_options[:headers].merge!(h)
51
86
  end
87
+
88
+ def cookies(h={})
89
+ raise ArgumentError, 'Cookies must be a hash' unless h.is_a?(Hash)
90
+ default_options[:cookies] ||= CookieHash.new
91
+ default_options[:cookies].add_cookies(h)
92
+ end
52
93
 
94
+ # Allows setting the format with which to parse.
95
+ # Must be one of the allowed formats ie: json, xml
96
+ #
97
+ # class Foo
98
+ # include HTTParty
99
+ # format :json
100
+ # end
53
101
  def format(f)
54
- raise UnsupportedFormat, "Must be one of: #{AllowedFormats.keys.join(', ')}" unless AllowedFormats.key?(f)
102
+ raise UnsupportedFormat, "Must be one of: #{AllowedFormats.values.join(', ')}" unless AllowedFormats.value?(f)
55
103
  default_options[:format] = f
56
104
  end
57
105
 
106
+ # Allows making a get request to a url.
107
+ #
108
+ # class Foo
109
+ # include HTTParty
110
+ # end
111
+ #
112
+ # # Simple get with full url
113
+ # Foo.get('http://foo.com/resource.json')
114
+ #
115
+ # # Simple get with full url and query parameters
116
+ # # ie: http://foo.com/resource.json?limit=10
117
+ # Foo.get('http://foo.com/resource.json', :query => {:limit => 10})
58
118
  def get(path, options={})
59
119
  perform_request Net::HTTP::Get, path, options
60
120
  end
61
-
121
+
122
+ # Allows making a post request to a url.
123
+ #
124
+ # class Foo
125
+ # include HTTParty
126
+ # end
127
+ #
128
+ # # Simple post with full url and setting the body
129
+ # Foo.post('http://foo.com/resources', :body => {:bar => 'baz'})
130
+ #
131
+ # # Simple post with full url using :query option,
132
+ # # which gets set as form data on the request.
133
+ # Foo.post('http://foo.com/resources', :query => {:bar => 'baz'})
62
134
  def post(path, options={})
63
135
  perform_request Net::HTTP::Post, path, options
64
136
  end
@@ -70,11 +142,25 @@ module HTTParty
70
142
  def delete(path, options={})
71
143
  perform_request Net::HTTP::Delete, path, options
72
144
  end
145
+
146
+ def default_options #:nodoc:
147
+ @default_options
148
+ end
73
149
 
74
150
  private
75
151
  def perform_request(http_method, path, options) #:nodoc:
152
+ process_cookies(options)
76
153
  Request.new(http_method, path, default_options.dup.merge(options)).perform
77
154
  end
155
+
156
+ def process_cookies(options) #:nodoc:
157
+ return unless options[:cookies] || default_options[:cookies]
158
+ options[:headers] ||= {}
159
+ options[:headers]["cookie"] = cookies(options[:cookies] || {}).to_cookie_string
160
+
161
+ default_options.delete(:cookies)
162
+ options.delete(:cookies)
163
+ end
78
164
  end
79
165
 
80
166
  def self.normalize_base_uri(url) #:nodoc:
@@ -87,7 +173,7 @@ module HTTParty
87
173
  "http#{'s' if use_ssl}://#{url}"
88
174
  end
89
175
 
90
- class Basement
176
+ class Basement #:nodoc:
91
177
  include HTTParty
92
178
  end
93
179
 
@@ -108,5 +194,8 @@ module HTTParty
108
194
  end
109
195
  end
110
196
 
197
+ require 'httparty/cookie_hash'
111
198
  require 'httparty/exceptions'
112
- require 'httparty/request'
199
+ require 'httparty/request'
200
+ require 'httparty/response'
201
+ require 'httparty/parsers'
File without changes
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <Entities total="0" results="0" page="1" page-size="25" href="https://s3-sandbox.parature.com/api/v1/5578/5633/Account" />
data/spec/hash_spec.rb ADDED
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Hash, "to_xml_attributes" do
4
+ before do
5
+ @hash = { :one => "ONE", "two" => "TWO" }
6
+ end
7
+
8
+ it "should turn the hash into xml attributes" do
9
+ attrs = @hash.to_xml_attributes
10
+ attrs.should match(/one="ONE"/m)
11
+ attrs.should match(/two="TWO"/m)
12
+ end
13
+
14
+ it 'should preserve _ in hash keys' do
15
+ attrs = {
16
+ :some_long_attribute => "with short value",
17
+ :crash => :burn,
18
+ :merb => "uses extlib"
19
+ }.to_xml_attributes
20
+
21
+ attrs.should =~ /some_long_attribute="with short value"/
22
+ attrs.should =~ /merb="uses extlib"/
23
+ attrs.should =~ /crash="burn"/
24
+ end
25
+ end
26
+
27
+
28
+ describe Hash, 'to_params' do
29
+ {
30
+ { "foo" => "bar", "baz" => "bat" } => "foo=bar&baz=bat",
31
+ { "foo" => [ "bar", "baz" ] } => "foo[]=bar&foo[]=baz",
32
+ { "foo" => [ {"bar" => "1"}, {"bar" => 2} ] } => "foo[][bar]=1&foo[][bar]=2",
33
+ { "foo" => { "bar" => [ {"baz" => 1}, {"baz" => "2"} ] } } => "foo[bar][][baz]=1&foo[bar][][baz]=2",
34
+ { "foo" => {"1" => "bar", "2" => "baz"} } => "foo[1]=bar&foo[2]=baz"
35
+ }.each do |hash, params|
36
+ it "should covert hash: #{hash.inspect} to params: #{params.inspect}" do
37
+ hash.to_params.split('&').sort.should == params.split('&').sort
38
+ end
39
+ end
40
+
41
+ it 'should not leave a trailing &' do
42
+ { :name => 'Bob', :address => { :street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222'] } }.to_params.should_not match(/&$/)
43
+ end
44
+
45
+ it 'should URL encode unsafe characters' do
46
+ {:q => "?&\" +"}.to_params.should == "q=%3F%26%22%20%2B"
47
+ end
48
+
49
+ end
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), '../spec_helper')
2
+
3
+ describe HTTParty::CookieHash do
4
+ before(:each) do
5
+ @cookie_hash = HTTParty::CookieHash.new
6
+ end
7
+
8
+ describe "#add_cookies" do
9
+ it "should add new key/value pairs to the hash" do
10
+ @cookie_hash.add_cookies(:foo => "bar")
11
+ @cookie_hash.add_cookies(:rofl => "copter")
12
+ @cookie_hash.length.should eql(2)
13
+ end
14
+
15
+ it "should overwrite any existing key" do
16
+ @cookie_hash.add_cookies(:foo => "bar")
17
+ @cookie_hash.add_cookies(:foo => "copter")
18
+ @cookie_hash.length.should eql(1)
19
+ @cookie_hash[:foo].should eql("copter")
20
+ end
21
+ end
22
+
23
+ # The regexen are required because Hashes aren't ordered, so a test against
24
+ # a hardcoded string was randomly failing.
25
+ describe "#to_cookie_string" do
26
+ before(:each) do
27
+ @cookie_hash.add_cookies(:foo => "bar")
28
+ @cookie_hash.add_cookies(:rofl => "copter")
29
+ @s = @cookie_hash.to_cookie_string
30
+ end
31
+
32
+ it "should format the key/value pairs, delimited by semi-colons" do
33
+ @s.should match(/foo=bar/)
34
+ @s.should match(/rofl=copter/)
35
+ @s.should match(/^\w+=\w+; \w+=\w+$/)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ describe HTTParty::Parsers::JSON do
4
+ TESTS = {
5
+ %q({"data": "G\u00fcnter"}) => {"data" => "Günter"},
6
+ %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
7
+ %q({returnTo:{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
8
+ %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
9
+ %q({"returnTo":{"\/categories":1}}) => {"returnTo" => {"/categories" => 1}},
10
+ %({"returnTo":[1,"a"]}) => {"returnTo" => [1, "a"]},
11
+ %({"returnTo":[1,"\\"a\\",", "b"]}) => {"returnTo" => [1, "\"a\",", "b"]},
12
+ %({a: "'", "b": "5,000"}) => {"a" => "'", "b" => "5,000"},
13
+ %({a: "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"},
14
+ %({a: "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)},
15
+ %({a: "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)},
16
+ # no time zone
17
+ %({a: "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"},
18
+ %([]) => [],
19
+ %({}) => {},
20
+ %(1) => 1,
21
+ %("") => "",
22
+ %("\\"") => "\"",
23
+ %(null) => nil,
24
+ %(true) => true,
25
+ %(false) => false,
26
+ %q("http:\/\/test.host\/posts\/1") => "http://test.host/posts/1"
27
+ }
28
+
29
+ TESTS.each do |json, expected|
30
+ it "should decode json (#{json})" do
31
+ lambda {
32
+ HTTParty::Parsers::JSON.decode(json).should == expected
33
+ }.should_not raise_error
34
+ end
35
+ end
36
+
37
+ it "should raise error for failed decoding" do
38
+ lambda {
39
+ HTTParty::Parsers::JSON.decode(%({: 1}))
40
+ }.should raise_error(HTTParty::Parsers::JSON::ParseError)
41
+ end
42
+ end