handsoap 1.1.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/README.markdown +105 -0
- data/VERSION.yml +4 -0
- data/generators/handsoap/USAGE +0 -0
- data/generators/handsoap/handsoap_generator.rb +113 -0
- data/generators/handsoap/templates/DUMMY +0 -0
- data/lib/handsoap.rb +4 -0
- data/lib/handsoap/compiler.rb +182 -0
- data/lib/handsoap/deferred.rb +50 -0
- data/lib/handsoap/http.rb +4 -0
- data/lib/handsoap/http/drivers.rb +24 -0
- data/lib/handsoap/http/drivers/abstract_driver.rb +170 -0
- data/lib/handsoap/http/drivers/curb_driver.rb +41 -0
- data/lib/handsoap/http/drivers/event_machine_driver.rb +46 -0
- data/lib/handsoap/http/drivers/http_client_driver.rb +38 -0
- data/lib/handsoap/http/drivers/mock_driver.rb +39 -0
- data/lib/handsoap/http/drivers/net_http_driver.rb +69 -0
- data/lib/handsoap/http/part.rb +78 -0
- data/lib/handsoap/http/request.rb +63 -0
- data/lib/handsoap/http/response.rb +28 -0
- data/lib/handsoap/parser.rb +241 -0
- data/lib/handsoap/service.rb +481 -0
- data/lib/handsoap/xml_mason.rb +221 -0
- data/lib/handsoap/xml_query_front.rb +320 -0
- metadata +81 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Handsoap
|
4
|
+
module Http
|
5
|
+
|
6
|
+
# Represents a HTTP Request.
|
7
|
+
class Request
|
8
|
+
attr_reader :url, :http_method, :headers, :body, :username, :password
|
9
|
+
attr_writer :body, :http_method
|
10
|
+
def initialize(url, http_method = :get)
|
11
|
+
@url = url
|
12
|
+
@http_method = http_method
|
13
|
+
@headers = {}
|
14
|
+
@body = nil
|
15
|
+
@username = nil
|
16
|
+
@password = nil
|
17
|
+
end
|
18
|
+
def set_auth(username, password)
|
19
|
+
@username = username
|
20
|
+
@password = password
|
21
|
+
end
|
22
|
+
def add_header(key, value)
|
23
|
+
if @headers[key].nil?
|
24
|
+
@headers[key] = []
|
25
|
+
end
|
26
|
+
@headers[key] << value
|
27
|
+
end
|
28
|
+
def set_header(key, value)
|
29
|
+
if value.nil?
|
30
|
+
@headers[key] = nil
|
31
|
+
else
|
32
|
+
@headers[key] = [value]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def inspect
|
36
|
+
"===============\n" +
|
37
|
+
"--- Request ---\n" +
|
38
|
+
"#{http_method.to_s.upcase} #{url}\n" +
|
39
|
+
(
|
40
|
+
if username && password
|
41
|
+
"Auth credentials: #{username}:#{password}\n"
|
42
|
+
else
|
43
|
+
""
|
44
|
+
end
|
45
|
+
) +
|
46
|
+
(
|
47
|
+
if headers.any?
|
48
|
+
"---\n" + headers.map { |key,values| values.map {|value| key + ": " + value + "\n" }.join("") }.join("")
|
49
|
+
else
|
50
|
+
""
|
51
|
+
end
|
52
|
+
) +
|
53
|
+
(
|
54
|
+
if body
|
55
|
+
"---\n" + body
|
56
|
+
else
|
57
|
+
""
|
58
|
+
end
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'handsoap/http/part'
|
3
|
+
|
4
|
+
module Handsoap
|
5
|
+
module Http
|
6
|
+
|
7
|
+
# Represents a HTTP Response.
|
8
|
+
class Response < Part
|
9
|
+
attr_reader :status
|
10
|
+
def initialize(status, headers, body, parts = nil)
|
11
|
+
@status = status.to_i
|
12
|
+
super(headers, body, parts)
|
13
|
+
end
|
14
|
+
def primary_part
|
15
|
+
# Strictly speaking, the main part doesn't need to be first, but until proven otherwise, we'll just assume that.
|
16
|
+
if multipart?
|
17
|
+
parts.first
|
18
|
+
else
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
private
|
23
|
+
def inspect_head
|
24
|
+
"--- Response ---\n" + "HTTP Status: #{status}\n"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'httpclient'
|
3
|
+
require 'openssl'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module Handsoap
|
7
|
+
# Classes for parsing a WSDL.
|
8
|
+
#
|
9
|
+
# Used internally by the generator.
|
10
|
+
module Parser #:nodoc: all
|
11
|
+
class Interface
|
12
|
+
attr_accessor :name, :operations
|
13
|
+
|
14
|
+
def initialize(name, operations = [])
|
15
|
+
@name = name
|
16
|
+
@operations = operations || []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Binding
|
21
|
+
attr_accessor :name, :protocol, :interface, :transport, :style, :encoding, :verb, :actions
|
22
|
+
|
23
|
+
def initialize(name, optional = {})
|
24
|
+
@name = name
|
25
|
+
@actions = optional[:actions] || []
|
26
|
+
@protocol = optional[:protocol]
|
27
|
+
@interface = optional[:interface]
|
28
|
+
@transport = optional[:transport]
|
29
|
+
@style = optional[:style]
|
30
|
+
@encoding = optional[:encoding]
|
31
|
+
@verb = optional[:verb]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Endpoint
|
36
|
+
attr_accessor :name, :protocol, :binding, :url
|
37
|
+
|
38
|
+
def initialize(name, protocol, binding, url)
|
39
|
+
@name = name
|
40
|
+
@protocol = protocol
|
41
|
+
@binding = binding
|
42
|
+
@url = url
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Operation
|
47
|
+
attr_accessor :name, :input, :output
|
48
|
+
|
49
|
+
def initialize(name, optional = {})
|
50
|
+
@name = name
|
51
|
+
@input = optional[:input]
|
52
|
+
@output = optional[:output]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Action
|
57
|
+
attr_accessor :name, :soap_action, :location
|
58
|
+
|
59
|
+
def initialize(name, optional = {})
|
60
|
+
@name = name
|
61
|
+
@soap_action = optional[:soap_action]
|
62
|
+
@location = optional[:location]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Wsdl
|
67
|
+
attr_reader :url
|
68
|
+
|
69
|
+
def initialize(doc, url = "void://")
|
70
|
+
@doc = doc
|
71
|
+
@url = url
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.read(url)
|
75
|
+
if url =~ /^http(s?):/
|
76
|
+
request = ::HTTPClient.new
|
77
|
+
request.ssl_config.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
|
78
|
+
response = request.get(url)
|
79
|
+
xml_src = response.content
|
80
|
+
else
|
81
|
+
xml_src = Kernel.open(url).read
|
82
|
+
end
|
83
|
+
self.new(Nokogiri.XML(xml_src), url)
|
84
|
+
end
|
85
|
+
|
86
|
+
def ns
|
87
|
+
{
|
88
|
+
'wsdl1' => "http://schemas.xmlsoap.org/wsdl/",
|
89
|
+
'wsdl2' => "http://www.w3.org/ns/wsdl/",
|
90
|
+
'soap11' => "http://schemas.xmlsoap.org/wsdl/soap/",
|
91
|
+
'soap12' => "http://schemas.xmlsoap.org/wsdl/soap12/",
|
92
|
+
'http' => "http://schemas.xmlsoap.org/wsdl/http/"
|
93
|
+
}
|
94
|
+
end
|
95
|
+
private :ns
|
96
|
+
|
97
|
+
def protocol_from_ns(node)
|
98
|
+
href = node.namespace.respond_to?(:href) ? node.namespace.href : @doc.namespaces["xmlns:#{node.namespace}"]
|
99
|
+
case href
|
100
|
+
when "http://schemas.xmlsoap.org/wsdl/soap/"
|
101
|
+
:soap11
|
102
|
+
when "http://schemas.xmlsoap.org/wsdl/soap12/"
|
103
|
+
:soap12
|
104
|
+
when "http://schemas.xmlsoap.org/wsdl/http/"
|
105
|
+
:http
|
106
|
+
else
|
107
|
+
raise "Unknown namespace '#{href}'"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
private :protocol_from_ns
|
111
|
+
|
112
|
+
def is_wsdl2?(node)
|
113
|
+
href = node.namespace.respond_to?(:href) ? node.namespace.href : @doc.namespaces["xmlns:#{node.namespace}"]
|
114
|
+
case href
|
115
|
+
when "http://schemas.xmlsoap.org/wsdl/"
|
116
|
+
false
|
117
|
+
when "http://www.w3.org/ns/wsdl/"
|
118
|
+
true
|
119
|
+
else
|
120
|
+
raise "Unknown namespace '#{href}'"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
private :is_wsdl2?
|
124
|
+
|
125
|
+
def service
|
126
|
+
services = @doc.xpath("//wsdl1:service|//wsdl2:service", ns)
|
127
|
+
raise "Expected exactly 1 service in WSDL" if services.length != 1
|
128
|
+
services[0][:name]
|
129
|
+
end
|
130
|
+
|
131
|
+
def interface
|
132
|
+
all_interfaces = self.interfaces
|
133
|
+
if all_interfaces.length != 1
|
134
|
+
# There are more than one portType, so we take a pick
|
135
|
+
all_bindings = self.bindings
|
136
|
+
all_interfaces.each do |interface|
|
137
|
+
b = all_bindings.find {|binding| binding.name == interface.name }
|
138
|
+
if [:soap11, :soap12].include? b.protocol
|
139
|
+
return interface
|
140
|
+
end
|
141
|
+
end
|
142
|
+
raise "Can't find a suitable soap 1.1 or 1.2 interface/portType in WSDL"
|
143
|
+
end
|
144
|
+
all_interfaces.first
|
145
|
+
end
|
146
|
+
|
147
|
+
def target_ns
|
148
|
+
@doc.root[:targetNamespace] || raise("Attribute targetNamespace not defined")
|
149
|
+
end
|
150
|
+
|
151
|
+
def preferred_protocol
|
152
|
+
e = endpoints
|
153
|
+
if e.select { |endpoint| endpoint.protocol == :soap12 }.any?
|
154
|
+
:soap12
|
155
|
+
elsif e.select { |endpoint| endpoint.protocol == :soap11 }.any?
|
156
|
+
:soap11
|
157
|
+
else
|
158
|
+
raise "Can't find any soap 1.1 or soap 1.2 endpoints"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def interfaces
|
163
|
+
@doc.xpath("//wsdl1:portType|//wsdl2:interface", ns).map do |port_type|
|
164
|
+
operations = port_type.xpath("./wsdl1:operation|./wsdl2:operation", ns).map do |operation|
|
165
|
+
if is_wsdl2?(operation)
|
166
|
+
input_node = operation.xpath("./wsdl2:input", ns).first
|
167
|
+
input = input_node ? input_node[:element] : nil
|
168
|
+
output_node = operation.xpath("./wsdl2:output", ns).first
|
169
|
+
output = output_node ? output_node[:element] : nil
|
170
|
+
else
|
171
|
+
input_node = operation.xpath("./wsdl1:input", ns).first
|
172
|
+
input = input_node ? input_node[:message] : nil
|
173
|
+
output_node = operation.xpath("./wsdl1:output", ns).first
|
174
|
+
output = output_node ? output_node[:message] : nil
|
175
|
+
end
|
176
|
+
Operation.new(operation[:name], :input => input, :output => output)
|
177
|
+
end
|
178
|
+
Interface.new(port_type[:name], operations)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def endpoints
|
183
|
+
@doc.xpath("//wsdl1:service/wsdl1:port|//wsdl2:service/wsdl2:endpoint", ns).map do |port|
|
184
|
+
binding = port[:binding]
|
185
|
+
if is_wsdl2?(port)
|
186
|
+
location = port[:address]
|
187
|
+
protocol = :binding
|
188
|
+
else
|
189
|
+
address = port.xpath("./soap11:address|./soap12:address|./http:address", ns).first
|
190
|
+
location = address[:location]
|
191
|
+
protocol = protocol_from_ns(address)
|
192
|
+
end
|
193
|
+
Endpoint.new(port[:name], protocol, binding, location)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def bindings
|
198
|
+
@doc.xpath("//wsdl1:binding|//wsdl2:binding", ns).map do |binding|
|
199
|
+
raise "WSDL 2.0 not supported" if is_wsdl2?(binding)
|
200
|
+
soap_binding = binding.xpath("./soap11:binding|./soap12:binding|./http:binding", ns).first
|
201
|
+
protocol = protocol_from_ns(soap_binding)
|
202
|
+
actions = []
|
203
|
+
style = nil
|
204
|
+
encoding = nil
|
205
|
+
actions = binding.xpath("./wsdl1:operation", ns).map do |operation|
|
206
|
+
soap_operation = operation.xpath("./soap11:operation|./soap12:operation|./http:operation", ns).first
|
207
|
+
if soap_operation[:style]
|
208
|
+
raise "Mixed styles not supported" if style && style != soap_operation[:style]
|
209
|
+
style = soap_operation[:style]
|
210
|
+
end
|
211
|
+
xquery = []
|
212
|
+
['soap11', 'soap12', 'http'].each do |version|
|
213
|
+
['input', 'output'].each do |message_name|
|
214
|
+
['header', 'body'].each do |part_name|
|
215
|
+
xquery << "./wsdl1:#{message_name}/#{version}:#{part_name}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
operation.xpath(xquery.join('|'), ns).each do |thing|
|
220
|
+
raise "Mixed encodings not supported" if encoding && encoding != thing[:use]
|
221
|
+
encoding = thing[:use]
|
222
|
+
end
|
223
|
+
Action.new(
|
224
|
+
operation[:name],
|
225
|
+
:soap_action => soap_operation[:soapAction],
|
226
|
+
:location => soap_operation[:location])
|
227
|
+
end
|
228
|
+
Binding.new(
|
229
|
+
binding[:name],
|
230
|
+
:protocol => protocol,
|
231
|
+
:interface => binding[:type],
|
232
|
+
:transport => soap_binding[:transport],
|
233
|
+
:style => style,
|
234
|
+
:encoding => encoding,
|
235
|
+
:verb => soap_binding[:verb],
|
236
|
+
:actions => actions)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,481 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'time'
|
3
|
+
require 'handsoap/xml_mason'
|
4
|
+
require 'handsoap/xml_query_front'
|
5
|
+
require 'handsoap/http'
|
6
|
+
require 'handsoap/deferred'
|
7
|
+
|
8
|
+
module Handsoap
|
9
|
+
|
10
|
+
def self.http_driver
|
11
|
+
@http_driver || (self.http_driver = :curb)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.http_driver=(driver)
|
15
|
+
@http_driver = driver
|
16
|
+
Handsoap::Http.drivers[driver].load!
|
17
|
+
return driver
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.xml_query_driver
|
21
|
+
@xml_query_driver || (self.xml_query_driver = :nokogiri)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.xml_query_driver=(driver)
|
25
|
+
@xml_query_driver = Handsoap::XmlQueryFront.load_driver!(driver)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Fault < StandardError
|
29
|
+
attr_reader :code, :reason, :details
|
30
|
+
def initialize(code, reason, details)
|
31
|
+
@code = code
|
32
|
+
@reason = reason
|
33
|
+
@details = details
|
34
|
+
end
|
35
|
+
def to_s
|
36
|
+
"Handsoap::Fault { :code => '#{@code}', :reason => '#{@reason}' }"
|
37
|
+
end
|
38
|
+
def self.from_xml(node, options = { :namespace => nil })
|
39
|
+
if not options[:namespace]
|
40
|
+
raise "Missing option :namespace"
|
41
|
+
end
|
42
|
+
ns = { 'env' => options[:namespace] }
|
43
|
+
fault_code = node.xpath('./env:Code/env:Value', ns).to_s
|
44
|
+
unless fault_code
|
45
|
+
fault_code = node.xpath('./faultcode', ns).to_s
|
46
|
+
end
|
47
|
+
reason = node.xpath('./env:Reason/env:Text[1]', ns).to_s
|
48
|
+
unless reason
|
49
|
+
reason = node.xpath('./faultstring', ns).to_s
|
50
|
+
end
|
51
|
+
details = node.xpath('./detail/*', ns)
|
52
|
+
self.new(fault_code, reason, details)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class HttpError < StandardError
|
57
|
+
attr_reader :response
|
58
|
+
def initialize(response)
|
59
|
+
@response = response
|
60
|
+
super()
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class SoapResponse
|
65
|
+
attr_reader :document, :http_response
|
66
|
+
def initialize(document, http_response)
|
67
|
+
@document = document
|
68
|
+
@http_response = http_response
|
69
|
+
end
|
70
|
+
def method_missing(method, *args, &block)
|
71
|
+
if @document.respond_to?(method)
|
72
|
+
@document.__send__ method, *args, &block
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class AsyncDispatch
|
80
|
+
attr_reader :action, :options, :request_block, :response_block
|
81
|
+
def request(action, options = { :soap_action => :auto }, &block)
|
82
|
+
@action = action
|
83
|
+
@options = options
|
84
|
+
@request_block = block
|
85
|
+
end
|
86
|
+
def response(&block)
|
87
|
+
@response_block = block
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Service
|
92
|
+
@@logger = nil
|
93
|
+
def self.logger=(io)
|
94
|
+
@@logger = io
|
95
|
+
end
|
96
|
+
# Sets the endpoint for the service.
|
97
|
+
# Arguments:
|
98
|
+
# :uri => endpoint uri of the service. Required.
|
99
|
+
# :version => 1 | 2
|
100
|
+
# :envelope_namespace => Namespace of SOAP-envelope
|
101
|
+
# :request_content_type => Content-Type of HTTP request.
|
102
|
+
# You must supply either :version or both :envelope_namspace and :request_content_type.
|
103
|
+
# :version is simply a shortcut for default values.
|
104
|
+
def self.endpoint(args = {})
|
105
|
+
@uri = args[:uri] || raise("Missing option :uri")
|
106
|
+
if args[:version]
|
107
|
+
soap_namespace = { 1 => 'http://schemas.xmlsoap.org/soap/envelope/', 2 => 'http://www.w3.org/2003/05/soap-envelope' }
|
108
|
+
raise("Unknown protocol version '#{@protocol_version.inspect}'") if soap_namespace[args[:version]].nil?
|
109
|
+
@envelope_namespace = soap_namespace[args[:version]]
|
110
|
+
@request_content_type = args[:version] == 1 ? "text/xml" : "application/soap+xml"
|
111
|
+
end
|
112
|
+
@envelope_namespace = args[:envelope_namespace] unless args[:envelope_namespace].nil?
|
113
|
+
@request_content_type = args[:request_content_type] unless args[:request_content_type].nil?
|
114
|
+
if @envelope_namespace.nil? || @request_content_type.nil?
|
115
|
+
raise("Missing option :envelope_namespace, :request_content_type or :version")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
def self.envelope_namespace
|
119
|
+
@envelope_namespace
|
120
|
+
end
|
121
|
+
def self.request_content_type
|
122
|
+
@request_content_type
|
123
|
+
end
|
124
|
+
def self.uri
|
125
|
+
@uri
|
126
|
+
end
|
127
|
+
@@instance = {}
|
128
|
+
def self.instance
|
129
|
+
@@instance[self.to_s] ||= self.new
|
130
|
+
end
|
131
|
+
def self.method_missing(method, *args, &block)
|
132
|
+
if instance.respond_to?(method)
|
133
|
+
instance.__send__ method, *args, &block
|
134
|
+
else
|
135
|
+
super
|
136
|
+
end
|
137
|
+
end
|
138
|
+
def envelope_namespace
|
139
|
+
self.class.envelope_namespace
|
140
|
+
end
|
141
|
+
def request_content_type
|
142
|
+
self.class.request_content_type
|
143
|
+
end
|
144
|
+
def uri
|
145
|
+
self.class.uri
|
146
|
+
end
|
147
|
+
# Creates an XML document and sends it over HTTP.
|
148
|
+
#
|
149
|
+
# +action+ is the QName of the rootnode of the envelope.
|
150
|
+
#
|
151
|
+
# +options+ currently takes one option +:soap_action+, which can be one of:
|
152
|
+
#
|
153
|
+
# :auto sends a SOAPAction http header, deduced from the action name. (This is the default)
|
154
|
+
#
|
155
|
+
# +String+ sends a SOAPAction http header.
|
156
|
+
#
|
157
|
+
# +nil+ sends no SOAPAction http header.
|
158
|
+
def invoke(action, options = { :soap_action => :auto }, &block) # :yields: Handsoap::XmlMason::Element
|
159
|
+
if action
|
160
|
+
if options.kind_of? String
|
161
|
+
options = { :soap_action => options }
|
162
|
+
end
|
163
|
+
if options[:soap_action] == :auto
|
164
|
+
options[:soap_action] = action.gsub(/^.+:/, "")
|
165
|
+
elsif options[:soap_action] == :none
|
166
|
+
options[:soap_action] = nil
|
167
|
+
end
|
168
|
+
doc = make_envelope do |body|
|
169
|
+
body.add action
|
170
|
+
end
|
171
|
+
if block_given?
|
172
|
+
yield doc.find(action)
|
173
|
+
end
|
174
|
+
# ready to dispatch
|
175
|
+
headers = {
|
176
|
+
"Content-Type" => "#{self.request_content_type};charset=UTF-8"
|
177
|
+
}
|
178
|
+
headers["SOAPAction"] = options[:soap_action] unless options[:soap_action].nil?
|
179
|
+
on_before_dispatch
|
180
|
+
request = make_http_request(self.uri, doc.to_s, headers)
|
181
|
+
driver = Handsoap::Http.drivers[Handsoap.http_driver].new
|
182
|
+
response = driver.send_http_request(request)
|
183
|
+
parse_http_response(response)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Async invocation
|
188
|
+
#
|
189
|
+
# Creates an XML document and sends it over HTTP.
|
190
|
+
#
|
191
|
+
# +user_block+ Block from userland
|
192
|
+
def async(user_block, &block) # :yields: Handsoap::AsyncDispatch
|
193
|
+
# Setup userland handlers
|
194
|
+
userland = Handsoap::Deferred.new
|
195
|
+
user_block.call(userland)
|
196
|
+
raise "Missing :callback" unless userland.has_callback?
|
197
|
+
raise "Missing :errback" unless userland.has_errback?
|
198
|
+
# Setup service level handlers
|
199
|
+
dispatcher = Handsoap::AsyncDispatch.new
|
200
|
+
yield dispatcher
|
201
|
+
raise "Missing :request_block" unless dispatcher.request_block
|
202
|
+
raise "Missing :response_block" unless dispatcher.response_block
|
203
|
+
# Done with the external configuration .. let's roll
|
204
|
+
action = dispatcher.action
|
205
|
+
options = dispatcher.options
|
206
|
+
if action #TODO: What if no action ?!?
|
207
|
+
if options.kind_of? String
|
208
|
+
options = { :soap_action => options }
|
209
|
+
end
|
210
|
+
if options[:soap_action] == :auto
|
211
|
+
options[:soap_action] = action.gsub(/^.+:/, "")
|
212
|
+
elsif options[:soap_action] == :none
|
213
|
+
options[:soap_action] = nil
|
214
|
+
end
|
215
|
+
doc = make_envelope do |body|
|
216
|
+
body.add action
|
217
|
+
end
|
218
|
+
dispatcher.request_block.call doc.find(action)
|
219
|
+
# ready to dispatch
|
220
|
+
headers = {
|
221
|
+
"Content-Type" => "#{self.request_content_type};charset=UTF-8"
|
222
|
+
}
|
223
|
+
headers["SOAPAction"] = options[:soap_action] unless options[:soap_action].nil?
|
224
|
+
on_before_dispatch
|
225
|
+
request = make_http_request(self.uri, doc.to_s, headers)
|
226
|
+
driver = Handsoap::Http.drivers[Handsoap.http_driver].new
|
227
|
+
if driver.respond_to? :send_http_request_async
|
228
|
+
deferred = driver.send_http_request_async(request)
|
229
|
+
else
|
230
|
+
# Fake async for sync-only drivers
|
231
|
+
deferred = Handsoap::Deferred.new
|
232
|
+
begin
|
233
|
+
deferred.trigger_callback driver.send_http_request(request)
|
234
|
+
rescue
|
235
|
+
deferred.trigger_errback $!
|
236
|
+
end
|
237
|
+
end
|
238
|
+
deferred.callback do |http_response|
|
239
|
+
begin
|
240
|
+
# Parse response
|
241
|
+
response_document = parse_http_response(http_response)
|
242
|
+
# Transform response
|
243
|
+
result = dispatcher.response_block.call(response_document)
|
244
|
+
# Yield to userland code
|
245
|
+
userland.trigger_callback(result)
|
246
|
+
rescue
|
247
|
+
userland.trigger_errback $!
|
248
|
+
end
|
249
|
+
end
|
250
|
+
# Pass driver level errors on
|
251
|
+
deferred.errback do |ex|
|
252
|
+
userland.trigger_errback(ex)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
|
258
|
+
# Hook that is called when a new request document is created.
|
259
|
+
#
|
260
|
+
# You can override this to add namespaces and other elements that are common to all requests (Such as authentication).
|
261
|
+
def on_create_document(doc)
|
262
|
+
end
|
263
|
+
# Hook that is called before the message is dispatched.
|
264
|
+
#
|
265
|
+
# You can override this to provide filtering and logging.
|
266
|
+
def on_before_dispatch
|
267
|
+
end
|
268
|
+
# Hook that is called after the http_client is created.
|
269
|
+
#
|
270
|
+
# You can override this to customize the http_client
|
271
|
+
def on_after_create_http_request(http_request)
|
272
|
+
end
|
273
|
+
# Hook that is called when there is a response.
|
274
|
+
#
|
275
|
+
# You can override this to register common namespaces, useful for parsing the document.
|
276
|
+
def on_response_document(doc)
|
277
|
+
end
|
278
|
+
# Hook that is called if there is a HTTP level error.
|
279
|
+
#
|
280
|
+
# Default behaviour is to raise an error.
|
281
|
+
def on_http_error(response)
|
282
|
+
raise HttpError, response
|
283
|
+
end
|
284
|
+
# Hook that is called if the dispatch returns a +Fault+.
|
285
|
+
#
|
286
|
+
# Default behaviour is to raise the Fault, but you can override this to provide logging and more fine-grained handling faults.
|
287
|
+
#
|
288
|
+
# See also: parse_soap_fault
|
289
|
+
def on_fault(fault)
|
290
|
+
raise fault
|
291
|
+
end
|
292
|
+
# Hook that is called if the response does not contain a valid SOAP enevlope.
|
293
|
+
#
|
294
|
+
# Default behaviour is to raise an error
|
295
|
+
#
|
296
|
+
# Note that if your service has operations that are one-way, you shouldn't raise an error here.
|
297
|
+
# This is however a fairly exotic case, so that is why the default behaviour is to raise an error.
|
298
|
+
def on_missing_document(response)
|
299
|
+
raise "The response is not a valid SOAP envelope"
|
300
|
+
end
|
301
|
+
|
302
|
+
def debug(message = nil) #:nodoc:
|
303
|
+
if @@logger
|
304
|
+
if message
|
305
|
+
@@logger.puts(message)
|
306
|
+
end
|
307
|
+
if block_given?
|
308
|
+
yield @@logger
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
def make_http_request(uri, post_body, headers)
|
314
|
+
request = Handsoap::Http::Request.new(uri, :post)
|
315
|
+
headers.each do |key, value|
|
316
|
+
request.add_header(key, value)
|
317
|
+
end
|
318
|
+
request.body = post_body
|
319
|
+
debug do |logger|
|
320
|
+
logger.puts request.inspect
|
321
|
+
end
|
322
|
+
on_after_create_http_request(request)
|
323
|
+
request
|
324
|
+
end
|
325
|
+
|
326
|
+
# Start the parsing pipe-line.
|
327
|
+
# There are various stages and hooks for each, so that you can override those in your service classes.
|
328
|
+
def parse_http_response(response)
|
329
|
+
debug do |logger|
|
330
|
+
logger.puts(response.inspect do |body|
|
331
|
+
Handsoap.pretty_format_envelope(body).chomp
|
332
|
+
end)
|
333
|
+
end
|
334
|
+
xml_document = parse_soap_response_document(response.primary_part.body)
|
335
|
+
soap_fault = parse_soap_fault(xml_document)
|
336
|
+
# Is the response a soap-fault?
|
337
|
+
unless soap_fault.nil?
|
338
|
+
return on_fault(soap_fault)
|
339
|
+
end
|
340
|
+
# Does the http-status indicate an error?
|
341
|
+
if response.status >= 400
|
342
|
+
return on_http_error(response)
|
343
|
+
end
|
344
|
+
# Does the response contain a valid xml-document?
|
345
|
+
if xml_document.nil?
|
346
|
+
return on_missing_document(response)
|
347
|
+
end
|
348
|
+
# Everything seems in order.
|
349
|
+
on_response_document(xml_document)
|
350
|
+
return SoapResponse.new(xml_document, response)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Creates a standard SOAP envelope and yields the +Body+ element.
|
354
|
+
def make_envelope # :yields: Handsoap::XmlMason::Element
|
355
|
+
doc = XmlMason::Document.new do |doc|
|
356
|
+
doc.alias 'env', self.envelope_namespace
|
357
|
+
doc.add "env:Envelope" do |env|
|
358
|
+
env.add "*:Header"
|
359
|
+
env.add "*:Body"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
self.class.fire_on_create_document doc # deprecated .. use instance method
|
363
|
+
on_create_document(doc)
|
364
|
+
if block_given?
|
365
|
+
yield doc.find("Body")
|
366
|
+
end
|
367
|
+
return doc
|
368
|
+
end
|
369
|
+
|
370
|
+
# String -> [XmlDocument | nil]
|
371
|
+
def parse_soap_response_document(http_body)
|
372
|
+
begin
|
373
|
+
Handsoap::XmlQueryFront.parse_string(http_body, Handsoap.xml_query_driver)
|
374
|
+
rescue Handsoap::XmlQueryFront::ParseError => ex
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# XmlDocument -> [Fault | nil]
|
380
|
+
def parse_soap_fault(document)
|
381
|
+
unless document.nil?
|
382
|
+
node = document.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => self.envelope_namespace }).first
|
383
|
+
Fault.from_xml(node, :namespace => self.envelope_namespace) unless node.nil?
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def self.pretty_format_envelope(xml_string)
|
389
|
+
if /^<.*:Envelope/.match(xml_string)
|
390
|
+
begin
|
391
|
+
doc = Handsoap::XmlQueryFront.parse_string(xml_string, Handsoap.xml_query_driver)
|
392
|
+
rescue
|
393
|
+
return xml_string
|
394
|
+
end
|
395
|
+
return doc.to_xml
|
396
|
+
# return "\n\e[1;33m" + doc.to_s + "\e[0m"
|
397
|
+
end
|
398
|
+
return xml_string
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Legacy/BC code here. This shouldn't be used in new applications.
|
403
|
+
module Handsoap
|
404
|
+
class Service
|
405
|
+
# Registers a simple method mapping without any arguments and no parsing of response.
|
406
|
+
#
|
407
|
+
# This is deprecated
|
408
|
+
def self.map_method(mapping)
|
409
|
+
if @mapping.nil?
|
410
|
+
@mapping = {}
|
411
|
+
end
|
412
|
+
@mapping.merge! mapping
|
413
|
+
end
|
414
|
+
def self.get_mapping(name)
|
415
|
+
@mapping[name] if @mapping
|
416
|
+
end
|
417
|
+
def method_missing(method, *args, &block)
|
418
|
+
action = self.class.get_mapping(method)
|
419
|
+
if action
|
420
|
+
invoke(action, *args, &block)
|
421
|
+
else
|
422
|
+
super
|
423
|
+
end
|
424
|
+
end
|
425
|
+
# Registers a block to call when a request document is created.
|
426
|
+
#
|
427
|
+
# This is deprecated, in favour of #on_create_document
|
428
|
+
def self.on_create_document(&block)
|
429
|
+
@create_document_callback = block
|
430
|
+
end
|
431
|
+
def self.fire_on_create_document(doc)
|
432
|
+
if @create_document_callback
|
433
|
+
@create_document_callback.call doc
|
434
|
+
end
|
435
|
+
end
|
436
|
+
private
|
437
|
+
# Helper to serialize a node into a ruby string
|
438
|
+
#
|
439
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_s
|
440
|
+
def xml_to_str(node, xquery = nil)
|
441
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
442
|
+
return if n.nil?
|
443
|
+
n.to_s
|
444
|
+
end
|
445
|
+
alias_method :xml_to_s, :xml_to_str
|
446
|
+
# Helper to serialize a node into a ruby integer
|
447
|
+
#
|
448
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_i
|
449
|
+
def xml_to_int(node, xquery = nil)
|
450
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
451
|
+
return if n.nil?
|
452
|
+
n.to_s.to_i
|
453
|
+
end
|
454
|
+
alias_method :xml_to_i, :xml_to_int
|
455
|
+
# Helper to serialize a node into a ruby float
|
456
|
+
#
|
457
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_f
|
458
|
+
def xml_to_float(node, xquery = nil)
|
459
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
460
|
+
return if n.nil?
|
461
|
+
n.to_s.to_f
|
462
|
+
end
|
463
|
+
alias_method :xml_to_f, :xml_to_float
|
464
|
+
# Helper to serialize a node into a ruby boolean
|
465
|
+
#
|
466
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_boolean
|
467
|
+
def xml_to_bool(node, xquery = nil)
|
468
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
469
|
+
return if n.nil?
|
470
|
+
n.to_s == "true"
|
471
|
+
end
|
472
|
+
# Helper to serialize a node into a ruby Time object
|
473
|
+
#
|
474
|
+
# *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_date
|
475
|
+
def xml_to_date(node, xquery = nil)
|
476
|
+
n = xquery ? node.xpath(xquery, ns).first : node
|
477
|
+
return if n.nil?
|
478
|
+
Time.iso8601(n.to_s)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|