jugend-httparty 0.5.2.1

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.
Files changed (54) hide show
  1. data/.gitignore +7 -0
  2. data/History +209 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest +47 -0
  5. data/README.rdoc +54 -0
  6. data/Rakefile +80 -0
  7. data/VERSION +1 -0
  8. data/bin/httparty +108 -0
  9. data/cucumber.yml +1 -0
  10. data/examples/aaws.rb +32 -0
  11. data/examples/basic.rb +11 -0
  12. data/examples/custom_parsers.rb +67 -0
  13. data/examples/delicious.rb +37 -0
  14. data/examples/google.rb +16 -0
  15. data/examples/rubyurl.rb +14 -0
  16. data/examples/twitter.rb +31 -0
  17. data/examples/whoismyrep.rb +10 -0
  18. data/features/basic_authentication.feature +20 -0
  19. data/features/command_line.feature +7 -0
  20. data/features/deals_with_http_error_codes.feature +26 -0
  21. data/features/handles_multiple_formats.feature +34 -0
  22. data/features/steps/env.rb +23 -0
  23. data/features/steps/httparty_response_steps.rb +26 -0
  24. data/features/steps/httparty_steps.rb +27 -0
  25. data/features/steps/mongrel_helper.rb +78 -0
  26. data/features/steps/remote_service_steps.rb +61 -0
  27. data/features/supports_redirection.feature +22 -0
  28. data/features/supports_timeout_option.feature +13 -0
  29. data/jugend-httparty.gemspec +127 -0
  30. data/lib/httparty/cookie_hash.rb +22 -0
  31. data/lib/httparty/core_extensions.rb +31 -0
  32. data/lib/httparty/exceptions.rb +26 -0
  33. data/lib/httparty/module_inheritable_attributes.rb +25 -0
  34. data/lib/httparty/parser.rb +141 -0
  35. data/lib/httparty/request.rb +206 -0
  36. data/lib/httparty/response.rb +62 -0
  37. data/lib/httparty.rb +343 -0
  38. data/spec/fixtures/delicious.xml +23 -0
  39. data/spec/fixtures/empty.xml +0 -0
  40. data/spec/fixtures/google.html +3 -0
  41. data/spec/fixtures/twitter.json +1 -0
  42. data/spec/fixtures/twitter.xml +403 -0
  43. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  44. data/spec/httparty/cookie_hash_spec.rb +71 -0
  45. data/spec/httparty/parser_spec.rb +154 -0
  46. data/spec/httparty/request_spec.rb +415 -0
  47. data/spec/httparty/response_spec.rb +83 -0
  48. data/spec/httparty_spec.rb +514 -0
  49. data/spec/spec.opts +3 -0
  50. data/spec/spec_helper.rb +19 -0
  51. data/spec/support/stub_response.rb +30 -0
  52. data/website/css/common.css +47 -0
  53. data/website/index.html +73 -0
  54. metadata +209 -0
