they-savon 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +631 -0
- data/Gemfile +9 -0
- data/LICENSE +20 -0
- data/README.md +42 -0
- data/Rakefile +7 -0
- data/lib/savon/client.rb +168 -0
- data/lib/savon/core_ext/object.rb +14 -0
- data/lib/savon/core_ext/string.rb +23 -0
- data/lib/savon/error.rb +6 -0
- data/lib/savon/global.rb +129 -0
- data/lib/savon/http/error.rb +42 -0
- data/lib/savon/soap/fault.rb +59 -0
- data/lib/savon/soap/request.rb +71 -0
- data/lib/savon/soap/response.rb +109 -0
- data/lib/savon/soap/xml.rb +227 -0
- data/lib/savon/soap.rb +21 -0
- data/lib/savon/version.rb +5 -0
- data/lib/savon/wasabi/document.rb +41 -0
- data/lib/savon.rb +14 -0
- data/savon.gemspec +32 -0
- data/spec/fixtures/gzip/message.gz +0 -0
- data/spec/fixtures/response/another_soap_fault.xml +14 -0
- data/spec/fixtures/response/authentication.xml +14 -0
- data/spec/fixtures/response/header.xml +13 -0
- data/spec/fixtures/response/list.xml +18 -0
- data/spec/fixtures/response/multi_ref.xml +39 -0
- data/spec/fixtures/response/soap_fault.xml +8 -0
- data/spec/fixtures/response/soap_fault12.xml +18 -0
- data/spec/fixtures/response/taxcloud.xml +1 -0
- data/spec/fixtures/wsdl/authentication.xml +63 -0
- data/spec/fixtures/wsdl/lower_camel.xml +52 -0
- data/spec/fixtures/wsdl/multiple_namespaces.xml +61 -0
- data/spec/fixtures/wsdl/multiple_types.xml +60 -0
- data/spec/fixtures/wsdl/taxcloud.xml +934 -0
- data/spec/savon/client_spec.rb +461 -0
- data/spec/savon/core_ext/object_spec.rb +19 -0
- data/spec/savon/core_ext/string_spec.rb +37 -0
- data/spec/savon/http/error_spec.rb +52 -0
- data/spec/savon/savon_spec.rb +146 -0
- data/spec/savon/soap/fault_spec.rb +89 -0
- data/spec/savon/soap/request_spec.rb +57 -0
- data/spec/savon/soap/response_spec.rb +224 -0
- data/spec/savon/soap/xml_spec.rb +309 -0
- data/spec/savon/soap_spec.rb +16 -0
- data/spec/savon/wasabi/document_spec.rb +45 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/endpoint.rb +25 -0
- data/spec/support/fixture.rb +35 -0
- metadata +216 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Daniel Harrington
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
Savon [![Build Status](https://secure.travis-ci.org/rubiii/savon.png)](http://travis-ci.org/rubiii/savon)
|
2
|
+
=====
|
3
|
+
|
4
|
+
Heavy metal Ruby SOAP client
|
5
|
+
|
6
|
+
[Documentation](http://savonrb.com) | [RDoc](http://rubydoc.info/gems/savon) |
|
7
|
+
[Mailing list](https://groups.google.com/forum/#!forum/savonrb) | [Twitter](http://twitter.com/savonrb)
|
8
|
+
|
9
|
+
Installation
|
10
|
+
------------
|
11
|
+
|
12
|
+
Savon is available through [Rubygems](http://rubygems.org/gems/savon) and can be installed via:
|
13
|
+
|
14
|
+
```
|
15
|
+
$ gem install savon
|
16
|
+
```
|
17
|
+
|
18
|
+
Introduction
|
19
|
+
------------
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
require "savon"
|
23
|
+
|
24
|
+
# create a client for your SOAP service
|
25
|
+
client = Savon::Client.new("http://service.example.com?wsdl")
|
26
|
+
|
27
|
+
client.wsdl.soap_actions
|
28
|
+
# => [:create_user, :get_user, :get_all_users]
|
29
|
+
|
30
|
+
# execute a SOAP request to call the "getUser" action
|
31
|
+
response = client.request(:get_user) do
|
32
|
+
soap.body = { :id => 1 }
|
33
|
+
end
|
34
|
+
|
35
|
+
response.body
|
36
|
+
# => { :get_user_response => { :first_name => "The", :last_name => "Hoff" } }
|
37
|
+
```
|
38
|
+
|
39
|
+
Documentation
|
40
|
+
-------------
|
41
|
+
|
42
|
+
Continue reading at [savonrb.com](http://savonrb.com)
|
data/Rakefile
ADDED
data/lib/savon/client.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require "httpi/request"
|
2
|
+
require "akami"
|
3
|
+
|
4
|
+
require "savon/wasabi/document"
|
5
|
+
require "savon/soap/xml"
|
6
|
+
require "savon/soap/request"
|
7
|
+
require "savon/soap/response"
|
8
|
+
|
9
|
+
module Savon
|
10
|
+
|
11
|
+
# = Savon::Client
|
12
|
+
#
|
13
|
+
# Savon::Client is the main object for connecting to a SOAP service.
|
14
|
+
class Client
|
15
|
+
|
16
|
+
# Initializes the Savon::Client for a SOAP service. Accepts a +block+ which is evaluated in the
|
17
|
+
# context of this object to let you access the +wsdl+, +http+, and +wsse+ methods.
|
18
|
+
#
|
19
|
+
# == Examples
|
20
|
+
#
|
21
|
+
# # Using a remote WSDL
|
22
|
+
# client = Savon::Client.new("http://example.com/UserService?wsdl")
|
23
|
+
#
|
24
|
+
# # Using a local WSDL
|
25
|
+
# client = Savon::Client.new File.expand_path("../wsdl/service.xml", __FILE__)
|
26
|
+
#
|
27
|
+
# # Directly accessing a SOAP endpoint
|
28
|
+
# client = Savon::Client.new do
|
29
|
+
# wsdl.endpoint = "http://example.com/UserService"
|
30
|
+
# wsdl.namespace = "http://users.example.com"
|
31
|
+
# end
|
32
|
+
def initialize(wsdl_document = nil, &block)
|
33
|
+
wsdl.document = wsdl_document if wsdl_document
|
34
|
+
process 1, &block if block
|
35
|
+
wsdl.request = http
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the <tt>Savon::Wasabi::Document</tt>.
|
39
|
+
def wsdl
|
40
|
+
@wsdl ||= Wasabi::Document.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the <tt>HTTPI::Request</tt>.
|
44
|
+
def http
|
45
|
+
@http ||= HTTPI::Request.new
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the <tt>Akami::WSSE</tt> object.
|
49
|
+
def wsse
|
50
|
+
@wsse ||= Akami.wsse
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the <tt>Savon::SOAP::XML</tt> object. Please notice, that this object is only available
|
54
|
+
# in a block given to <tt>Savon::Client#request</tt>. A new instance of this object is created
|
55
|
+
# per SOAP request.
|
56
|
+
attr_reader :soap
|
57
|
+
|
58
|
+
# Executes a SOAP request for a given SOAP action. Accepts a +block+ which is evaluated in the
|
59
|
+
# context of this object to let you access the +soap+, +wsdl+, +http+ and +wsse+ methods.
|
60
|
+
#
|
61
|
+
# == Examples
|
62
|
+
#
|
63
|
+
# # Calls a "getUser" SOAP action with the payload of "<userId>123</userId>"
|
64
|
+
# client.request(:get_user) { soap.body = { :user_id => 123 } }
|
65
|
+
#
|
66
|
+
# # Prefixes the SOAP input tag with a given namespace: "<wsdl:GetUser>...</wsdl:GetUser>"
|
67
|
+
# client.request(:wsdl, "GetUser") { soap.body = { :user_id => 123 } }
|
68
|
+
#
|
69
|
+
# # SOAP input tag with attributes: <getUser xmlns:wsdl="http://example.com">...</getUser>"
|
70
|
+
# client.request(:get_user, "xmlns:wsdl" => "http://example.com")
|
71
|
+
def request(*args, &block)
|
72
|
+
raise ArgumentError, "Savon::Client#request requires at least one argument" if args.empty?
|
73
|
+
|
74
|
+
self.soap = SOAP::XML.new
|
75
|
+
preconfigure extract_options(args)
|
76
|
+
process &block if block
|
77
|
+
soap.wsse = wsse
|
78
|
+
|
79
|
+
response = SOAP::Request.new(http, soap).response
|
80
|
+
set_cookie response.http.headers
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Writer for the <tt>Savon::SOAP::XML</tt> object.
|
87
|
+
attr_writer :soap
|
88
|
+
|
89
|
+
# Accessor for the original self of a given block.
|
90
|
+
attr_accessor :original_self
|
91
|
+
|
92
|
+
# Passes a cookie from the last request +headers+ to the next one.
|
93
|
+
def set_cookie(headers)
|
94
|
+
http.headers["Cookie"] = headers["Set-Cookie"] if headers["Set-Cookie"]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Expects an Array of +args+ and returns an Array containing the namespace (might be +nil+),
|
98
|
+
# the SOAP input and a Hash of attributes for the input tag (which might be empty).
|
99
|
+
def extract_options(args)
|
100
|
+
attributes = Hash === args.last ? args.pop : {}
|
101
|
+
namespace = args.size > 1 ? args.shift.to_sym : nil
|
102
|
+
input = args.first
|
103
|
+
|
104
|
+
[namespace, input, attributes]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Expects an Array of +args+ to preconfigure the system.
|
108
|
+
def preconfigure(args)
|
109
|
+
soap.endpoint = wsdl.endpoint
|
110
|
+
soap.namespace_identifier = args[0]
|
111
|
+
soap.namespace = wsdl.namespace
|
112
|
+
soap.element_form_default = wsdl.element_form_default if wsdl.document?
|
113
|
+
|
114
|
+
body = args[2].delete(:body)
|
115
|
+
soap.body = body if body
|
116
|
+
|
117
|
+
wsdl.type_namespaces.each do |path, uri|
|
118
|
+
soap.use_namespace(path, uri)
|
119
|
+
end
|
120
|
+
|
121
|
+
wsdl.type_definitions.each do |path, type|
|
122
|
+
soap.types[path] = type
|
123
|
+
end
|
124
|
+
|
125
|
+
set_soap_action args[1]
|
126
|
+
set_soap_input *args
|
127
|
+
end
|
128
|
+
|
129
|
+
# Expects an +input+ and sets the +SOAPAction+ HTTP headers.
|
130
|
+
def set_soap_action(input_tag)
|
131
|
+
soap_action = wsdl.soap_action(input_tag.to_sym) if wsdl.document?
|
132
|
+
soap_action ||= Gyoku::XMLKey.create(input_tag).to_sym
|
133
|
+
http.headers["SOAPAction"] = %{"#{soap_action}"}
|
134
|
+
end
|
135
|
+
|
136
|
+
# Expects a +namespace+, +input+ and +attributes+ and sets the SOAP input.
|
137
|
+
def set_soap_input(namespace, input, attributes)
|
138
|
+
new_input_tag = wsdl.soap_input(input.to_sym) if wsdl.document?
|
139
|
+
new_input_tag ||= Gyoku::XMLKey.create(input)
|
140
|
+
soap.input = [namespace, new_input_tag.to_sym, attributes]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Processes a given +block+. Yields objects if the block expects any arguments.
|
144
|
+
# Otherwise evaluates the block in the context of this object.
|
145
|
+
def process(offset = 0, &block)
|
146
|
+
block.arity > 0 ? yield_objects(offset, &block) : evaluate(&block)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Yields a number of objects to a given +block+ depending on how many arguments
|
150
|
+
# the block is expecting.
|
151
|
+
def yield_objects(offset, &block)
|
152
|
+
yield *[soap, wsdl, http, wsse][offset, block.arity]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Evaluates a given +block+ inside this object. Stores the original block binding.
|
156
|
+
def evaluate(&block)
|
157
|
+
self.original_self = eval "self", block.binding
|
158
|
+
instance_eval &block
|
159
|
+
end
|
160
|
+
|
161
|
+
# Handles calls to undefined methods by delegating to the original block binding.
|
162
|
+
def method_missing(method, *args, &block)
|
163
|
+
super unless original_self
|
164
|
+
original_self.send method, *args, &block
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Savon
|
2
|
+
module CoreExt
|
3
|
+
module Object
|
4
|
+
|
5
|
+
# Returns +true+ if the Object is nil, false or empty. Implementation from ActiveSupport.
|
6
|
+
def blank?
|
7
|
+
respond_to?(:empty?) ? empty? : !self
|
8
|
+
end unless method_defined?(:blank?)
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Object.send :include, Savon::CoreExt::Object
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "savon/soap"
|
2
|
+
|
3
|
+
module Savon
|
4
|
+
module CoreExt
|
5
|
+
module String
|
6
|
+
|
7
|
+
# Returns the String in snake_case.
|
8
|
+
def snakecase
|
9
|
+
str = dup
|
10
|
+
str.gsub! /::/, '/'
|
11
|
+
str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
|
12
|
+
str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
|
13
|
+
str.tr! ".", "_"
|
14
|
+
str.tr! "-", "_"
|
15
|
+
str.downcase!
|
16
|
+
str
|
17
|
+
end unless method_defined?(:snakecase)
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
String.send :include, Savon::CoreExt::String
|
data/lib/savon/error.rb
ADDED
data/lib/savon/global.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "savon/soap"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module Global
|
6
|
+
|
7
|
+
# Sets whether to log HTTP requests.
|
8
|
+
attr_writer :log
|
9
|
+
|
10
|
+
# Returns whether to log HTTP requests. Defaults to +true+.
|
11
|
+
def log?
|
12
|
+
@log != false
|
13
|
+
end
|
14
|
+
|
15
|
+
# Sets the logger to use.
|
16
|
+
attr_writer :logger
|
17
|
+
|
18
|
+
# Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
|
19
|
+
def logger
|
20
|
+
@logger ||= ::Logger.new STDOUT
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sets the log level.
|
24
|
+
attr_writer :log_level
|
25
|
+
|
26
|
+
# Returns the log level. Defaults to :debug.
|
27
|
+
def log_level
|
28
|
+
@log_level ||= :debug
|
29
|
+
end
|
30
|
+
|
31
|
+
# Logs a given +message+.
|
32
|
+
def log(message)
|
33
|
+
logger.send log_level, filtered(message) if log?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the log filter.
|
37
|
+
attr_writer :log_filter
|
38
|
+
|
39
|
+
# Returns the log filter. Defaults to blank.
|
40
|
+
def log_filter
|
41
|
+
@log_filter ||= ''
|
42
|
+
end
|
43
|
+
|
44
|
+
# Filters Message based on log filter
|
45
|
+
def filtered(message)
|
46
|
+
xml = Nokogiri::XML(message)
|
47
|
+
return message if @log_filter.empty? || !xml.errors.empty?
|
48
|
+
|
49
|
+
@log_filter.each do |filter|
|
50
|
+
xml.xpath("//*[local-name()='#{filter}']").map { |node| node.content = '***FILTERED***' }
|
51
|
+
end
|
52
|
+
return xml.root.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sets whether to raise HTTP errors and SOAP faults.
|
56
|
+
attr_writer :raise_errors
|
57
|
+
|
58
|
+
# Returns whether to raise errors. Defaults to +true+.
|
59
|
+
def raise_errors?
|
60
|
+
@raise_errors != false
|
61
|
+
end
|
62
|
+
|
63
|
+
# Sets the global SOAP version.
|
64
|
+
def soap_version=(version)
|
65
|
+
raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::Versions.include? version
|
66
|
+
@version = version
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns SOAP version. Defaults to +DefaultVersion+.
|
70
|
+
def soap_version
|
71
|
+
@version ||= SOAP::DefaultVersion
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns whether to strip namespaces in a SOAP response Hash.
|
75
|
+
# Defaults to +true+.
|
76
|
+
def strip_namespaces?
|
77
|
+
Savon.deprecate("use Nori.strip_namespaces? instead of Savon.strip_namespaces?")
|
78
|
+
Nori.strip_namespaces?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sets whether to strip namespaces in a SOAP response Hash.
|
82
|
+
def strip_namespaces=(strip)
|
83
|
+
Savon.deprecate("use Nori.strip_namespaces= instead of Savon.strip_namespaces=")
|
84
|
+
Nori.strip_namespaces = strip
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the global env_namespace.
|
88
|
+
attr_reader :env_namespace
|
89
|
+
|
90
|
+
# Sets the global env_namespace.
|
91
|
+
attr_writer :env_namespace
|
92
|
+
|
93
|
+
# Returns the global soap_header.
|
94
|
+
attr_reader :soap_header
|
95
|
+
|
96
|
+
# Sets the global soap_header.
|
97
|
+
attr_writer :soap_header
|
98
|
+
|
99
|
+
# Expects a +message+ and raises a warning if configured.
|
100
|
+
def deprecate(message)
|
101
|
+
warn("Deprecation: #{message}") if deprecate?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sets whether to warn about deprecations.
|
105
|
+
def deprecate=(deprecate)
|
106
|
+
@deprecate = deprecate
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns whether to warn about deprecation.
|
110
|
+
def deprecate?
|
111
|
+
@deprecate != false
|
112
|
+
end
|
113
|
+
|
114
|
+
# Reset to default configuration.
|
115
|
+
def reset_config!
|
116
|
+
self.log = true
|
117
|
+
self.logger = ::Logger.new STDOUT
|
118
|
+
self.log_level = :debug
|
119
|
+
self.raise_errors = true
|
120
|
+
self.soap_version = SOAP::DefaultVersion
|
121
|
+
self.strip_namespaces = true
|
122
|
+
self.env_namespace = nil
|
123
|
+
self.soap_header = {}
|
124
|
+
self.log_filter = ''
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "savon/error"
|
2
|
+
require "savon/soap/xml"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module HTTP
|
6
|
+
|
7
|
+
# = Savon::HTTP::Error
|
8
|
+
#
|
9
|
+
# Represents an HTTP error. Contains the original <tt>HTTPI::Response</tt>.
|
10
|
+
class Error < Error
|
11
|
+
|
12
|
+
# Expects an <tt>HTTPI::Response</tt>.
|
13
|
+
def initialize(http)
|
14
|
+
self.http = http
|
15
|
+
end
|
16
|
+
|
17
|
+
# Accessor for the <tt>HTTPI::Response</tt>.
|
18
|
+
attr_accessor :http
|
19
|
+
|
20
|
+
# Returns whether an HTTP error is present.
|
21
|
+
def present?
|
22
|
+
http.error?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the HTTP error message.
|
26
|
+
def to_s
|
27
|
+
return "" unless present?
|
28
|
+
|
29
|
+
@message ||= begin
|
30
|
+
message = "HTTP error (#{http.code})"
|
31
|
+
message << ": #{http.body}" unless http.body.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the HTTP response as a Hash.
|
36
|
+
def to_hash
|
37
|
+
@hash = { :code => http.code, :headers => http.headers, :body => http.body }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "savon/error"
|
2
|
+
require "savon/soap/xml"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module SOAP
|
6
|
+
|
7
|
+
# = Savon::SOAP::Fault
|
8
|
+
#
|
9
|
+
# Represents a SOAP fault. Contains the original <tt>HTTPI::Response</tt>.
|
10
|
+
class Fault < Error
|
11
|
+
|
12
|
+
# Expects an <tt>HTTPI::Response</tt>.
|
13
|
+
def initialize(http)
|
14
|
+
self.http = http
|
15
|
+
end
|
16
|
+
|
17
|
+
# Accessor for the <tt>HTTPI::Response</tt>.
|
18
|
+
attr_accessor :http
|
19
|
+
|
20
|
+
# Returns whether a SOAP fault is present.
|
21
|
+
def present?
|
22
|
+
@present ||= http.body.include?("Fault>") && (soap1_fault? || soap2_fault?)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the SOAP fault message.
|
26
|
+
def to_s
|
27
|
+
return "" unless present?
|
28
|
+
@message ||= message_by_version to_hash[:fault]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the SOAP response body as a Hash.
|
32
|
+
def to_hash
|
33
|
+
@hash ||= Nori.parse(http.body)[:envelope][:body]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Returns whether the response contains a SOAP 1.1 fault.
|
39
|
+
def soap1_fault?
|
40
|
+
http.body.include?("faultcode>") && http.body.include?("faultstring>")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns whether the response contains a SOAP 1.2 fault.
|
44
|
+
def soap2_fault?
|
45
|
+
http.body.include?("Code>") && http.body.include?("Reason>")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the SOAP fault message by version.
|
49
|
+
def message_by_version(fault)
|
50
|
+
if fault[:faultcode]
|
51
|
+
"(#{fault[:faultcode]}) #{fault[:faultstring]}"
|
52
|
+
elsif fault[:code]
|
53
|
+
"(#{fault[:code][:value]}) #{fault[:reason][:text]}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "httpi"
|
2
|
+
require "savon/soap/response"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module SOAP
|
6
|
+
|
7
|
+
# = Savon::SOAP::Request
|
8
|
+
#
|
9
|
+
# Executes SOAP requests.
|
10
|
+
class Request
|
11
|
+
|
12
|
+
# Content-Types by SOAP version.
|
13
|
+
ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
|
14
|
+
|
15
|
+
# Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object
|
16
|
+
# to execute a SOAP request and returns the response.
|
17
|
+
def self.execute(request, soap)
|
18
|
+
new(request, soap).response
|
19
|
+
end
|
20
|
+
|
21
|
+
# Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object.
|
22
|
+
def initialize(request, soap)
|
23
|
+
self.request = setup(request, soap)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Accessor for the <tt>HTTPI::Request</tt>.
|
27
|
+
attr_accessor :request
|
28
|
+
|
29
|
+
# Executes the request and returns the response.
|
30
|
+
def response
|
31
|
+
@response ||= with_logging { HTTPI.post request }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Sets up the +request+ using a given +soap+ object.
|
37
|
+
def setup(request, soap)
|
38
|
+
url, body = soap.endpoint, soap.to_xml
|
39
|
+
|
40
|
+
request.url = url
|
41
|
+
request.body = body
|
42
|
+
request.headers["Content-Type"] ||= ContentType[soap.version]
|
43
|
+
request.headers["Content-Length"] ||= body.length.to_s
|
44
|
+
|
45
|
+
request
|
46
|
+
end
|
47
|
+
|
48
|
+
# Logs the HTTP request, yields to a given +block+ and returns a <tt>Savon::SOAP::Response</tt>.
|
49
|
+
def with_logging
|
50
|
+
log_request request.url, request.headers, request.body
|
51
|
+
response = yield
|
52
|
+
log_response response.code, response.body
|
53
|
+
SOAP::Response.new response
|
54
|
+
end
|
55
|
+
|
56
|
+
# Logs the SOAP request +url+, +headers+ and +body+.
|
57
|
+
def log_request(url, headers, body)
|
58
|
+
Savon.log "SOAP request: #{url}"
|
59
|
+
Savon.log headers.map { |key, value| "#{key}: #{value}" }.join(", ")
|
60
|
+
Savon.log body
|
61
|
+
end
|
62
|
+
|
63
|
+
# Logs the SOAP response +code+ and +body+.
|
64
|
+
def log_response(code, body)
|
65
|
+
Savon.log "SOAP response (status #{code}):"
|
66
|
+
Savon.log body
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "savon/soap/xml"
|
2
|
+
require "savon/soap/fault"
|
3
|
+
require "savon/http/error"
|
4
|
+
|
5
|
+
module Savon
|
6
|
+
module SOAP
|
7
|
+
|
8
|
+
# = Savon::SOAP::Response
|
9
|
+
#
|
10
|
+
# Represents the SOAP response and contains the HTTP response.
|
11
|
+
class Response
|
12
|
+
|
13
|
+
# Expects an <tt>HTTPI::Response</tt> and handles errors.
|
14
|
+
def initialize(response)
|
15
|
+
self.http = response
|
16
|
+
raise_errors if Savon.raise_errors?
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :http
|
20
|
+
|
21
|
+
# Returns whether the request was successful.
|
22
|
+
def success?
|
23
|
+
!soap_fault? && !http_error?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns whether there was a SOAP fault.
|
27
|
+
def soap_fault?
|
28
|
+
soap_fault.present?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the <tt>Savon::SOAP::Fault</tt>.
|
32
|
+
def soap_fault
|
33
|
+
@soap_fault ||= Fault.new http
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns whether there was an HTTP error.
|
37
|
+
def http_error?
|
38
|
+
http_error.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the <tt>Savon::HTTP::Error</tt>.
|
42
|
+
def http_error
|
43
|
+
@http_error ||= HTTP::Error.new http
|
44
|
+
end
|
45
|
+
|
46
|
+
# Shortcut accessor for the SOAP response body Hash.
|
47
|
+
def [](key)
|
48
|
+
body[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the SOAP response header as a Hash.
|
52
|
+
def header
|
53
|
+
hash[:envelope][:header]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the SOAP response body as a Hash.
|
57
|
+
def body
|
58
|
+
hash[:envelope][:body]
|
59
|
+
end
|
60
|
+
|
61
|
+
alias to_hash body
|
62
|
+
|
63
|
+
# Traverses the SOAP response body Hash for a given +path+ of Hash keys and returns
|
64
|
+
# the value as an Array. Defaults to return an empty Array in case the path does not
|
65
|
+
# exist or returns nil.
|
66
|
+
def to_array(*path)
|
67
|
+
result = path.inject body do |memo, key|
|
68
|
+
return [] unless memo[key]
|
69
|
+
memo[key]
|
70
|
+
end
|
71
|
+
|
72
|
+
result.kind_of?(Array) ? result.compact : [result].compact
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the complete SOAP response XML without normalization.
|
76
|
+
def hash
|
77
|
+
@hash ||= Nori.parse(to_xml)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the SOAP response XML.
|
81
|
+
def to_xml
|
82
|
+
http.body
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a <tt>Nokogiri::XML::Document</tt> for the SOAP response XML.
|
86
|
+
def doc
|
87
|
+
@doc ||= Nokogiri::XML(to_xml)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns an Array of <tt>Nokogiri::XML::Node</tt> objects retrieved with the given +path+.
|
91
|
+
# Automatically adds all of the document's namespaces unless a +namespaces+ hash is provided.
|
92
|
+
def xpath(path, namespaces = nil)
|
93
|
+
doc.xpath(path, namespaces || xml_namespaces)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def raise_errors
|
99
|
+
raise soap_fault if soap_fault?
|
100
|
+
raise http_error if http_error?
|
101
|
+
end
|
102
|
+
|
103
|
+
def xml_namespaces
|
104
|
+
@xml_namespaces ||= doc.collect_namespaces
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|