httparty 0.4.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of httparty might be problematic. Click here for more details.

@@ -1,7 +1,13 @@
1
- class BasicObject #:nodoc:
2
- instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
3
- end unless defined?(BasicObject)
4
-
1
+ module HTTParty
2
+ if defined?(::BasicObject)
3
+ BasicObject = ::BasicObject #:nodoc:
4
+ else
5
+ class BasicObject #:nodoc:
6
+ instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
7
+ end
8
+ end
9
+ end
10
+
5
11
  # 1.8.6 has mistyping of transitive in if statement
6
12
  require "rexml/document"
7
13
  module REXML #:nodoc:
@@ -1,7 +1,10 @@
1
1
  module HTTParty
2
2
  # Exception raised when you attempt to set a non-existant format
3
3
  class UnsupportedFormat < StandardError; end
4
-
4
+
5
+ # Exception raised when using a URI scheme other than HTTP or HTTPS
6
+ class UnsupportedURIScheme < StandardError; end
7
+
5
8
  # Exception that is raised when request has redirected too many times
6
9
  class RedirectionTooDeep < StandardError; end
7
- end
10
+ end
@@ -16,10 +16,10 @@ module HTTParty
16
16
 
17
17
  def inherited(subclass)
18
18
  @mattr_inheritable_attrs.each do |inheritable_attribute|
19
- instance_var = "@#{inheritable_attribute}"
20
- subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
19
+ instance_var = "@#{inheritable_attribute}"
20
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var).clone)
21
21
  end
22
22
  end
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -0,0 +1,140 @@
1
+ module HTTParty
2
+ # The default parser used by HTTParty, supports xml, json, html, yaml, and
3
+ # plain text.
4
+ #
5
+ # == Custom Parsers
6
+ #
7
+ # If you'd like to do your own custom parsing, subclassing HTTParty::Parser
8
+ # will make that process much easier. There are a few different ways you can
9
+ # utilize HTTParty::Parser as a superclass.
10
+ #
11
+ # @example Intercept the parsing for all formats
12
+ # class SimpleParser < HTTParty::Parser
13
+ # def parse
14
+ # perform_parsing
15
+ # end
16
+ # end
17
+ #
18
+ # @example Add the atom format and parsing method to the default parser
19
+ # class AtomParsingIncluded < HTTParty::Parser
20
+ # SupportedFormats.merge!(
21
+ # {"application/atom+xml" => :atom}
22
+ # )
23
+ #
24
+ # def atom
25
+ # perform_atom_parsing
26
+ # end
27
+ # end
28
+ #
29
+ # @example Only support the atom format
30
+ # class ParseOnlyAtom < HTTParty::Parser
31
+ # SupportedFormats = {"application/atom+xml" => :atom}
32
+ #
33
+ # def atom
34
+ # perform_atom_parsing
35
+ # end
36
+ # end
37
+ #
38
+ # @abstract Read the Custom Parsers section for more information.
39
+ class Parser
40
+ SupportedFormats = {
41
+ 'text/xml' => :xml,
42
+ 'application/xml' => :xml,
43
+ 'application/json' => :json,
44
+ 'text/json' => :json,
45
+ 'application/javascript' => :json,
46
+ 'text/javascript' => :json,
47
+ 'text/html' => :html,
48
+ 'application/x-yaml' => :yaml,
49
+ 'text/yaml' => :yaml,
50
+ 'text/plain' => :plain
51
+ }
52
+
53
+ # The response body of the request
54
+ # @return [String]
55
+ attr_reader :body
56
+
57
+ # The intended parsing format for the request
58
+ # @return [Symbol] e.g. :json
59
+ attr_reader :format
60
+
61
+ # @param [String] body the response body
62
+ # @param [Symbol] format the response format
63
+ # @return parsed response
64
+ def self.call(body, format)
65
+ new(body, format).parse
66
+ end
67
+
68
+ # @return [Hash] the SupportedFormats hash
69
+ def self.formats
70
+ const_get(:SupportedFormats)
71
+ end
72
+
73
+ # @param [String] mimetype response MIME type
74
+ # @return [Symbol]
75
+ # @return [nil] mime type not supported
76
+ def self.format_from_mimetype(mimetype)
77
+ formats[formats.keys.detect {|k| mimetype.include?(k)}]
78
+ end
79
+
80
+ # @return [Array<Symbol>] list of supported formats
81
+ def self.supported_formats
82
+ formats.values.uniq
83
+ end
84
+
85
+ # @param [Symbol] format e.g. :json, :xml
86
+ # @return [Boolean]
87
+ def self.supports_format?(format)
88
+ supported_formats.include?(format)
89
+ end
90
+
91
+ def initialize(body, format)
92
+ @body = body
93
+ @format = format
94
+ end
95
+ private_class_method :new
96
+
97
+ # @return [Object] the parsed body
98
+ # @return [nil] when the response body is nil or an empty string
99
+ def parse
100
+ return nil if body.nil? || body.empty?
101
+ if supports_format?
102
+ parse_supported_format
103
+ else
104
+ body
105
+ end
106
+ end
107
+
108
+ protected
109
+
110
+ def xml
111
+ Crack::XML.parse(body)
112
+ end
113
+
114
+ def json
115
+ Crack::JSON.parse(body)
116
+ end
117
+
118
+ def yaml
119
+ YAML.load(body)
120
+ end
121
+
122
+ def html
123
+ body
124
+ end
125
+
126
+ def plain
127
+ body
128
+ end
129
+
130
+ def supports_format?
131
+ self.class.supports_format?(format)
132
+ end
133
+
134
+ def parse_supported_format
135
+ send(format)
136
+ rescue NoMethodError
137
+ raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
138
+ end
139
+ end
140
+ end
@@ -2,38 +2,57 @@ require 'uri'
2
2
 