@@ -0,0 +1,141 @@
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
+ # Instantiate the parser and call {#parse}.
62
+ # @param [String] body the response body
63
+ # @param [Symbol] format the response format
64
+ # @return parsed response
65
+ def self.call(body, format)
66
+ new(body, format).parse
67
+ end
68
+
69
+ # @return [Hash] the SupportedFormats hash
70
+ def self.formats
71
+ const_get(:SupportedFormats)
72
+ end
73
+
74
+ # @param [String] mimetype response MIME type
75
+ # @return [Symbol]
76
+ # @return [nil] mime type not supported
77
+ def self.format_from_mimetype(mimetype)
78
+ formats[formats.keys.detect {|k| mimetype.include?(k)}]
79
+ end
80
+
81
+ # @return [Array<Symbol>] list of supported formats
82
+ def self.supported_formats
83
+ formats.values.uniq
84
+ end
85
+
86
+ # @param [Symbol] format e.g. :json, :xml
87
+ # @return [Boolean]
88
+ def self.supports_format?(format)
89
+ supported_formats.include?(format)
90
+ end
91
+
92
+ def initialize(body, format)
93
+ @body = body
94
+ @format = format
95
+ end
96
+ private_class_method :new
97
+
98
+ # @return [Object] the parsed body
99
+ # @return [nil] when the response body is nil or an empty string
100
+ def parse
101
+ return nil if body.nil? || body.empty?
102
+ if supports_format?
103
+ parse_supported_format
104
+ else
105
+ body
106
+ end
107
+ end
108
+
109
+ protected
110
+
111
+ def xml
112
+ Crack::XML.parse(body)
113
+ end
114
+
115
+ def json
116
+ Crack::JSON.parse(body)
117
+ end
118
+
119
+ def yaml
120
+ YAML.load(body)
121
+ end
122
+
123
+ def html
124
+ body
125
+ end
126
+
127
+ def plain
128
+ body
129
+ end
130
+
131
+ def supports_format?
132
+ self.class.supports_format?(format)
133
+ end
134
+
135
+ def parse_supported_format
136
+ send(format)
137
+ rescue NoMethodError
138
+ raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,206 @@
1
+ require 'uri'
2
+
3
+ module HTTParty
4
+ class Request #:nodoc:
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
+
16
+ attr_accessor :http_method, :path, :options, :last_response, :redirect
17
+
18
+ def initialize(http_method, path, o={})
19
+ self.http_method = http_method
20
+ self.path = path
21
+ self.options = {
22
+ :limit => o.delete(:no_follow) ? 1 : 5,
23
+ :default_params => {},
24
+ :parser => Parser
25
+ }.merge(o)
26
+ end
27
+
28
+ def path=(uri)
29
+ @path = URI.parse(uri)
30
+ end
31
+
32
+ def uri
33
+ new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
34
+
35
+ # avoid double query string on redirects [#12]
36
+ unless redirect
37
+ new_uri.query = query_string(new_uri)
38
+ end
39
+
40
+ unless SupportedURISchemes.include? new_uri.class
41
+ raise UnsupportedURIScheme, "'#{new_uri}' Must be HTTP or HTTPS"
42
+ end
43
+
44
+ new_uri
45
+ end
46
+
47
+ def format
48
+ options[:format]
49
+ end
50
+
51
+ def parser
52
+ options[:parser]
53
+ end
54
+
55
+ def perform
56
+ validate
57
+ setup_raw_request
58
+ get_response
59
+ handle_response
60
+ end
61
+
62
+ private
63
+
64
+ def http
65
+ http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
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
+
81
+ if options[:debug_output]
82
+ http.set_debug_output(options[:debug_output])
83
+ end
84
+
85
+ http
86
+ end
87
+
88
+ def ssl_implied?
89
+ uri.port == 443 || uri.instance_of?(URI::HTTPS)
90
+ end
91
+
92
+ def body
93
+ options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
94
+ end
95
+
96
+ def credentials
97
+ options[:basic_auth] || options[:digest_auth]
98
+ end
99
+
100
+ def username
101
+ credentials[:username]
102
+ end
103
+
104
+ def password
105
+ credentials[:password]
106
+ end
107
+
108
+ def setup_raw_request
109
+ @raw_request = http_method.new(uri.request_uri)
110
+ @raw_request.body = body if body
111
+ @raw_request.initialize_http_header(options[:headers])
112
+ @raw_request.basic_auth(username, password) if options[:basic_auth]
113
+ setup_digest_auth if options[:digest_auth]
114
+ end
115
+
116
+ def setup_digest_auth
117
+ res = http.head(uri.request_uri, options[:headers])
118
+ if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
119
+ @raw_request.digest_auth(username, password, res)
120
+ end
121
+ end
122
+
123
+ def perform_actual_request
124
+ http.request(@raw_request)
125
+ end
126
+
127
+ def get_response
128
+ self.last_response = perform_actual_request
129
+ options[:format] ||= format_from_mimetype(last_response['content-type'])
130
+ end
131
+
132
+ def query_string(uri)
133
+ query_string_parts = []
134
+ query_string_parts << uri.query unless uri.query.nil?
135
+
136
+ if options[:query].is_a?(Hash)
137
+ query_string_parts << options[:default_params].merge(options[:query]).to_params
138
+ else
139
+ query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
140
+ query_string_parts << options[:query] unless options[:query].nil?
141
+ end
142
+
143
+ query_string_parts.size > 0 ? query_string_parts.join('&') : nil
144
+ end
145
+
146
+ # Raises exception Net::XXX (http error code) if an http error occured
147
+ def handle_response
148
+ case last_response
149
+ when Net::HTTPMultipleChoice, # 300
150
+ Net::HTTPMovedPermanently, # 301
151
+ Net::HTTPFound, # 302
152
+ Net::HTTPSeeOther, # 303
153
+ Net::HTTPUseProxy, # 305
154
+ Net::HTTPTemporaryRedirect
155
+ if last_response.key?('location')
156
+ options[:limit] -= 1
157
+ self.path = last_response['location']
158
+ self.redirect = true
159
+ self.http_method = Net::HTTP::Get unless options[:maintain_method_across_redirects]
160
+ capture_cookies(last_response)
161
+ perform
162
+ else
163
+ last_response
164
+ end
165
+ else
166
+ Response.new(last_response, parse_response(last_response.body))
167
+ end
168
+ end
169
+
170
+ def parse_response(body)
171
+ parser.call(body, format)
172
+ end
173
+
174
+ def capture_cookies(response)
175
+ return unless response['Set-Cookie']
176
+ cookies_hash = HTTParty::CookieHash.new()
177
+ cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
178
+ cookies_hash.add_cookies(response['Set-Cookie'])
179
+ options[:headers] ||= {}
180
+ options[:headers]['Cookie'] = cookies_hash.to_cookie_string
181
+ end
182
+
183
+ # Uses the HTTP Content-Type header to determine the format of the
184
+ # response It compares the MIME type returned to the types stored in the
185
+ # SupportedFormats hash
186
+ def format_from_mimetype(mimetype)
187
+ if mimetype && parser.respond_to?(:format_from_mimetype)
188
+ parser.format_from_mimetype(mimetype)
189
+ end
190
+ end
191
+
192
+ def validate
193
+ raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
194
+ raise ArgumentError, 'only get, post, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
195
+ raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
196
+ raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
197
+ raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
198
+ raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].is_a?(Hash)
199
+ raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
200
+ end
201
+
202
+ def post?
203
+ Net::HTTP::Post == http_method
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,62 @@
1
+ module HTTParty
2
+ class Response < HTTParty::BasicObject #:nodoc:
3
+ class Headers
4
+ include Net::HTTPHeader
5
+
6
+ def initialize(header)
7
+ @header = header
8
+ end
9
+
10
+ def ==(other)
11
+ @header == other
12
+ end
13
+
14
+ def inspect
15
+ @header.inspect
16
+ end
17
+
18
+ def method_missing(name, *args, &block)
19
+ if @header.respond_to?(name)
20
+ @header.send(name, *args, &block)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def respond_to?(method)
27
+ super || @header.respond_to?(method)
28
+ end
29
+ end
30
+
31
+ attr_reader :response, :parsed_response, :body, :headers
32
+
33
+ def initialize(response, parsed_response)
34
+ @response = response
35
+ @body = response.body
36
+ @parsed_response = parsed_response
37
+ @headers = Headers.new(response.to_hash)
38
+ end
39
+
40
+ def class
41
+ Object.instance_method(:class).bind(self).call
42
+ end
43
+
44
+ def code
45
+ response.code.to_i
46
+ end
47
+
48
+ def inspect
49
+ %(<#{self.class} @response=#{response.inspect}>)
50
+ end
51
+
52
+ def method_missing(name, *args, &block)
53
+ if parsed_response.respond_to?(name)
54
+ parsed_response.send(name, *args, &block)
55
+ elsif response.respond_to?(name)
56
+ response.send(name, *args, &block)
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end