dkastner-httparty 0.9.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.
- data/.gitignore +10 -0
- data/.travis.yml +8 -0
- data/Gemfile +15 -0
- data/Guardfile +16 -0
- data/History +293 -0
- data/MIT-LICENSE +20 -0
- data/README.md +79 -0
- data/Rakefile +15 -0
- data/bin/httparty +114 -0
- data/cucumber.yml +1 -0
- data/examples/aaws.rb +32 -0
- data/examples/basic.rb +32 -0
- data/examples/crack.rb +19 -0
- data/examples/custom_parsers.rb +67 -0
- data/examples/delicious.rb +37 -0
- data/examples/google.rb +16 -0
- data/examples/headers_and_user_agents.rb +6 -0
- data/examples/nokogiri_html_parser.rb +22 -0
- data/examples/rubyurl.rb +14 -0
- data/examples/tripit_sign_in.rb +33 -0
- data/examples/twitter.rb +31 -0
- data/examples/whoismyrep.rb +10 -0
- data/features/basic_authentication.feature +20 -0
- data/features/command_line.feature +7 -0
- data/features/deals_with_http_error_codes.feature +26 -0
- data/features/digest_authentication.feature +20 -0
- data/features/handles_compressed_responses.feature +19 -0
- data/features/handles_multiple_formats.feature +34 -0
- data/features/steps/env.rb +22 -0
- data/features/steps/httparty_response_steps.rb +26 -0
- data/features/steps/httparty_steps.rb +27 -0
- data/features/steps/mongrel_helper.rb +94 -0
- data/features/steps/remote_service_steps.rb +69 -0
- data/features/supports_redirection.feature +22 -0
- data/features/supports_timeout_option.feature +13 -0
- data/httparty.gemspec +24 -0
- data/lib/httparty.rb +503 -0
- data/lib/httparty/connection_adapter.rb +116 -0
- data/lib/httparty/cookie_hash.rb +22 -0
- data/lib/httparty/core_extensions.rb +32 -0
- data/lib/httparty/exceptions.rb +26 -0
- data/lib/httparty/hash_conversions.rb +51 -0
- data/lib/httparty/module_inheritable_attributes.rb +44 -0
- data/lib/httparty/net_digest_auth.rb +84 -0
- data/lib/httparty/parser.rb +145 -0
- data/lib/httparty/request.rb +243 -0
- data/lib/httparty/response.rb +62 -0
- data/lib/httparty/response/headers.rb +31 -0
- data/lib/httparty/version.rb +3 -0
- data/spec/fixtures/delicious.xml +23 -0
- data/spec/fixtures/empty.xml +0 -0
- data/spec/fixtures/google.html +3 -0
- data/spec/fixtures/ssl/generate.sh +29 -0
- data/spec/fixtures/ssl/generated/1fe462c2.0 +16 -0
- data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
- data/spec/fixtures/ssl/generated/ca.crt +16 -0
- data/spec/fixtures/ssl/generated/ca.key +15 -0
- data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
- data/spec/fixtures/ssl/generated/server.crt +13 -0
- data/spec/fixtures/ssl/generated/server.key +15 -0
- data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
- data/spec/fixtures/twitter.json +1 -0
- data/spec/fixtures/twitter.xml +403 -0
- data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
- data/spec/httparty/connection_adapter_spec.rb +206 -0
- data/spec/httparty/cookie_hash_spec.rb +70 -0
- data/spec/httparty/net_digest_auth_spec.rb +115 -0
- data/spec/httparty/parser_spec.rb +171 -0
- data/spec/httparty/request_spec.rb +507 -0
- data/spec/httparty/response_spec.rb +214 -0
- data/spec/httparty/ssl_spec.rb +62 -0
- data/spec/httparty_spec.rb +703 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/ssl_test_helper.rb +47 -0
- data/spec/support/ssl_test_server.rb +80 -0
- data/spec/support/stub_response.rb +43 -0
- data/website/css/common.css +47 -0
- data/website/index.html +73 -0
- metadata +190 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
module HTTParty
|
2
|
+
# Default connection adapter that returns a new Net::HTTP each time
|
3
|
+
#
|
4
|
+
# == Custom Connection Factories
|
5
|
+
#
|
6
|
+
# If you like to implement your own connection adapter, subclassing
|
7
|
+
# HTTPParty::ConnectionAdapter will make it easier. Just override
|
8
|
+
# the #connection method. The uri and options attributes will have
|
9
|
+
# all the info you need to construct your http connection. Whatever
|
10
|
+
# you return from your connection method needs to adhere to the
|
11
|
+
# Net::HTTP interface as this is what HTTParty expects.
|
12
|
+
#
|
13
|
+
# @example log the uri and options
|
14
|
+
# class LoggingConnectionAdapter < HTTParty::ConnectionAdapter
|
15
|
+
# def connection
|
16
|
+
# puts uri
|
17
|
+
# puts options
|
18
|
+
# Net::HTTP.new(uri)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @example count number of http calls
|
23
|
+
# class CountingConnectionAdapter < HTTParty::ConnectionAdapter
|
24
|
+
# @@count = 0
|
25
|
+
#
|
26
|
+
# self.count
|
27
|
+
# @@count
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def connection
|
31
|
+
# self.count += 1
|
32
|
+
# super
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# === Configuration
|
37
|
+
# There is lots of configuration data available for your connection adapter
|
38
|
+
# in the #options attribute. It is up to you to interpret them within your
|
39
|
+
# connection adapter. Take a look at the implementation of
|
40
|
+
# HTTParty::ConnectionAdapter#connection for examples of how they are used.
|
41
|
+
# Something are probably interesting are as follows:
|
42
|
+
# * :+timeout+: timeout in seconds
|
43
|
+
# * :+debug_output+: see HTTParty::ClassMethods.debug_output.
|
44
|
+
# * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
|
45
|
+
# * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
|
46
|
+
# * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
|
47
|
+
# * :+connection_adapter_options+: contains the hash your passed to HTTParty.connection_adapter when you configured your connection adapter
|
48
|
+
class ConnectionAdapter
|
49
|
+
|
50
|
+
def self.call(uri, options)
|
51
|
+
new(uri, options).connection
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :uri, :options
|
55
|
+
|
56
|
+
def initialize(uri, options={})
|
57
|
+
raise ArgumentError, "uri must be a URI, not a #{uri.class}" unless uri.kind_of? URI
|
58
|
+
|
59
|
+
@uri = uri
|
60
|
+
@options = options
|
61
|
+
end
|
62
|
+
|
63
|
+
def connection
|
64
|
+
http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
|
65
|
+
|
66
|
+
http.use_ssl = ssl_implied?(uri)
|
67
|
+
|
68
|
+
attach_ssl_certificates(http, options)
|
69
|
+
|
70
|
+
if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
|
71
|
+
http.open_timeout = options[:timeout]
|
72
|
+
http.read_timeout = options[:timeout]
|
73
|
+
end
|
74
|
+
|
75
|
+
if options[:debug_output]
|
76
|
+
http.set_debug_output(options[:debug_output])
|
77
|
+
end
|
78
|
+
|
79
|
+
return http
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def ssl_implied?(uri)
|
84
|
+
uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
85
|
+
end
|
86
|
+
|
87
|
+
def attach_ssl_certificates(http, options)
|
88
|
+
if http.use_ssl?
|
89
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
90
|
+
|
91
|
+
# Client certificate authentication
|
92
|
+
if options[:pem]
|
93
|
+
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
94
|
+
http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
|
95
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
96
|
+
end
|
97
|
+
|
98
|
+
# SSL certificate authority file and/or directory
|
99
|
+
if options[:ssl_ca_file]
|
100
|
+
http.ca_file = options[:ssl_ca_file]
|
101
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
102
|
+
end
|
103
|
+
|
104
|
+
if options[:ssl_ca_path]
|
105
|
+
http.ca_path = options[:ssl_ca_path]
|
106
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
107
|
+
end
|
108
|
+
|
109
|
+
# This is only Ruby 1.9+
|
110
|
+
if options[:ssl_version] && http.respond_to?(:ssl_version=)
|
111
|
+
http.ssl_version = options[:ssl_version]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class HTTParty::CookieHash < Hash #:nodoc:
|
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
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_cookie_string
|
20
|
+
delete_if { |k, v| CLIENT_COOKIES.include?(k.to_s) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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
|
+
|
10
|
+
unless defined?(Net::HTTP::Patch)
|
11
|
+
class Net::HTTP
|
12
|
+
def patch(path, data, initheader = nil, dest = nil, &block) #:nodoc:
|
13
|
+
res = nil
|
14
|
+
request(Patch.new(path, initheader), data) {|r|
|
15
|
+
r.read_body dest, &block
|
16
|
+
res = r
|
17
|
+
}
|
18
|
+
unless @newimpl
|
19
|
+
res.value
|
20
|
+
return res, res.body
|
21
|
+
end
|
22
|
+
res
|
23
|
+
end
|
24
|
+
|
25
|
+
class Patch < Net::HTTPRequest
|
26
|
+
METHOD = 'PATCH'
|
27
|
+
REQUEST_HAS_BODY = true
|
28
|
+
RESPONSE_HAS_BODY = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module HTTParty
|
2
|
+
# Exception raised when you attempt to set a non-existant format
|
3
|
+
class UnsupportedFormat < StandardError; end
|
4
|
+
|
5
|
+
# Exception raised when using a URI scheme other than HTTP or HTTPS
|
6
|
+
class UnsupportedURIScheme < StandardError; end
|
7
|
+
|
8
|
+
# @abstract Exceptions which inherit from ResponseError contain the Net::HTTP
|
9
|
+
# response object accessible via the {#response} method.
|
10
|
+
class ResponseError < StandardError
|
11
|
+
# Returns the response of the last request
|
12
|
+
# @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g.
|
13
|
+
# Net::HTTPOK
|
14
|
+
attr_reader :response
|
15
|
+
|
16
|
+
# Instantiate an instance of ResponseError with a Net::HTTPResponse object
|
17
|
+
# @param [Net::HTTPResponse]
|
18
|
+
def initialize(response)
|
19
|
+
@response = response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Exception that is raised when request has redirected too many times.
|
24
|
+
# Calling {#response} returns the Net:HTTP response object.
|
25
|
+
class RedirectionTooDeep < ResponseError; end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module HTTParty
|
2
|
+
module HashConversions
|
3
|
+
# @return <String> This hash as a query string
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# { :name => "Bob",
|
7
|
+
# :address => {
|
8
|
+
# :street => '111 Ruby Ave.',
|
9
|
+
# :city => 'Ruby Central',
|
10
|
+
# :phones => ['111-111-1111', '222-222-2222']
|
11
|
+
# }
|
12
|
+
# }.to_params
|
13
|
+
# #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
|
14
|
+
def self.to_params(hash)
|
15
|
+
params = hash.map { |k,v| normalize_param(k,v) }.join
|
16
|
+
params.chop! # trailing &
|
17
|
+
params
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param key<Object> The key for the param.
|
21
|
+
# @param value<Object> The value for the param.
|
22
|
+
#
|
23
|
+
# @return <String> This key value pair as a param
|
24
|
+
#
|
25
|
+
# @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
|
26
|
+
def self.normalize_param(key, value)
|
27
|
+
param = ''
|
28
|
+
stack = []
|
29
|
+
|
30
|
+
if value.is_a?(Array)
|
31
|
+
param << value.map { |element| normalize_param("#{key}[]", element) }.join
|
32
|
+
elsif value.is_a?(Hash)
|
33
|
+
stack << [key,value]
|
34
|
+
else
|
35
|
+
param << "#{key}=#{URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}&"
|
36
|
+
end
|
37
|
+
|
38
|
+
stack.each do |parent, hash|
|
39
|
+
hash.each do |key, value|
|
40
|
+
if value.is_a?(Hash)
|
41
|
+
stack << ["#{parent}[#{key}]", value]
|
42
|
+
else
|
43
|
+
param << normalize_param("#{parent}[#{key}]", value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
param
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module HTTParty
|
2
|
+
module ModuleInheritableAttributes #:nodoc:
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
# borrowed from Rails 3.2 ActiveSupport
|
8
|
+
def self.hash_deep_dup(h)
|
9
|
+
duplicate = h.dup
|
10
|
+
duplicate.each_pair do |k,v|
|
11
|
+
tv = duplicate[k]
|
12
|
+
duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? hash_deep_dup(tv) : v
|
13
|
+
end
|
14
|
+
duplicate
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods #:nodoc:
|
18
|
+
def mattr_inheritable(*args)
|
19
|
+
@mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
|
20
|
+
@mattr_inheritable_attrs += args
|
21
|
+
args.each do |arg|
|
22
|
+
module_eval %(class << self; attr_accessor :#{arg} end)
|
23
|
+
end
|
24
|
+
@mattr_inheritable_attrs
|
25
|
+
end
|
26
|
+
|
27
|
+
def inherited(subclass)
|
28
|
+
super
|
29
|
+
@mattr_inheritable_attrs.each do |inheritable_attribute|
|
30
|
+
ivar = "@#{inheritable_attribute}"
|
31
|
+
subclass.instance_variable_set(ivar, instance_variable_get(ivar).clone)
|
32
|
+
if instance_variable_get(ivar).respond_to?(:merge)
|
33
|
+
method = <<-EOM
|
34
|
+
def self.#{inheritable_attribute}
|
35
|
+
#{ivar} = superclass.#{inheritable_attribute}.merge ModuleInheritableAttributes.hash_deep_dup(#{ivar})
|
36
|
+
end
|
37
|
+
EOM
|
38
|
+
subclass.class_eval method
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module HTTPHeader
|
6
|
+
def digest_auth(username, password, response)
|
7
|
+
@header['Authorization'] = DigestAuthenticator.new(username, password,
|
8
|
+
@method, @path, response).authorization_header
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class DigestAuthenticator
|
13
|
+
def initialize(username, password, method, path, response_header)
|
14
|
+
@username = username
|
15
|
+
@password = password
|
16
|
+
@method = method
|
17
|
+
@path = path
|
18
|
+
@response = parse(response_header)
|
19
|
+
end
|
20
|
+
|
21
|
+
def authorization_header
|
22
|
+
@cnonce = md5(random)
|
23
|
+
header = [
|
24
|
+
%Q(Digest username="#{@username}"),
|
25
|
+
%Q(realm="#{@response['realm']}"),
|
26
|
+
%Q(nonce="#{@response['nonce']}"),
|
27
|
+
%Q(uri="#{@path}"),
|
28
|
+
%Q(response="#{request_digest}"),
|
29
|
+
]
|
30
|
+
|
31
|
+
if qop_present?
|
32
|
+
fields = [
|
33
|
+
%Q(cnonce="#{@cnonce}"),
|
34
|
+
%Q(qop="#{@response['qop']}"),
|
35
|
+
%Q(nc="00000001")
|
36
|
+
]
|
37
|
+
fields.each { |field| header << field }
|
38
|
+
end
|
39
|
+
|
40
|
+
header << %Q(opaque="#{@response['opaque']}") if opaque_present?
|
41
|
+
header
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def parse(response_header)
|
47
|
+
response_header['www-authenticate'] =~ /^(\w+) (.*)/
|
48
|
+
params = {}
|
49
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
50
|
+
params
|
51
|
+
end
|
52
|
+
|
53
|
+
def opaque_present?
|
54
|
+
@response.has_key?('opaque') and not @response['opaque'].empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
def qop_present?
|
58
|
+
@response.has_key?('qop') and not @response['qop'].empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def random
|
62
|
+
"%x" % (Time.now.to_i + rand(65535))
|
63
|
+
end
|
64
|
+
|
65
|
+
def request_digest
|
66
|
+
a = [md5(a1), @response['nonce'], md5(a2)]
|
67
|
+
a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
|
68
|
+
md5(a.join(":"))
|
69
|
+
end
|
70
|
+
|
71
|
+
def md5(str)
|
72
|
+
Digest::MD5.hexdigest(str)
|
73
|
+
end
|
74
|
+
|
75
|
+
def a1
|
76
|
+
[@username, @response['realm'], @password].join(":")
|
77
|
+
end
|
78
|
+
|
79
|
+
def a2
|
80
|
+
[@method, @path].join(":")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,145 @@
|
|
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
|
+
|
97
|
+
# @return [Object] the parsed body
|
98
|
+
# @return [nil] when the response body is nil, an empty string, spaces only or "null"
|
99
|
+
def parse
|
100
|
+
return nil if body.nil? || body.strip.empty? || body == "null"
|
101
|
+
if supports_format?
|
102
|
+
parse_supported_format
|
103
|
+
else
|
104
|
+
body
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
protected
|
109
|
+
|
110
|
+
def xml
|
111
|
+
MultiXml.parse(body)
|
112
|
+
end
|
113
|
+
|
114
|
+
def json
|
115
|
+
# https://github.com/sferik/rails/commit/5e62670131dfa1718eaf21ff8dd3371395a5f1cc
|
116
|
+
if MultiJson.respond_to?(:adapter)
|
117
|
+
MultiJson.load(body)
|
118
|
+
else
|
119
|
+
MultiJson.decode(body)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def yaml
|
124
|
+
YAML.load(body)
|
125
|
+
end
|
126
|
+
|
127
|
+
def html
|
128
|
+
body
|
129
|
+
end
|
130
|
+
|
131
|
+
def plain
|
132
|
+
body
|
133
|
+
end
|
134
|
+
|
135
|
+
def supports_format?
|
136
|
+
self.class.supports_format?(format)
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse_supported_format
|
140
|
+
send(format)
|
141
|
+
rescue NoMethodError => e
|
142
|
+
raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|