3
3
  module HTTParty
4
4
  class Request #:nodoc:
5
- SupportedHTTPMethods = [Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Put, Net::HTTP::Delete]
6
-
5
+ SupportedHTTPMethods = [
6
+ Net::HTTP::Get,
7
+ Net::HTTP::Post,
8
+ Net::HTTP::Put,
9
+ Net::HTTP::Delete,
10
+ Net::HTTP::Head,
11
+ Net::HTTP::Options
12
+ ]
13
+
14
+ SupportedURISchemes = [URI::HTTP, URI::HTTPS]
15
+
7
16
  attr_accessor :http_method, :path, :options
8
-
17
+
9
18
  def initialize(http_method, path, o={})
10
19
  self.http_method = http_method
11
20
  self.path = path
12
21
  self.options = {
13
- :limit => o.delete(:no_follow) ? 0 : 5,
22
+ :limit => o.delete(:no_follow) ? 0 : 5,
14
23
  :default_params => {},
24
+ :parser => Parser
15
25
  }.merge(o)
16
26
  end
17
27
 
18
28
  def path=(uri)
19
29
  @path = URI.parse(uri)
20
30
  end
21
-
31
+
22
32
  def uri
23
33
  new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
24
-
34
+
25
35
  # avoid double query string on redirects [#12]
26
36
  unless @redirect
27
37
  new_uri.query = query_string(new_uri)
28
38
  end
29
-
39
+
40
+ unless SupportedURISchemes.include? new_uri.class
41
+ raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP or HTTPS"
42
+ end
43
+
30
44
  new_uri
31
45
  end
32
-
46
+
33
47
  def format
34
48
  options[:format]
35
49
  end
36
-
50
+
51
+ def parser
52
+ options[:parser]
53
+ end
54
+
55
+
37
56
  def perform
38
57
  validate
39
58
  setup_raw_request
@@ -44,23 +63,36 @@ module HTTParty
44
63
 
45
64
  def http
46
65
  http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
47
- http.use_ssl = (uri.port == 443)
48
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
66
+ http.use_ssl = ssl_implied?
67
+
49
68
  if options[:timeout] && options[:timeout].is_a?(Integer)
50
69
  http.open_timeout = options[:timeout]
51
70
  http.read_timeout = options[:timeout]
52
71
  end
72
+
73
+ if options[:pem] && http.use_ssl?
74
+ http.cert = OpenSSL::X509::Certificate.new(options[:pem])
75
+ http.key = OpenSSL::PKey::RSA.new(options[:pem])
76
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
77
+ else
78
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
79
+ end
80
+
53
81
  http
54
82
  end
55
83
 
84
+ def ssl_implied?
85
+ uri.port == 443 || uri.instance_of?(URI::HTTPS)
86
+ end
87
+
56
88
  def body
57
89
  options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
58
90
  end
59
-
91
+
60
92
  def username
61
93
  options[:basic_auth][:username]
62
94
  end
63
-
95
+
64
96
  def password
65
97
  options[:basic_auth][:password]
66
98
  end
@@ -81,7 +113,7 @@ module HTTParty
81
113
  options[:format] ||= format_from_mimetype(response['content-type'])
82
114
  response
83
115
  end
84
-
116
+
85
117
  def query_string(uri)
86
118
  query_string_parts = []
87
119
  query_string_parts << uri.query unless uri.query.nil?
@@ -92,10 +124,10 @@ module HTTParty
92
124
  query_string_parts << options[:default_params].to_params unless options[:default_params].nil?
