hoopla-savon 0.7.6
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/CHANGELOG +156 -0
- data/README.rdoc +64 -0
- data/Rakefile +51 -0
- data/lib/savon/client.rb +112 -0
- data/lib/savon/core_ext/array.rb +31 -0
- data/lib/savon/core_ext/datetime.rb +8 -0
- data/lib/savon/core_ext/hash.rb +102 -0
- data/lib/savon/core_ext/net_http.rb +19 -0
- data/lib/savon/core_ext/object.rb +19 -0
- data/lib/savon/core_ext/string.rb +66 -0
- data/lib/savon/core_ext/symbol.rb +8 -0
- data/lib/savon/core_ext/uri.rb +10 -0
- data/lib/savon/core_ext.rb +8 -0
- data/lib/savon/logger.rb +56 -0
- data/lib/savon/request.rb +135 -0
- data/lib/savon/response.rb +155 -0
- data/lib/savon/soap.rb +302 -0
- data/lib/savon/wsdl.rb +142 -0
- data/lib/savon/wsdl_stream.rb +85 -0
- data/lib/savon/wsse.rb +163 -0
- data/lib/savon.rb +32 -0
- data/spec/basic_spec_helper.rb +11 -0
- data/spec/endpoint_helper.rb +23 -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/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 +27 -0
- data/spec/integration/http_basic_auth_spec.rb +16 -0
- data/spec/integration/server.rb +51 -0
- data/spec/savon/client_spec.rb +81 -0
- data/spec/savon/core_ext/array_spec.rb +19 -0
- data/spec/savon/core_ext/datetime_spec.rb +12 -0
- data/spec/savon/core_ext/hash_spec.rb +178 -0
- data/spec/savon/core_ext/net_http_spec.rb +38 -0
- data/spec/savon/core_ext/object_spec.rb +40 -0
- data/spec/savon/core_ext/string_spec.rb +87 -0
- data/spec/savon/core_ext/symbol_spec.rb +11 -0
- data/spec/savon/core_ext/uri_spec.rb +19 -0
- data/spec/savon/request_spec.rb +93 -0
- data/spec/savon/response_spec.rb +137 -0
- data/spec/savon/soap_spec.rb +204 -0
- data/spec/savon/wsdl_spec.rb +110 -0
- data/spec/savon/wsse_spec.rb +132 -0
- data/spec/spec_helper.rb +11 -0
- metadata +206 -0
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 ||= ::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,135 @@
|
|
|
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 SOAP +endpoint+ String. Also accepts an optional Hash
|
|
50
|
+
# of +options+ for specifying a proxy server.
|
|
51
|
+
def initialize(endpoint, options = {})
|
|
52
|
+
@endpoint = URI endpoint
|
|
53
|
+
@proxy = options[:proxy] ? URI(options[:proxy]) : URI("")
|
|
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
|
+
Kernel.open(@endpoint.to_s)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Executes a SOAP request using a given Savon::SOAP instance and returns the Net::HTTP response.
|
|
84
|
+
def soap(soap)
|
|
85
|
+
@soap = soap
|
|
86
|
+
http.endpoint @soap.endpoint.host, @soap.endpoint.port
|
|
87
|
+
http.use_ssl = @soap.endpoint.ssl?
|
|
88
|
+
|
|
89
|
+
log_request
|
|
90
|
+
@response = http.start do |h|
|
|
91
|
+
h.request request(:soap) { |request| request.body = @soap.to_xml }
|
|
92
|
+
end
|
|
93
|
+
log_response
|
|
94
|
+
@response
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Returns the Net::HTTP object.
|
|
98
|
+
def http
|
|
99
|
+
@http ||= Net::HTTP::Proxy(@proxy.host, @proxy.port).new @endpoint.host, @endpoint.port
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Logs the SOAP request.
|
|
105
|
+
def log_request
|
|
106
|
+
log "SOAP request: #{@soap.endpoint}"
|
|
107
|
+
log soap_headers.merge(headers).map { |key, value| "#{key}: #{value}" }.join(", ")
|
|
108
|
+
log @soap.to_xml
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Logs the SOAP response.
|
|
112
|
+
def log_response
|
|
113
|
+
log "SOAP response (status #{@response.code}):"
|
|
114
|
+
log @response.body
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns a Net::HTTP request for a given +type+. Yields the request to an optional block.
|
|
118
|
+
def request(type)
|
|
119
|
+
request = case type
|
|
120
|
+
when :wsdl then Net::HTTP::Get.new @endpoint.request_uri
|
|
121
|
+
when :soap then Net::HTTP::Post.new @soap.endpoint.request_uri, soap_headers.merge(headers)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
request.basic_auth(*@basic_auth) if @basic_auth
|
|
125
|
+
yield request if block_given?
|
|
126
|
+
request
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Returns a Hash containing the SOAP headers for an HTTP request.
|
|
130
|
+
def soap_headers
|
|
131
|
+
{ "Content-Type" => ContentType[@soap.version], "SOAPAction" => @soap.action }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
module Savon
|
|
2
|
+
|
|
3
|
+
# = Savon::Response
|
|
4
|
+
#
|
|
5
|
+
# Savon::Response represents both HTTP and SOAP response.
|
|
6
|
+
#
|
|
7
|
+
# == SOAP fault
|
|
8
|
+
#
|
|
9
|
+
# Assuming the default behavior of raising errors is disabled, you can ask the response object
|
|
10
|
+
# if there was a SOAP fault or an HTTP error and get the SOAP fault or HTTP error message.
|
|
11
|
+
#
|
|
12
|
+
# response.soap_fault?
|
|
13
|
+
# # => true
|
|
14
|
+
#
|
|
15
|
+
# response.soap_fault
|
|
16
|
+
# # => "(soap:Server) Fault occurred while processing."
|
|
17
|
+
#
|
|
18
|
+
# response.http_error?
|
|
19
|
+
# # => true
|
|
20
|
+
#
|
|
21
|
+
# response.http_error
|
|
22
|
+
# # => "Not found (404)"
|
|
23
|
+
#
|
|
24
|
+
# == Response as XML
|
|
25
|
+
#
|
|
26
|
+
# To get the raw SOAP response XML, you can call to_xml or to_s on the response object.
|
|
27
|
+
#
|
|
28
|
+
# response.to_xml
|
|
29
|
+
# => "<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
|
30
|
+
# => "..."
|
|
31
|
+
# => "</soap:Envelope>"
|
|
32
|
+
#
|
|
33
|
+
# == Response as a Hash
|
|
34
|
+
#
|
|
35
|
+
# You can also let Savon translate the SOAP response body to a Hash.
|
|
36
|
+
#
|
|
37
|
+
# response.to_hash
|
|
38
|
+
# => { :findUserByIdResponse => {
|
|
39
|
+
# => :id => "123",
|
|
40
|
+
# => :username => "eve"
|
|
41
|
+
# => :active => true
|
|
42
|
+
# => }
|
|
43
|
+
#
|
|
44
|
+
# When translating the SOAP response to a Hash, some XML tags and values are converted to more
|
|
45
|
+
# convenient Ruby objects. Translation is done through John Nunemaker's {Crack}[http://github.com/jnunemaker/crack]
|
|
46
|
+
# library along with some custom mapping.
|
|
47
|
+
#
|
|
48
|
+
# * XML tags (Hash keys) are converted to snake_case Symbols and namespaces are stripped off
|
|
49
|
+
# * SOAP xs:nil values are converted to nil objects
|
|
50
|
+
# * XML values specified in xs:DateTime format are converted to DateTime objects
|
|
51
|
+
# * XML values of "true" and "false" are converted to TrueClass and FalseClass
|
|
52
|
+
#
|
|
53
|
+
# == Net::HTTP response
|
|
54
|
+
#
|
|
55
|
+
# If for some reason you need to access the Net::HTTP response object ... you can.
|
|
56
|
+
#
|
|
57
|
+
# bc. response.http
|
|
58
|
+
# => #<Net::HTTPOK:0x7f749a1aa4a8>
|
|
59
|
+
class Response
|
|
60
|
+
|
|
61
|
+
# The maximum HTTP response code considered to be OK.
|
|
62
|
+
MaxNonErrorResponseCode = 299
|
|
63
|
+
|
|
64
|
+
# The global setting of whether to raise errors.
|
|
65
|
+
@@raise_errors = true
|
|
66
|
+
|
|
67
|
+
# Sets the global setting of whether to raise errors.
|
|
68
|
+
def self.raise_errors=(raise_errors)
|
|
69
|
+
@@raise_errors = raise_errors
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns the global setting of whether to raise errors.
|
|
73
|
+
def self.raise_errors?
|
|
74
|
+
@@raise_errors
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Expects a Net::HTTPResponse and handles errors.
|
|
78
|
+
def initialize(http)
|
|
79
|
+
@http = http
|
|
80
|
+
|
|
81
|
+
handle_soap_fault
|
|
82
|
+
handle_http_error
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns whether there was a SOAP fault.
|
|
86
|
+
def soap_fault?
|
|
87
|
+
!@soap_fault.blank?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns the SOAP fault message.
|
|
91
|
+
attr_reader :soap_fault
|
|
92
|
+
|
|
93
|
+
# Returns whether there was an HTTP error.
|
|
94
|
+
def http_error?
|
|
95
|
+
!@http_error.blank?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns the HTTP error message.
|
|
99
|
+
attr_reader :http_error
|
|
100
|
+
|
|
101
|
+
# Returns the SOAP response body as a Hash.
|
|
102
|
+
def to_hash
|
|
103
|
+
@body ||= (Crack::XML.parse(@http.body) rescue {}).find_soap_body
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the SOAP response XML.
|
|
107
|
+
def to_xml
|
|
108
|
+
@http.body
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the HTTP response object.
|
|
112
|
+
attr_reader :http
|
|
113
|
+
|
|
114
|
+
alias :to_s :to_xml
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Handles SOAP faults. Raises a Savon::SOAPFault unless the default behavior of raising errors
|
|
119
|
+
# was turned off.
|
|
120
|
+
def handle_soap_fault
|
|
121
|
+
if soap_fault_message
|
|
122
|
+
@soap_fault = soap_fault_message
|
|
123
|
+
raise Savon::SOAPFault, @soap_fault if self.class.raise_errors?
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns a SOAP fault message in case a SOAP fault was found.
|
|
128
|
+
def soap_fault_message
|
|
129
|
+
@soap_fault_message ||= soap_fault_message_by_version to_hash[:fault]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Expects a Hash that might contain information about a SOAP fault. Returns the SOAP fault
|
|
133
|
+
# message in case one was found.
|
|
134
|
+
def soap_fault_message_by_version(soap_fault)
|
|
135
|
+
return unless soap_fault
|
|
136
|
+
|
|
137
|
+
if soap_fault.keys.include? :faultcode
|
|
138
|
+
"(#{soap_fault[:faultcode]}) #{soap_fault[:faultstring]}"
|
|
139
|
+
elsif soap_fault.keys.include? :code
|
|
140
|
+
"(#{soap_fault[:code][:value]}) #{soap_fault[:reason][:text]}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Handles HTTP errors. Raises a Savon::HTTPError unless the default behavior of raising errors
|
|
145
|
+
# was turned off.
|
|
146
|
+
def handle_http_error
|
|
147
|
+
if @http.code.to_i > MaxNonErrorResponseCode
|
|
148
|
+
@http_error = "#{@http.message} (#{@http.code})"
|
|
149
|
+
@http_error << ": #{@http.body}" unless @http.body.empty?
|
|
150
|
+
raise Savon::HTTPError, http_error if self.class.raise_errors?
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
end
|
data/lib/savon/soap.rb
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
module Savon
|
|
2
|
+
|
|
3
|
+
# = Savon::SOAP
|
|
4
|
+
#
|
|
5
|
+
# Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is
|
|
6
|
+
# passed to it as the first argument. The object allows setting the SOAP version, header, body
|
|
7
|
+
# and namespaces per request.
|
|
8
|
+
#
|
|
9
|
+
# == Body
|
|
10
|
+
#
|
|
11
|
+
# The body method lets you specify parameters to be received by the SOAP action.
|
|
12
|
+
#
|
|
13
|
+
# You can either pass in a hash (which will be translated to XML via Hash.to_soap_xml):
|
|
14
|
+
#
|
|
15
|
+
# response = client.get_user_by_id do |soap|
|
|
16
|
+
# soap.body = { :id => 123 }
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Or a string containing the raw XML:
|
|
20
|
+
#
|
|
21
|
+
# response = client.get_user_by_id do |soap|
|
|
22
|
+
# soap.body = "<id>123</id>"
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# Request output:
|
|
26
|
+
#
|
|
27
|
+
# <env:Envelope
|
|
28
|
+
# xmlns:wsdl="http://example.com/user/1.0/UserService"
|
|
29
|
+
# xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
|
30
|
+
# <env:Body>
|
|
31
|
+
# <wsdl:getUserById><id>123</id></wsdl:getUserById>
|
|
32
|
+
# </env:Body>
|
|
33
|
+
# </env:Envelope>
|
|
34
|
+
#
|
|
35
|
+
# Please look at the documentation of Hash.to_soap_xml for some more information.
|
|
36
|
+
#
|
|
37
|
+
# == Version
|
|
38
|
+
#
|
|
39
|
+
# Savon defaults to SOAP 1.1. In case your service uses SOAP 1.2, you can use the version method
|
|
40
|
+
# to change the default per request.
|
|
41
|
+
#
|
|
42
|
+
# response = client.get_all_users do |soap|
|
|
43
|
+
# soap.version = 2
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# You can also change the default to SOAP 1.2 for all request:
|
|
47
|
+
#
|
|
48
|
+
# Savon::SOAP.version = 2
|
|
49
|
+
#
|
|
50
|
+
# == Header
|
|
51
|
+
#
|
|
52
|
+
# If you need to add custom XML into the SOAP header, you can use the header method.
|
|
53
|
+
#
|
|
54
|
+
# The value is expected to be a hash (which will be translated to XML via Hash.to_soap_xml):
|
|
55
|
+
#
|
|
56
|
+
# response = client.get_all_users do |soap|
|
|
57
|
+
# soap.header["specialApiKey"] = "secret"
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# Or a string containing the raw XML:
|
|
61
|
+
#
|
|
62
|
+
# response = client.get_all_users do |soap|
|
|
63
|
+
# soap.header = "<specialApiKey>secret</specialApiKey>"
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# Request output:
|
|
67
|
+
#
|
|
68
|
+
# <env:Envelope
|
|
69
|
+
# xmlns:wsdl="http://example.com/user/1.0/UserService"
|
|
70
|
+
# xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
|
71
|
+
# <env:Header>
|
|
72
|
+
# <specialApiKey>secret</specialApiKey>
|
|
73
|
+
# </env:Header>
|
|
74
|
+
# <env:Body>
|
|
75
|
+
# <wsdl:getAllUsers></wsdl:getAllUsers>
|
|
76
|
+
# </env:Body>
|
|
77
|
+
# </env:Envelope>
|
|
78
|
+
#
|
|
79
|
+
# == Namespaces
|
|
80
|
+
#
|
|
81
|
+
# The namespaces method contains a hash of attributes for the SOAP envelope. You can overwrite it
|
|
82
|
+
# or add additional attributes.
|
|
83
|
+
#
|
|
84
|
+
# response = client.get_all_users do |soap|
|
|
85
|
+
# soap.namespaces["xmlns:domains"] = "http://domains.example.com"
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# Request output:
|
|
89
|
+
#
|
|
90
|
+
# <env:Envelope
|
|
91
|
+
# xmlns:wsdl="http://example.com/user/1.0/UserService"
|
|
92
|
+
# xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
|
|
93
|
+
# xmlns:domains="http://domains.example.com">
|
|
94
|
+
# <env:Body>
|
|
95
|
+
# <wsdl:getAllUsers></wsdl:getAllUsers>
|
|
96
|
+
# </env:Body>
|
|
97
|
+
# </env:Envelope>
|
|
98
|
+
#
|
|
99
|
+
# == Input
|
|
100
|
+
#
|
|
101
|
+
# You can change the name of the SOAP input tag in case you need to.
|
|
102
|
+
#
|
|
103
|
+
# response = client.get_all_users do |soap|
|
|
104
|
+
# soap.input = "GetAllUsersRequest"
|
|
105
|
+
# end
|
|
106
|
+
#
|
|
107
|
+
# Request output:
|
|
108
|
+
#
|
|
109
|
+
# <env:Envelope
|
|
110
|
+
# xmlns:wsdl="http://example.com/user/1.0/UserService"
|
|
111
|
+
# xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
|
112
|
+
# <env:Body>
|
|
113
|
+
# <wsdl:GetAllUsersRequest></wsdl:GetAllUsersRequest>
|
|
114
|
+
# </env:Body>
|
|
115
|
+
# </env:Envelope>
|
|
116
|
+
class SOAP
|
|
117
|
+
|
|
118
|
+
# Supported SOAP versions.
|
|
119
|
+
Versions = [1, 2]
|
|
120
|
+
|
|
121
|
+
# SOAP namespaces by SOAP version.
|
|
122
|
+
Namespace = {
|
|
123
|
+
1 => "http://schemas.xmlsoap.org/soap/envelope/",
|
|
124
|
+
2 => "http://www.w3.org/2003/05/soap-envelope"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Content-Types by SOAP version.
|
|
128
|
+
ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
|
|
129
|
+
|
|
130
|
+
# SOAP xs:dateTime format.
|
|
131
|
+
DateTimeFormat = "%Y-%m-%dT%H:%M:%SZ"
|
|
132
|
+
|
|
133
|
+
# SOAP xs:dateTime Regexp.
|
|
134
|
+
DateTimeRegexp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
|
135
|
+
|
|
136
|
+
# The global SOAP version.
|
|
137
|
+
@@version = 1
|
|
138
|
+
|
|
139
|
+
# Returns the global SOAP version.
|
|
140
|
+
def self.version
|
|
141
|
+
@@version
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Sets the global SOAP version.
|
|
145
|
+
def self.version=(version)
|
|
146
|
+
@@version = version if Versions.include? version
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Sets the global SOAP header. Expected to be a Hash that can be translated to XML via
|
|
150
|
+
# Hash.to_soap_xml or any other Object responding to to_s.
|
|
151
|
+
def self.header=(header)
|
|
152
|
+
@@header = header
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns the global SOAP header. Defaults to an empty Hash.
|
|
156
|
+
def self.header
|
|
157
|
+
@@header ||= {}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Sets the global namespaces. Expected to be a Hash containing the namespaces (keys) and the
|
|
161
|
+
# corresponding URI's (values).
|
|
162
|
+
def self.namespaces=(namespaces)
|
|
163
|
+
@@namespaces = namespaces if namespaces.kind_of? Hash
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the global namespaces. A Hash containing the namespaces (keys) and the corresponding
|
|
167
|
+
# URI's (values).
|
|
168
|
+
def self.namespaces
|
|
169
|
+
@@namespaces ||= {}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Initialzes the SOAP object. Expects a SOAP +operation+ Hash along with an +endpoint+.
|
|
173
|
+
def initialize(action, input, endpoint)
|
|
174
|
+
@action, @input = action, input
|
|
175
|
+
@endpoint = endpoint.kind_of?(URI) ? endpoint : URI(endpoint)
|
|
176
|
+
@builder = Builder::XmlMarkup.new
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Sets the WSSE options.
|
|
180
|
+
attr_writer :wsse
|
|
181
|
+
|
|
182
|
+
# Sets the SOAP action.
|
|
183
|
+
attr_writer :action
|
|
184
|
+
|
|
185
|
+
# Returns the SOAP action.
|
|
186
|
+
def action
|
|
187
|
+
@action ||= ""
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Sets the SOAP input.
|
|
191
|
+
attr_writer :input
|
|
192
|
+
|
|
193
|
+
# Returns the SOAP input.
|
|
194
|
+
def input
|
|
195
|
+
@input ||= ""
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Accessor for the SOAP endpoint.
|
|
199
|
+
attr_accessor :endpoint
|
|
200
|
+
|
|
201
|
+
# Sets the SOAP header. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
|
|
202
|
+
# or any other Object responding to to_s.
|
|
203
|
+
attr_writer :header
|
|
204
|
+
|
|
205
|
+
# Returns the SOAP header. Defaults to an empty Hash.
|
|
206
|
+
def header
|
|
207
|
+
@header ||= {}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Accessor for the SOAP body. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
|
|
211
|
+
# or any other Object responding to to_s.
|
|
212
|
+
attr_accessor :body
|
|
213
|
+
|
|
214
|
+
# Accessor for overwriting the default SOAP request. Let's you specify completely custom XML.
|
|
215
|
+
attr_accessor :xml
|
|
216
|
+
|
|
217
|
+
# Sets the namespaces. Expected to be a Hash containing the namespaces (keys) and the
|
|
218
|
+
# corresponding URI's (values).
|
|
219
|
+
attr_writer :namespaces
|
|
220
|
+
|
|
221
|
+
# Returns the namespaces. A Hash containing the namespaces (keys) and the corresponding URI's
|
|
222
|
+
# (values). Defaults to a Hash containing an +xmlns:env+ key and the namespace for the current
|
|
223
|
+
# SOAP version.
|
|
224
|
+
def namespaces
|
|
225
|
+
@namespaces ||= { "xmlns:env" => Namespace[version] }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Convenience method for setting the +xmlns:wsdl+ namespace.
|
|
229
|
+
def namespace=(namespace)
|
|
230
|
+
namespaces["xmlns:wsdl"] = namespace
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Sets the SOAP version.
|
|
234
|
+
def version=(version)
|
|
235
|
+
@version = version if Versions.include? version
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Returns the SOAP version. Defaults to the global default.
|
|
239
|
+
def version
|
|
240
|
+
@version ||= self.class.version
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Returns the SOAP envelope XML.
|
|
244
|
+
def to_xml
|
|
245
|
+
unless @xml
|
|
246
|
+
@builder.instruct!
|
|
247
|
+
@xml = @builder.env :Envelope, merged_namespaces do |xml|
|
|
248
|
+
xml.env(:Header) { xml << merged_header } unless merged_header.empty?
|
|
249
|
+
xml_body xml
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
@xml
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
private
|
|
256
|
+
|
|
257
|
+
# Returns a String containing the global and per request header.
|
|
258
|
+
def merged_header
|
|
259
|
+
if self.class.header.kind_of?(Hash) && header.kind_of?(Hash)
|
|
260
|
+
merged_header = self.class.header.merge(header).to_soap_xml
|
|
261
|
+
else
|
|
262
|
+
global_header = self.class.header.to_soap_xml rescue self.class.header.to_s
|
|
263
|
+
request_header = header.to_soap_xml rescue header.to_s
|
|
264
|
+
merged_header = global_header + request_header
|
|
265
|
+
end
|
|
266
|
+
merged_header + wsse_header
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Returns the WSSE header or an empty String in case WSSE was not set.
|
|
270
|
+
def wsse_header
|
|
271
|
+
@wsse.respond_to?(:header) ? @wsse.header : ""
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Adds a SOAP XML body to a given +xml+ Object.
|
|
275
|
+
def xml_body(xml)
|
|
276
|
+
xml.env(:Body) do
|
|
277
|
+
xml.tag!(:wsdl, *input_array) { xml << (@body.to_soap_xml rescue @body.to_s) }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Returns a Hash containing the global and per request namespaces.
|
|
282
|
+
def merged_namespaces
|
|
283
|
+
self.class.namespaces.merge namespaces
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Returns an Array of SOAP input names to append to the wsdl namespace. Defaults to use the
|
|
287
|
+
# name of the SOAP action. May return an empty Array in case the specified SOAP input seems
|
|
288
|
+
# to be invalid.
|
|
289
|
+
def input_array
|
|
290
|
+
if input.kind_of?(Array) && !input.blank?
|
|
291
|
+
[input[0].to_sym, input[1]]
|
|
292
|
+
elsif !input.blank?
|
|
293
|
+
[input.to_sym]
|
|
294
|
+
elsif !action.blank?
|
|
295
|
+
[action.to_sym]
|
|
296
|
+
else
|
|
297
|
+
[]
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
end
|
|
302
|
+
end
|