tictoc-savon 0.7.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +5 -0
- data/CHANGELOG +176 -0
- data/LICENSE +20 -0
- data/README.rdoc +64 -0
- data/Rakefile +50 -0
- data/lib/savon.rb +35 -0
- data/lib/savon/client.rb +131 -0
- data/lib/savon/core_ext.rb +8 -0
- data/lib/savon/core_ext/array.rb +31 -0
- data/lib/savon/core_ext/datetime.rb +10 -0
- data/lib/savon/core_ext/hash.rb +107 -0
- data/lib/savon/core_ext/net_http.rb +19 -0
- data/lib/savon/core_ext/object.rb +16 -0
- data/lib/savon/core_ext/string.rb +69 -0
- data/lib/savon/core_ext/symbol.rb +8 -0
- data/lib/savon/core_ext/uri.rb +10 -0
- data/lib/savon/logger.rb +56 -0
- data/lib/savon/request.rb +138 -0
- data/lib/savon/response.rb +174 -0
- data/lib/savon/soap.rb +302 -0
- data/lib/savon/version.rb +5 -0
- data/lib/savon/wsdl.rb +137 -0
- data/lib/savon/wsdl_stream.rb +85 -0
- data/lib/savon/wsse.rb +163 -0
- data/spec/basic_spec_helper.rb +11 -0
- data/spec/endpoint_helper.rb +23 -0
- data/spec/fixtures/gzip/gzip_response_fixture.rb +7 -0
- data/spec/fixtures/gzip/message.gz +0 -0
- data/spec/fixtures/response/response_fixture.rb +36 -0
- data/spec/fixtures/response/xml/authentication.xml +14 -0
- data/spec/fixtures/response/xml/multi_ref.xml +39 -0
- data/spec/fixtures/response/xml/soap_fault.xml +8 -0
- data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
- data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
- data/spec/fixtures/wsdl/wsdl_fixture.yml +42 -0
- data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
- data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
- data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
- data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
- data/spec/http_stubs.rb +26 -0
- data/spec/integration/http_basic_auth_spec.rb +16 -0
- data/spec/integration/server.rb +51 -0
- data/spec/savon/client_spec.rb +86 -0
- data/spec/savon/core_ext/array_spec.rb +49 -0
- data/spec/savon/core_ext/datetime_spec.rb +21 -0
- data/spec/savon/core_ext/hash_spec.rb +190 -0
- data/spec/savon/core_ext/net_http_spec.rb +38 -0
- data/spec/savon/core_ext/object_spec.rb +34 -0
- data/spec/savon/core_ext/string_spec.rb +99 -0
- data/spec/savon/core_ext/symbol_spec.rb +12 -0
- data/spec/savon/core_ext/uri_spec.rb +19 -0
- data/spec/savon/request_spec.rb +117 -0
- data/spec/savon/response_spec.rb +179 -0
- data/spec/savon/soap_spec.rb +202 -0
- data/spec/savon/wsdl_spec.rb +107 -0
- data/spec/savon/wsse_spec.rb +132 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +5 -0
- metadata +229 -0
@@ -0,0 +1,8 @@
|
|
1
|
+
require "savon/core_ext/object"
|
2
|
+
require "savon/core_ext/string"
|
3
|
+
require "savon/core_ext/symbol"
|
4
|
+
require "savon/core_ext/datetime"
|
5
|
+
require "savon/core_ext/array"
|
6
|
+
require "savon/core_ext/hash"
|
7
|
+
require "savon/core_ext/uri"
|
8
|
+
require "savon/core_ext/net_http"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
# Translates the Array into SOAP compatible XML. See: Hash.to_soap_xml.
|
4
|
+
def to_soap_xml(key, escape_xml = true, attributes = {})
|
5
|
+
xml = Builder::XmlMarkup.new
|
6
|
+
|
7
|
+
each_with_index do |item, index|
|
8
|
+
attrs = tag_attributes attributes, index
|
9
|
+
case item
|
10
|
+
when Hash then xml.tag!(key, attrs) { xml << item.to_soap_xml }
|
11
|
+
else xml.tag!(key, attrs) { xml << (escape_xml ? item.to_soap_value : item.to_soap_value!) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
xml.target!
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# Takes a Hash of +attributes+ and the +index+ for which to return attributes
|
21
|
+
# for duplicate tags.
|
22
|
+
def tag_attributes(attributes, index)
|
23
|
+
return {} if attributes.empty?
|
24
|
+
|
25
|
+
attributes.inject({}) do |hash, (key, value)|
|
26
|
+
value = value[index] if value.kind_of? Array
|
27
|
+
hash.merge key => value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Returns the values from the soap:Body element or an empty Hash in case the soap:Body tag could
|
4
|
+
# not be found.
|
5
|
+
def find_soap_body
|
6
|
+
envelope = self[keys.first] || {}
|
7
|
+
body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
|
8
|
+
body_key ? envelope[body_key].map_soap_response : {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Translates the Hash into SOAP request compatible XML.
|
12
|
+
#
|
13
|
+
# { :find_user => { :id => 123, "wsdl:Key" => "api" } }.to_soap_xml
|
14
|
+
# # => "<findUser><id>123</id><wsdl:Key>api</wsdl:Key></findUser>"
|
15
|
+
#
|
16
|
+
# ==== Mapping
|
17
|
+
#
|
18
|
+
# * Hash keys specified as Symbols are converted to lowerCamelCase Strings
|
19
|
+
# * Hash keys specified as Strings are not converted and may contain namespaces
|
20
|
+
# * DateTime values are converted to xs:dateTime Strings
|
21
|
+
# * Objects responding to to_datetime (except Strings) are converted to xs:dateTime Strings
|
22
|
+
# * TrueClass and FalseClass objects are converted to "true" and "false" Strings
|
23
|
+
# * All other objects are expected to be converted to Strings using to_s
|
24
|
+
#
|
25
|
+
# An example:
|
26
|
+
#
|
27
|
+
# { :magic_request => {
|
28
|
+
# :perform_move => true,
|
29
|
+
# "perform_at" => DateTime.new(2010, 11, 22, 11, 22, 33)
|
30
|
+
# }
|
31
|
+
# }.to_soap_xml
|
32
|
+
#
|
33
|
+
# <magicRequest>
|
34
|
+
# <performMove>true</performMove>
|
35
|
+
# <perform_at>2012-06-11T10:42:21</perform_at>
|
36
|
+
# </magicRequest>
|
37
|
+
#
|
38
|
+
# ==== Escaped XML values
|
39
|
+
#
|
40
|
+
# By default, special characters in XML String values are escaped.
|
41
|
+
#
|
42
|
+
# ==== Fixed order of XML tags
|
43
|
+
#
|
44
|
+
# In case your service requires the tags to be in a specific order (parameterOrder), you have two
|
45
|
+
# options. The first is to specify your body as an XML string. The second is to specify the order
|
46
|
+
# through an additional array stored under the +:order!+ key.
|
47
|
+
#
|
48
|
+
# { :name => "Eve", :id => 123, :order! => [:id, :name] }.to_soap_xml
|
49
|
+
# # => "<id>123</id><name>Eve</name>"
|
50
|
+
#
|
51
|
+
# ==== XML attributes
|
52
|
+
#
|
53
|
+
# If you need attributes, you could either go with an XML string or add another hash under the
|
54
|
+
# +:attributes!+ key.
|
55
|
+
#
|
56
|
+
# { :person => "Eve", :attributes! => { :person => { :id => 666 } } }.to_soap_xml
|
57
|
+
# # => '<person id="666">Eve</person>'
|
58
|
+
def to_soap_xml
|
59
|
+
xml = Builder::XmlMarkup.new
|
60
|
+
attributes = delete(:attributes!) || {}
|
61
|
+
|
62
|
+
order.each do |key|
|
63
|
+
attrs = attributes[key] || {}
|
64
|
+
value = self[key]
|
65
|
+
escape_xml = key.to_s[-1, 1] != "!"
|
66
|
+
key = key.to_soap_key
|
67
|
+
|
68
|
+
case value
|
69
|
+
when Array then xml << value.to_soap_xml(key, escape_xml, attrs)
|
70
|
+
when Hash then xml.tag!(key, attrs) { xml << value.to_soap_xml }
|
71
|
+
else xml.tag!(key, attrs) { xml << (escape_xml ? value.to_soap_value : value.to_soap_value!) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
xml.target!
|
76
|
+
end
|
77
|
+
|
78
|
+
# Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
|
79
|
+
def map_soap_response
|
80
|
+
inject({}) do |hash, (key, value)|
|
81
|
+
value = case value
|
82
|
+
when Hash then value["xsi:nil"] ? nil : value.map_soap_response
|
83
|
+
when Array then value.map { |val| val.map_soap_response rescue val }
|
84
|
+
when String then value.map_soap_response
|
85
|
+
end
|
86
|
+
|
87
|
+
hash.merge key.strip_namespace.snakecase.to_sym => value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Deletes and returns an Array of keys stored under the :order! key. Defaults to return the actual
|
94
|
+
# keys of this Hash if no :order! key could be found. Raises an ArgumentError in case the :order!
|
95
|
+
# Array does not match the Hash keys.
|
96
|
+
def order
|
97
|
+
order = delete :order!
|
98
|
+
order = keys unless order.kind_of? Array
|
99
|
+
|
100
|
+
missing, spurious = keys - order, order - keys
|
101
|
+
raise ArgumentError, "Missing elements in :order! #{missing.inspect}" unless missing.empty?
|
102
|
+
raise ArgumentError, "Spurious elements in :order! #{spurious.inspect}" unless spurious.empty?
|
103
|
+
|
104
|
+
order
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Net
|
2
|
+
class HTTP
|
3
|
+
|
4
|
+
# Sets the endpoint +address+ and +port+.
|
5
|
+
def endpoint(address, port)
|
6
|
+
@address, @port = address, port
|
7
|
+
end
|
8
|
+
|
9
|
+
# Convenience method for setting SSL client authentication through a Hash of +options+.
|
10
|
+
def ssl_client_auth(options)
|
11
|
+
self.use_ssl = true
|
12
|
+
self.cert = options[:cert] if options[:cert]
|
13
|
+
self.key = options[:key] if options[:key]
|
14
|
+
self.ca_file = options[:ca_file] if options[:ca_file]
|
15
|
+
self.verify_mode = options[:verify_mode] if options[:verify_mode]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Object
|
2
|
+
|
3
|
+
# Returns +true+ if the Object is nil, false or empty. Implementation from ActiveSupport.
|
4
|
+
def blank?
|
5
|
+
respond_to?(:empty?) ? empty? : !self
|
6
|
+
end unless defined? blank?
|
7
|
+
|
8
|
+
# Returns the Object as a SOAP request compliant value.
|
9
|
+
def to_soap_value
|
10
|
+
return to_s unless respond_to? :to_datetime
|
11
|
+
to_datetime.to_soap_value
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :to_soap_value!, :to_soap_value
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
# Returns a random String of a given +length+.
|
4
|
+
def self.random(length = 100)
|
5
|
+
(0...length).map { ("a".."z").to_a[rand(26)] }.join
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns the String in snake_case.
|
9
|
+
def snakecase
|
10
|
+
str = dup
|
11
|
+
str.gsub! /::/, '/'
|
12
|
+
str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
|
13
|
+
str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
|
14
|
+
str.tr! ".", "_"
|
15
|
+
str.tr! "-", "_"
|
16
|
+
str.downcase!
|
17
|
+
str
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the String in lowerCamelCase.
|
21
|
+
def lower_camelcase
|
22
|
+
str = dup
|
23
|
+
str.gsub!(/\/(.?)/) { "::#{$1.upcase}" }
|
24
|
+
str.gsub!(/(?:_+|-+)([a-z])/) { $1.upcase }
|
25
|
+
str.gsub!(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
|
26
|
+
str
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns whether the String starts with a given +prefix+.
|
30
|
+
def starts_with?(prefix)
|
31
|
+
prefix = prefix.to_s
|
32
|
+
self[0, prefix.length] == prefix
|
33
|
+
end unless defined? starts_with?
|
34
|
+
|
35
|
+
# Returns whether the String ends with a given +suffix+.
|
36
|
+
def ends_with?(suffix)
|
37
|
+
suffix = suffix.to_s
|
38
|
+
self[-suffix.length, suffix.length] == suffix
|
39
|
+
end unless defined? ends_with?
|
40
|
+
|
41
|
+
# Returns the String without namespace.
|
42
|
+
def strip_namespace
|
43
|
+
split(":").last
|
44
|
+
end
|
45
|
+
|
46
|
+
# Translates SOAP response values to Ruby Objects.
|
47
|
+
def map_soap_response
|
48
|
+
return DateTime.parse(self) if Savon::SOAP::DateTimeRegexp === self
|
49
|
+
return true if self.strip.downcase == "true"
|
50
|
+
return false if self.strip.downcase == "false"
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the Object as a SOAP request compliant key.
|
55
|
+
def to_soap_key
|
56
|
+
self[-1, 1] == "!" ? chop : self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the String as a SOAP value. Escapes special characters for XML.
|
60
|
+
def to_soap_value
|
61
|
+
CGI.escapeHTML self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Convert the String into a SOAP value without escaping special characters.
|
65
|
+
def to_soap_value!
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/lib/savon/logger.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# = Savon::Logger
|
4
|
+
#
|
5
|
+
# Savon::Logger can be mixed into classes to provide logging behavior.
|
6
|
+
#
|
7
|
+
# By default, the Logger mixin uses {Ruby's Logger}[http://ruby-doc.org/stdlib/libdoc/logger/rdoc/]
|
8
|
+
# from the standard library, a log level of :debug and is pointing to STDOUT.
|
9
|
+
module Logger
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# Sets whether to log.
|
14
|
+
def log=(log)
|
15
|
+
@log = log
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns whether to log. Defaults to +true+.
|
19
|
+
def log?
|
20
|
+
@log != false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sets the logger.
|
24
|
+
def logger=(logger)
|
25
|
+
@logger = logger
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
|
29
|
+
def logger
|
30
|
+
@logger ||= ActiveRecord::Base.logger rescue ::Logger.new STDOUT
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets the log level.
|
34
|
+
def log_level=(log_level)
|
35
|
+
@log_level = log_level
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the log level. Defaults to +debug+.
|
39
|
+
def log_level
|
40
|
+
@log_level ||= :debug
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# Extends the class including this module with its ClassMethods.
|
46
|
+
def self.included(base)
|
47
|
+
base.extend ClassMethods
|
48
|
+
end
|
49
|
+
|
50
|
+
# Logs a given +message+.
|
51
|
+
def log(message)
|
52
|
+
self.class.logger.send self.class.log_level, message if self.class.log?
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# = Savon::Request
|
4
|
+
#
|
5
|
+
# Savon::Request handles both WSDL and SOAP requests.
|
6
|
+
#
|
7
|
+
# == The Net::HTTP object
|
8
|
+
#
|
9
|
+
# You can access the Net::HTTP object used for both WSDL and SOAP requests via:
|
10
|
+
#
|
11
|
+
# client.request.http
|
12
|
+
#
|
13
|
+
# Here's an example of how to set open and read timeouts on the Net::HTTP object.
|
14
|
+
#
|
15
|
+
# client.request.http.open_timeout = 30
|
16
|
+
# client.request.http.read_timeout = 30
|
17
|
+
#
|
18
|
+
# Please refer to the {Net::HTTP documentation}[http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/]
|
19
|
+
# for more information.
|
20
|
+
#
|
21
|
+
# == HTTP basic authentication
|
22
|
+
#
|
23
|
+
# Setting credentials for HTTP basic authentication:
|
24
|
+
#
|
25
|
+
# client.request.basic_auth "username", "password"
|
26
|
+
#
|
27
|
+
# == SSL client authentication
|
28
|
+
#
|
29
|
+
# You can use the methods provided by Net::HTTP to set SSL client authentication or use a shortcut:
|
30
|
+
#
|
31
|
+
# client.request.http.ssl_client_auth(
|
32
|
+
# :cert => OpenSSL::X509::Certificate.new(File.read("client_cert.pem")),
|
33
|
+
# :key => OpenSSL::PKey::RSA.new(File.read("client_key.pem"), "password if one exists"),
|
34
|
+
# :ca_file => "cacert.pem",
|
35
|
+
# :verify_mode => OpenSSL::SSL::VERIFY_PEER
|
36
|
+
# )
|
37
|
+
#
|
38
|
+
# == HTTP headers
|
39
|
+
#
|
40
|
+
# There's an accessor for the Hash of HTTP headers sent with any SOAP call:
|
41
|
+
#
|
42
|
+
# client.request.headers["custom"] = "header"
|
43
|
+
class Request
|
44
|
+
include Logger
|
45
|
+
|
46
|
+
# Content-Types by SOAP version.
|
47
|
+
ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
|
48
|
+
|
49
|
+
# Expects a WSDL or SOAP +endpoint+ and accepts a custom +proxy+ address.
|
50
|
+
def initialize(endpoint, options = {})
|
51
|
+
@endpoint = URI endpoint
|
52
|
+
@proxy = URI options[:proxy] || ""
|
53
|
+
headers["Accept-encoding"] = "gzip,deflate" if options[:gzip]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the endpoint URI.
|
57
|
+
attr_reader :endpoint
|
58
|
+
|
59
|
+
# Returns the proxy URI.
|
60
|
+
attr_reader :proxy
|
61
|
+
|
62
|
+
# Returns the HTTP headers for a SOAP request.
|
63
|
+
def headers
|
64
|
+
@headers ||= {}
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sets the HTTP headers for a SOAP request.
|
68
|
+
def headers=(headers)
|
69
|
+
@headers = headers if headers.kind_of? Hash
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sets the +username+ and +password+ for HTTP basic authentication.
|
73
|
+
def basic_auth(username, password)
|
74
|
+
@basic_auth = [username, password]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Retrieves WSDL document and returns the Net::HTTP response.
|
78
|
+
def wsdl
|
79
|
+
log "Retrieving WSDL from: #{@endpoint}"
|
80
|
+
http.endpoint @endpoint.host, @endpoint.port
|
81
|
+
http.use_ssl = @endpoint.ssl?
|
82
|
+
http.start { |h| h.request request(:wsdl) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Executes a SOAP request using a given Savon::SOAP instance and returns the Net::HTTP response.
|
86
|
+
def soap(soap)
|
87
|
+
@soap = soap
|
88
|
+
http.endpoint @soap.endpoint.host, @soap.endpoint.port
|
89
|
+
http.use_ssl = @soap.endpoint.ssl?
|
90
|
+
|
91
|
+
log_request
|
92
|
+
@response = http.start do |h|
|
93
|
+
h.request request(:soap) { |request| request.body = @soap.to_xml }
|
94
|
+
end
|
95
|
+
log_response
|
96
|
+
@response
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the Net::HTTP object.
|
100
|
+
def http
|
101
|
+
@http ||= Net::HTTP::Proxy(@proxy.host, @proxy.port).new @endpoint.host, @endpoint.port
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Logs the SOAP request.
|
107
|
+
def log_request
|
108
|
+
log "SOAP request: #{@soap.endpoint}"
|
109
|
+
log soap_headers.merge(headers).map { |key, value| "#{key}: #{value}" }.join(", ")
|
110
|
+
log @soap.to_xml
|
111
|
+
end
|
112
|
+
|
113
|
+
# Logs the SOAP response.
|
114
|
+
def log_response
|
115
|
+
log "SOAP response (status #{@response.code}):"
|
116
|
+
log @response.body
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns a Net::HTTP request for a given +type+. Yields the request to an optional block.
|
120
|
+
def request(type)
|
121
|
+
request = case type
|
122
|
+
when :wsdl then Net::HTTP::Get.new @endpoint.request_uri
|
123
|
+
when :soap then Net::HTTP::Post.new @soap.endpoint.request_uri, soap_headers.merge(headers)
|
124
|
+
end
|
125
|
+
|
126
|
+
request.basic_auth(*@basic_auth) if @basic_auth
|
127
|
+
yield request if block_given?
|
128
|
+
request
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns a Hash containing the SOAP headers for an HTTP request.
|
132
|
+
def soap_headers
|
133
|
+
{ "Content-Type" => ContentType[@soap.version], "SOAPAction" => @soap.action }
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|