httparty 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,22 @@
1
1
  class HTTParty::CookieHash < Hash #:nodoc:
2
- def add_cookies(hash)
3
- merge!(hash)
2
+
3
+ CLIENT_COOKIES = %w{path expires domain path secure HTTPOnly}
4
+
5
+ def add_cookies(value)
6
+ case value
7
+ when Hash
8
+ merge!(value)
9
+ when String
10
+ value.split('; ').each do |cookie|
11
+ array = cookie.split('=')
12
+ self[array[0].to_sym] = array[1]
13
+ end
14
+ else
15
+ raise "add_cookies only takes a Hash or a String"
16
+ end
4
17
  end
5
18
 
6
19
  def to_cookie_string
7
- collect { |k, v| "#{k}=#{v}" }.join("; ")
20
+ delete_if { |k, v| CLIENT_COOKIES.include?(k.to_s) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
8
21
  end
9
22
  end
@@ -1,7 +1,13 @@
1
- class BlankSlate #:nodoc:
2
- instance_methods.each { |m| undef_method m unless m =~ /^__/ }
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
3
9
  end
4
-
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
@@ -41,28 +60,48 @@ module HTTParty
41
60
  end
42
61
 
43
62
  private
63
+
44
64
  def http
45
65
  http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
46
- http.use_ssl = (uri.port == 443)
47
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
66
+ http.use_ssl = ssl_implied?
67
+
68
+ if options[:timeout] && options[:timeout].is_a?(Integer)
69
+ http.open_timeout = options[:timeout]
70
+ http.read_timeout = options[:timeout]
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
+
48
81
  http
49
82
  end
50
83
 
51
- def configure_basic_auth
52
- @raw_request.basic_auth(options[:basic_auth][:username], options[:basic_auth][:password])
84
+ def ssl_implied?
85
+ uri.port == 443 || uri.instance_of?(URI::HTTPS)
86
+ end
87
+
88
+ def body
89
+ options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
90
+ end
91
+
92
+ def username
93
+ options[:basic_auth][:username]
94
+ end
95
+
96
+ def password
97
+ options[:basic_auth][:password]
53
98
  end
54
99
 
55
100
  def setup_raw_request
56
101
  @raw_request = http_method.new(uri.request_uri)
57
-
58
- if post? && options[:query]
59
- @raw_request.set_form_data(options[:query])
60
- end
61
-
62
- @raw_request.body = options[:body].is_a?(Hash) ? options[:body].to_params : options[:body] unless options[:body].blank?
63
- @raw_request.initialize_http_header options[:headers]
64
-
65
- configure_basic_auth if options[:basic_auth]
102
+ @raw_request.body = body if body
103
+ @raw_request.initialize_http_header(options[:headers])
104
+ @raw_request.basic_auth(username, password) if options[:basic_auth]
66
105
  end
67
106
 
68
107
  def perform_actual_request
@@ -74,21 +113,21 @@ module HTTParty
74
113
  options[:format] ||= format_from_mimetype(response['content-type'])
75
114
  response
76
115
  end
77
-
116
+
78
117
  def query_string(uri)
79
118
  query_string_parts = []
80
- query_string_parts << uri.query unless uri.query.blank?
119
+ query_string_parts << uri.query unless uri.query.nil?
81
120
 
82
121
  if options[:query].is_a?(Hash)
83
122
  query_string_parts << options[:default_params].merge(options[:query]).to_params
84
123
  else
85
- query_string_parts << options[:default_params].to_params unless options[:default_params].blank?
86
- query_string_parts << options[:query] unless options[:query].blank?
124
+ query_string_parts << options[:default_params].to_params unless options[:default_params].nil?
125
+ query_string_parts << options[:query] unless options[:query].nil?
87
126
  end
88
-
127
+
89
128
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
90
129
  end
91
-
130
+
92
131
  # Raises exception Net::XXX (http error code) if an http error occured
93
132
  def handle_response(response)
94
133
  case response
@@ -96,44 +135,47 @@ module HTTParty
96
135
  options[:limit] -= 1
97
136
  self.path = response['location']
98
137
  @redirect = true
138
+ self.http_method = Net::HTTP::Get
139
+ capture_cookies(response)
99
140
  perform
100
141
  else
101
- parsed_response = parse_response(response.body)
102
- 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)
103
143
  end
104
144
  end
105
-
145
+
106
146
  def parse_response(body)
107
- return nil if body.nil? or body.empty?
108
- case format
109
- when :xml
110
- Crack::XML.parse(body)
111
- when :json
112
- Crack::JSON.parse(body)
113
- when :yaml
114
- YAML::load(body)
115
- else
116
- body
117
- end
147
+ parser.call(body, format)
148
+ end
149
+
150
+ def capture_cookies(response)
151
+ return unless response['Set-Cookie']
152
+ cookies_hash = HTTParty::CookieHash.new()
153
+ cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
154
+ cookies_hash.add_cookies(response['Set-Cookie'])
155
+ options[:headers] ||= {}
156
+ options[:headers]['Cookie'] = cookies_hash.to_cookie_string
118
157
  end
119
-
120
- # Uses the HTTP Content-Type header to determine the format of the response
121
- # 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
122
162
  def format_from_mimetype(mimetype)
123
- return nil if mimetype.nil?
124
- 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
125
166
  end
126
-
167
+
127
168
  def validate
128
169
  raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
129
- 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)
130
171
  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
131
172
  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
132
173
  raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
133
174
  end
134
-
175
+
135
176
  def post?
136
177
  Net::HTTP::Post == http_method
137
178
  end
138
179
  end
139
180
  end
181
+
@@ -1,5 +1,5 @@
1
1
  module HTTParty
2
- class Response < BlankSlate #: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.3'
2
+ Version = '0.5.0'
3
3
  end