93
125
  query_string_parts << options[:query] unless options[:query].nil?
94
126
  end
95
-
127
+
96
128
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
97
129
  end
98
-
130
+
99
131
  # Raises exception Net::XXX (http error code) if an http error occured
100
132
  def handle_response(response)
101
133
  case response
@@ -107,43 +139,14 @@ module HTTParty
107
139
  capture_cookies(response)
108
140
  perform
109
141
  else
110
- parsed_response = parse_response(response.body)
111
- Response.new(parsed_response, response.body, response.code, response.message, response.to_hash)
142
+ Response.new(parse_response(response.body), response.body, response.code, response.message, response.to_hash)
112
143
  end
113
144
  end
114
-
115
- # HTTParty.const_get((self.format.to_s || 'text').capitalize)
145
+
116
146
  def parse_response(body)
117
- return nil if body.nil? or body.empty?
118
- if options[:parser].blank?
119
- case format
120
- when :xml
121
- Crack::XML.parse(body)
122
- when :json
123
- Crack::JSON.parse(body)
124
- when :yaml
125
- YAML::load(body)
126
- else
127
- body
128
- end
129
- else
130
- if options[:parser].is_a?(Proc)
131
- options[:parser].call(body)
132
- else
133
- body
134
- end
135
- end
136
- end
137
-
138
- def capture_cookies(response)
139
- return unless response['Set-Cookie']
140
- cookies_hash = HTTParty::CookieHash.new()
141
- cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
142
- cookies_hash.add_cookies(response['Set-Cookie'])
143
- options[:headers] ||= {}
144
- options[:headers]['Cookie'] = cookies_hash.to_cookie_string
147
+ parser.call(body, format)
145
148
  end
146
-
149
+
147
150
  def capture_cookies(response)
148
151
  return unless response['Set-Cookie']
149
152
  cookies_hash = HTTParty::CookieHash.new()
@@ -152,24 +155,27 @@ module HTTParty
152
155
  options[:headers] ||= {}
153
156
  options[:headers]['Cookie'] = cookies_hash.to_cookie_string
154
157
  end
155
-
156
- # Uses the HTTP Content-Type header to determine the format of the response
157
- # It compares the MIME type returned to the types stored in the AllowedFormats hash
158
+
159
+ # Uses the HTTP Content-Type header to determine the format of the
160
+ # response It compares the MIME type returned to the types stored in the
161
+ # SupportedFormats hash
158
162
  def format_from_mimetype(mimetype)
159
- return nil if mimetype.nil?
160
- AllowedFormats.each { |k, v| return v if mimetype.include?(k) }
163
+ if mimetype && parser.respond_to?(:format_from_mimetype)
164
+ parser.format_from_mimetype(mimetype)
165
+ end
161
166
  end
162
-
167
+
163
168
  def validate
164
169
  raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
165
- raise ArgumentError, 'only get, post, put and delete methods are supported' unless SupportedHTTPMethods.include?(http_method)
170
+ raise ArgumentError, 'only get, post, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
166
171
  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
167
172
  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
168
173
  raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
169
174
  end
170
-
175
+
171
176
  def post?
172
177
  Net::HTTP::Post == http_method
173
178
  end
174
179
  end
175
180
  end
181
+
@@ -1,5 +1,5 @@
1
1
  module HTTParty
2
- class Response < BasicObject #:nodoc:
2
+ class Response < HTTParty::BasicObject #:nodoc:
3
3
  attr_accessor :body, :code, :message, :headers
4
4
  attr_reader :delegate
5
5
 
@@ -1,3 +1,3 @@
1
1
  module HTTParty #:nodoc:
2
- Version = '0.4.4'
2
+ Version = '0.5.0'
3
3
  end
@@ -0,0 +1,154 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ class CustomParser < HTTParty::Parser; end
4
+
5
+ describe HTTParty::Parser do
6
+ describe ".SupportedFormats" do
7
+ it "returns a hash" do
8
+ HTTParty::Parser::SupportedFormats.should be_instance_of(Hash)
9
+ end
10
+ end
11
+
12
+ describe ".call" do
13
+ it "generates an HTTParty::Parser instance with the given body and format" do
14
+ HTTParty::Parser.should_receive(:new).with('body', :plain).and_return(stub(:parse => nil))
15
+ HTTParty::Parser.call('body', :plain)
16
+ end
17
+
18
+ it "calls #parse on the parser" do
19
+ parser = mock('Parser')
20
+ parser.should_receive(:parse)
21
+ HTTParty::Parser.stub(:new => parser)
22
+ parser = HTTParty::Parser.call('body', :plain)
23
+ end
24
+ end
25
+
26
+ describe ".formats" do
27
+ it "returns the SupportedFormats constant" do
28
+ HTTParty::Parser.formats.should == HTTParty::Parser::SupportedFormats
29
+ end
30
+
31
+ it "returns the SupportedFormats constant for subclasses" do
32
+ class MyParser < HTTParty::Parser
33
+ SupportedFormats = {"application/atom+xml" => :atom}
34
+ end
35
+ MyParser.formats.should == {"application/atom+xml" => :atom}
36
+ end
37
+ end
38
+
39
+ describe ".format_from_mimetype" do
40
+ it "returns a symbol representing the format mimetype" do
41
+ HTTParty::Parser.format_from_mimetype("text/plain").should == :plain
42
+ end
43
+
44
+ it "returns nil when the mimetype is not supported" do
45
+ HTTParty::Parser.format_from_mimetype("application/atom+xml").should be_nil
46
+ end
47
+ end
48
+
49
+ describe ".supported_formats" do
50
+ it "returns a unique set of supported formats represented by symbols" do
51
+ HTTParty::Parser.supported_formats.should == HTTParty::Parser::SupportedFormats.values.uniq
52
+ end
53
+ end
54
+
55
+ describe ".supports_format?" do
56
+ it "returns true for a supported format" do
57
+ HTTParty::Parser.stub(:supported_formats => [:json])
58
+ HTTParty::Parser.supports_format?(:json).should be_true
59
+ end
60
+
61
+ it "returns false for an unsupported format" do
62
+ HTTParty::Parser.stub(:supported_formats => [])
63
+ HTTParty::Parser.supports_format?(:json).should be_false
64
+ end
65
+ end
66
+
67
+ describe "#parse" do
68
+ before do
69
+ @parser = HTTParty::Parser.new('body', :json)
70
+ end
71
+
72
+ it "attempts to parse supported formats" do
73
+ @parser.stub(:supports_format? => true)
74
+ @parser.should_receive(:parse_supported_format)
75
+ @parser.parse
76
+ end
77
+
78
+ it "returns the unparsed body when the format is unsupported" do
79
+ @parser.stub(:supports_format? => false)
80
+ @parser.parse.should == @parser.body
81
+ end
82
+
83
+ it "returns nil for an empty body" do
84
+ @parser.stub(:body => '')
85
+ @parser.parse.should be_nil
86
+ end
87
+
88
+ it "returns nil for a nil body" do
89
+ @parser.stub(:body => nil)
90
+ @parser.parse.should be_nil
91
+ end
92
+ end
93
+
94
+ describe "#supports_format?" do
95
+ it "utilizes the class method to determine if the format is supported" do
96
+ HTTParty::Parser.should_receive(:supports_format?).with(:json)
97
+ parser = HTTParty::Parser.new('body', :json)
98
+ parser.send(:supports_format?)
99
+ end
100
+ end
101
+
102
+ describe "#parse_supported_format" do
103
+ it "calls the parser for the given format" do
104
+ parser = HTTParty::Parser.new('body', :json)
105
+ parser.should_receive(:json)
106
+ parser.send(:parse_supported_format)
107
+ end
108
+
109
+ context "when a parsing method does not exist for the given format" do
110
+ it "raises an exception" do
111
+ parser = HTTParty::Parser.new('body', :atom)
112
+ expect do
113
+ parser.send(:parse_supported_format)
114
+ end.to raise_error(NotImplementedError, "HTTParty::Parser has not implemented a parsing method for the :atom format.")
115
+ end
116
+
117
+ it "raises a useful exception message for subclasses" do
118
+ parser = CustomParser.new('body', :atom)
119
+ expect do
120
+ parser.send(:parse_supported_format)
121
+ end.to raise_error(NotImplementedError, "CustomParser has not implemented a parsing method for the :atom format.")
122
+ end
123
+ end
124
+ end
125
+
126
+ context "parsers" do
127
+ subject do
128
+ HTTParty::Parser.new('body', nil)
129
+ end
130
+
131
+ it "parses xml with Crack" do
132
+ Crack::XML.should_receive(:parse).with('body')
133
+ subject.send(:xml)
134
+ end
135
+
136
+ it "parses json with Crack" do
137
+ Crack::JSON.should_receive(:parse).with('body')
138
+ subject.send(:json)
139
+ end
140
+
141
+ it "parses yaml" do
142
+ YAML.should_receive(:load).with('body')
143
+ subject.send(:yaml)
144
+ end
145
+
146
+ it "parses html by simply returning the body" do
147
+ subject.send(:html).should == 'body'
148
+ end
149
+
150
+ it "parses plain text by simply returning the body" do
151
+ subject.send(:plain).should == 'body'
152
+ end
153
+ end
154
+ end