pgericson-handsoap 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Handsoap
4
+ module Http
5
+
6
+ # Represents a HTTP Part.
7
+ # For simple HTTP-requests there is only one part, which is the response.
8
+ class Part
9
+ attr_reader :headers, :body, :parts
10
+
11
+ def initialize(headers, body, parts = nil)
12
+ @headers = headers
13
+ @body = body
14
+ @parts = parts
15
+ end
16
+
17
+ # Returns a header.
18
+ # Returns String | Array | nil
19
+ def [](key)
20
+ key.to_s.downcase!
21
+ (@headers[key] && @headers[key].length == 1) ? @headers[key].first : @headers[key]
22
+ end
23
+
24
+ # Returns the mime-type part of the content-type header
25
+ def mime_type
26
+ @headers['content-type'].first.match(/^[^;]+/).to_s if @headers['content-type']
27
+ end
28
+
29
+ # Returns the charset part of the content-type header
30
+ def charset
31
+ if @headers['content-type']
32
+ match_data = @headers['content-type'].first.match(/^[^;]+; charset=([^;]+)/)
33
+ if match_data
34
+ match_data[1].to_s
35
+ end
36
+ end
37
+ end
38
+
39
+ def multipart?
40
+ !! @parts
41
+ end
42
+
43
+ def inspect(&block)
44
+ str = inspect_head
45
+ if headers.any?
46
+ str << headers.map { |key,values| values.map {|value| normalize_header_key(key) + ": " + value + "\n" }.join("") }.join("")
47
+ end
48
+ if body
49
+ if multipart?
50
+ if block_given?
51
+ str << parts.map{|part| part.inspect(&block) }.join("")
52
+ else
53
+ str << parts.map{|part| part.inspect }.join("")
54
+ end
55
+ elsif body
56
+ str << "---\n"
57
+ if block_given?
58
+ str << yield(body)
59
+ else
60
+ str << body
61
+ end
62
+ str << "\n---"
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def inspect_head
70
+ "--- Part ---\n"
71
+ end
72
+
73
+ def normalize_header_key(key)
74
+ key.split("-").map{|s| s.downcase.capitalize }.join("-")
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,73 @@
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, :trust_ca_file, :client_cert_file, :client_cert_key_file
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
+ @trust_ca_file = nil
18
+ @client_cert_file = nil
19
+ @client_cert_key_file = nil
20
+ end
21
+ def set_trust_ca_file(ca_file)
22
+ @trust_ca_file = ca_file
23
+ end
24
+ def set_client_cert_files(client_cert_file,client_cert_key_file)
25
+ @client_cert_file = client_cert_file
26
+ @client_cert_key_file = client_cert_key_file
27
+ end
28
+ def set_auth(username, password)
29
+ @username = username
30
+ @password = password
31
+ end
32
+ def add_header(key, value)
33
+ if @headers[key].nil?
34
+ @headers[key] = []
35
+ end
36
+ @headers[key] << value
37
+ end
38
+ def set_header(key, value)
39
+ if value.nil?
40
+ @headers[key] = nil
41
+ else
42
+ @headers[key] = [value]
43
+ end
44
+ end
45
+ def inspect
46
+ "===============\n" +
47
+ "--- Request ---\n" +
48
+ "#{http_method.to_s.upcase} #{url}\n" +
49
+ (
50
+ if username && password
51
+ "Auth credentials: #{username}:#{password}\n"
52
+ else
53
+ ""
54
+ end
55
+ ) +
56
+ (
57
+ if headers.any?
58
+ "---\n" + headers.map { |key,values| values.map {|value| key + ": " + value + "\n" }.join("") }.join("")
59
+ else
60
+ ""
61
+ end
62
+ ) +
63
+ (
64
+ if body
65
+ "---\n" + body
66
+ else
67
+ ""
68
+ end
69
+ )
70
+ end
71
+ end
72
+ end
73
+ 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,573 @@
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
+ # Sets the timeout
29
+ def self.timeout=(timeout)
30
+ @timeout = timeout
31
+ end
32
+
33
+ # fetches the timeout
34
+ # the default timeout is set to 60seconds
35
+ def self.timeout
36
+ @timeout || (self.timeout = 60)
37
+ end
38
+
39
+ # Tell Handsoap to follow redirects
40
+ def self.follow_redirects!
41
+ @follow_redirects = true
42
+ end
43
+
44
+ # Check whether Handsoap should follow redirects
45
+ def self.follow_redirects?
46
+ @follow_redirects || false
47
+ end
48
+
49
+ # Sets the max number of redirects
50
+ def self.max_redirects=(max_redirects)
51
+ @max_redirects = max_redirects
52
+ end
53
+
54
+ # Fetches the max number of redirects
55
+ # The default is 1
56
+ def self.max_redirects
57
+ @max_redirects || (self.max_redirects = 1)
58
+ end
59
+
60
+ # Wraps SOAP errors in a standard class.
61
+ class Fault < StandardError
62
+ attr_reader :code, :reason, :details
63
+
64
+ def initialize(code, reason, details)
65
+ @code = code
66
+ @reason = reason
67
+ @details = details
68
+ end
69
+
70
+ def to_s
71
+ "Handsoap::Fault { :code => '#{@code}', :reason => '#{@reason}' }"
72
+ end
73
+
74
+ def self.from_xml(node, options = { :namespace => nil })
75
+ if not options[:namespace]
76
+ raise "Missing option :namespace"
77
+ end
78
+
79
+ ns = { 'env' => options[:namespace] }
80
+
81
+ # tries to find SOAP1.2 fault code
82
+ fault_code = node.xpath("./env:Code/env:Value", ns).to_s
83
+
84
+ # if no SOAP1.2 fault code was found, try the SOAP1.1 way
85
+ unless fault_code
86
+ fault_code = node.xpath('./faultcode', ns).to_s
87
+
88
+ # if fault_code is blank, add the namespace and try again
89
+ unless fault_code
90
+ fault_code = node.xpath("//env:faultcode", ns).to_s
91
+ end
92
+ end
93
+
94
+ # tries to find SOAP1.2 reason
95
+ reason = node.xpath("./env:Reason/env:Text[1]", ns).to_s
96
+
97
+ # if no SOAP1.2 faultstring was found, try the SOAP1.1 way
98
+ unless reason
99
+ reason = node.xpath('./faultstring', ns).to_s
100
+
101
+ # if reason is blank, add the namespace and try again
102
+ unless reason
103
+ reason = node.xpath("//env:faultstring", ns).to_s
104
+ end
105
+ end
106
+
107
+ details = node.xpath('./detail/*', ns)
108
+ self.new(fault_code, reason, details)
109
+ end
110
+ end
111
+
112
+ class HttpError < StandardError
113
+ attr_reader :response
114
+ def initialize(response)
115
+ @response = response
116
+ super()
117
+ end
118
+ end
119
+
120
+ class SoapResponse
121
+ attr_reader :document, :http_response
122
+ def initialize(document, http_response)
123
+ @document = document
124
+ @http_response = http_response
125
+ end
126
+ def method_missing(method, *args, &block)
127
+ if @document.respond_to?(method)
128
+ @document.__send__ method, *args, &block
129
+ else
130
+ super
131
+ end
132
+ end
133
+ end
134
+
135
+ class AsyncDispatch
136
+ attr_reader :action, :options, :request_block, :response_block
137
+ def request(action, options = { :soap_action => :auto }, &block)
138
+ @action = action
139
+ @options = options
140
+ @request_block = block
141
+ end
142
+ def response(&block)
143
+ @response_block = block
144
+ end
145
+ end
146
+
147
+ class Service
148
+ @@logger = nil
149
+ def self.logger=(io)
150
+ @@logger = io
151
+ end
152
+ # Sets the endpoint for the service.
153
+ # Arguments:
154
+ # :uri => endpoint uri of the service. Required.
155
+ # :version => 1 | 2
156
+ # :envelope_namespace => Namespace of SOAP-envelope
157
+ # :request_content_type => Content-Type of HTTP request.
158
+ # You must supply either :version or both :envelope_namspace and :request_content_type.
159
+ # :version is simply a shortcut for default values.
160
+ def self.endpoint(args = {})
161
+ @uri = args[:uri] || raise("Missing option :uri")
162
+ if args[:version]
163
+ soap_namespace = { 1 => 'http://schemas.xmlsoap.org/soap/envelope/', 2 => 'http://www.w3.org/2003/05/soap-envelope' }
164
+ raise("Unknown protocol version '#{@protocol_version.inspect}'") if soap_namespace[args[:version]].nil?
165
+ @envelope_namespace = soap_namespace[args[:version]]
166
+ @request_content_type = args[:version] == 1 ? "text/xml" : "application/soap+xml"
167
+ end
168
+ @envelope_namespace = args[:envelope_namespace] unless args[:envelope_namespace].nil?
169
+ @request_content_type = args[:request_content_type] unless args[:request_content_type].nil?
170
+ if @envelope_namespace.nil? || @request_content_type.nil?
171
+ raise("Missing option :envelope_namespace, :request_content_type or :version")
172
+ end
173
+ end
174
+ def self.envelope_namespace
175
+ @envelope_namespace
176
+ end
177
+ def self.request_content_type
178
+ @request_content_type
179
+ end
180
+ def self.uri
181
+ @uri
182
+ end
183
+ @@instance = {}
184
+ def self.instance
185
+ @@instance[self.to_s] ||= self.new
186
+ end
187
+ def self.method_missing(method, *args, &block)
188
+ if instance.respond_to?(method)
189
+ instance.__send__ method, *args, &block
190
+ else
191
+ super
192
+ end
193
+ end
194
+ def envelope_namespace
195
+ self.class.envelope_namespace
196
+ end
197
+ def request_content_type
198
+ self.class.request_content_type
199
+ end
200
+ def uri
201
+ self.class.uri
202
+ end
203
+ def http_driver_instance
204
+ Handsoap::Http.drivers[Handsoap.http_driver].new
205
+ end
206
+ # Creates an XML document and sends it over HTTP.
207
+ #
208
+ # +action+ is the QName of the rootnode of the envelope.
209
+ #
210
+ # +options+ currently takes one option +:soap_action+, which can be one of:
211
+ #
212
+ # :auto sends a SOAPAction http header, deduced from the action name. (This is the default)
213
+ #
214
+ # +String+ sends a SOAPAction http header.
215
+ #
216
+ # +nil+ sends no SOAPAction http header.
217
+ def invoke(action, options = { :soap_action => :auto }, &block) # :yields: Handsoap::XmlMason::Element
218
+ if action
219
+ if options.kind_of? String
220
+ options = { :soap_action => options }
221
+ end
222
+ if options[:soap_action] == :auto
223
+ options[:soap_action] = action.gsub(/^.+:/, "")
224
+ elsif options[:soap_action] == :none
225
+ options[:soap_action] = nil
226
+ end
227
+ doc = make_envelope do |body,header|
228
+ if options[:soap_header]
229
+ iterate_hash(header, options[:soap_header])
230
+ end
231
+
232
+ body.add action
233
+ if options[:soap_body]
234
+ iterate_hash(body, options[:soap_body])
235
+ end
236
+ end
237
+ if block_given?
238
+ yield doc.find(action)
239
+ end
240
+ # ready to dispatch
241
+ headers = {
242
+ "Content-Type" => "#{self.request_content_type};charset=UTF-8"
243
+ }
244
+ headers["SOAPAction"] = options[:soap_action] unless options[:soap_action].nil?
245
+ on_before_dispatch
246
+ request = make_http_request(self.uri, doc.to_s, headers,options[:http_options])
247
+ response = http_driver_instance.send_http_request(request)
248
+ parse_http_response(response)
249
+ end
250
+ end
251
+
252
+
253
+
254
+ # Async invocation
255
+ #
256
+ # Creates an XML document and sends it over HTTP.
257
+ #
258
+ # +user_block+ Block from userland
259
+ def async(user_block, &block) # :yields: Handsoap::AsyncDispatch
260
+ # Setup userland handlers
261
+ userland = Handsoap::Deferred.new
262
+ user_block.call(userland)
263
+ raise "Missing :callback" unless userland.has_callback?
264
+ raise "Missing :errback" unless userland.has_errback?
265
+ # Setup service level handlers
266
+ dispatcher = Handsoap::AsyncDispatch.new
267
+ yield dispatcher
268
+ raise "Missing :request_block" unless dispatcher.request_block
269
+ raise "Missing :response_block" unless dispatcher.response_block
270
+ # Done with the external configuration .. let's roll
271
+ action = dispatcher.action
272
+ options = dispatcher.options
273
+ if action #TODO: What if no action ?!?
274
+ if options.kind_of? String
275
+ options = { :soap_action => options }
276
+ end
277
+ if options[:soap_action] == :auto
278
+ options[:soap_action] = action.gsub(/^.+:/, "")
279
+ elsif options[:soap_action] == :none
280
+ options[:soap_action] = nil
281
+ end
282
+ doc = make_envelope do |body|
283
+ body.add action
284
+ end
285
+ dispatcher.request_block.call doc.find(action)
286
+ # ready to dispatch
287
+ headers = {
288
+ "Content-Type" => "#{self.request_content_type};charset=UTF-8"
289
+ }
290
+ headers["SOAPAction"] = options[:soap_action] unless options[:soap_action].nil?
291
+ on_before_dispatch
292
+ request = make_http_request(self.uri, doc.to_s, headers)
293
+ driver = self.http_driver_instance
294
+ if driver.respond_to? :send_http_request_async
295
+ deferred = driver.send_http_request_async(request)
296
+ else
297
+ # Fake async for sync-only drivers
298
+ deferred = Handsoap::Deferred.new
299
+ begin
300
+ deferred.trigger_callback driver.send_http_request(request)
301
+ rescue
302
+ deferred.trigger_errback $!
303
+ end
304
+ end
305
+ deferred.callback do |http_response|
306
+ begin
307
+ # Parse response
308
+ response_document = parse_http_response(http_response)
309
+ # Transform response
310
+ result = dispatcher.response_block.call(response_document)
311
+ # Yield to userland code
312
+ userland.trigger_callback(result)
313
+ rescue
314
+ userland.trigger_errback $!
315
+ end
316
+ end
317
+ # Pass driver level errors on
318
+ deferred.errback do |ex|
319
+ userland.trigger_errback(ex)
320
+ end
321
+ end
322
+ return nil
323
+ end
324
+
325
+
326
+
327
+ #Used to iterate over a Hash, that can include Hash, Array or String/Float/Integer etc and insert it in the correct element.
328
+ def iterate_hash(element, hash)
329
+ hash.each do |name,value|
330
+ if value.is_a?(Hash)
331
+ element.add(name){|subelement| iterate_hash(subelement, value)}
332
+ elsif value.is_a?(Array)
333
+ value.each do |item|
334
+ element.add(name, iterate_hash(element,item)) if item.is_a?(Hash)
335
+ end
336
+ else
337
+ puts "#{name.to_s}: #{name.class.to_s} - #{value.to_s}:#{value.class.to_s}"
338
+ element.add name, value.to_s
339
+ end
340
+ end
341
+ end
342
+
343
+ # Hook that is called when a new request document is created.
344
+ #
345
+ # You can override this to add namespaces and other elements that are common to all requests (Such as authentication).
346
+ def on_create_document(doc)
347
+ end
348
+ # Hook that is called before the message is dispatched.
349
+ #
350
+ # You can override this to provide filtering and logging.
351
+ def on_before_dispatch
352
+ end
353
+ # Hook that is called after the http_client is created.
354
+ #
355
+ # You can override this to customize the http_client
356
+ def on_after_create_http_request(http_request)
357
+ end
358
+ # Hook that is called when there is a response.
359
+ #
360
+ # You can override this to register common namespaces, useful for parsing the document.
361
+ def on_response_document(doc)
362
+ end
363
+ # Hook that is called if there is a HTTP level error.
364
+ #
365
+ # Default behaviour is to raise an error.
366
+ def on_http_error(response)
367
+ raise HttpError, response
368
+ end
369
+ # Hook that is called if the dispatch returns a +Fault+.
370
+ #
371
+ # Default behaviour is to raise the Fault, but you can override this to provide logging and more fine-grained handling faults.
372
+ #
373
+ # See also: parse_soap_fault
374
+ def on_fault(fault)
375
+ raise fault
376
+ end
377
+ # Hook that is called if the response does not contain a valid SOAP enevlope.
378
+ #
379
+ # Default behaviour is to raise an error
380
+ #
381
+ # Note that if your service has operations that are one-way, you shouldn't raise an error here.
382
+ # This is however a fairly exotic case, so that is why the default behaviour is to raise an error.
383
+ def on_missing_document(response)
384
+ raise "The response is not a valid SOAP envelope"
385
+ end
386
+
387
+ def debug(message = nil) #:nodoc:
388
+ if @@logger
389
+ if message
390
+ @@logger.puts(message)
391
+ end
392
+ if block_given?
393
+ yield @@logger
394
+ end
395
+ end
396
+ end
397
+
398
+ def make_http_request(uri, post_body, headers,http_options=nil)
399
+ request = Handsoap::Http::Request.new(uri, :post)
400
+
401
+ # SSL CA AND CLIENT CERTIFICATES
402
+ if http_options
403
+ request.set_trust_ca_file(http_options[:trust_ca_file]) if http_options[:trust_ca_file]
404
+ request.set_client_cert_files(http_options[:client_cert_file],http_options[:client_cert_key_file]) if http_options[:client_cert_file] && http_options[:client_cert_key_file]
405
+ end
406
+
407
+ headers.each do |key, value|
408
+ request.add_header(key, value)
409
+ end
410
+ request.body = post_body
411
+ debug do |logger|
412
+ logger.puts request.inspect
413
+ end
414
+ on_after_create_http_request(request)
415
+ request
416
+ end
417
+
418
+ # Start the parsing pipe-line.
419
+ # There are various stages and hooks for each, so that you can override those in your service classes.
420
+ def parse_http_response(response)
421
+ debug do |logger|
422
+ logger.puts(response.inspect do |body|
423
+ Handsoap.pretty_format_envelope(body).chomp
424
+ end)
425
+ end
426
+ xml_document = parse_soap_response_document(response.primary_part.body)
427
+ soap_fault = parse_soap_fault(xml_document)
428
+ # Is the response a soap-fault?
429
+ unless soap_fault.nil?
430
+ return on_fault(soap_fault)
431
+ end
432
+ # Does the http-status indicate an error?
433
+ if response.status >= 400
434
+ return on_http_error(response)
435
+ end
436
+ # Does the response contain a valid xml-document?
437
+ if xml_document.nil?
438
+ return on_missing_document(response)
439
+ end
440
+ # Everything seems in order.
441
+ on_response_document(xml_document)
442
+ return SoapResponse.new(xml_document, response)
443
+ end
444
+
445
+ # Creates a standard SOAP envelope and yields the +Body+ element.
446
+ def make_envelope # :yields: Handsoap::XmlMason::Element
447
+ doc = XmlMason::Document.new do |doc|
448
+ doc.alias 'env', self.envelope_namespace
449
+ doc.add "env:Envelope" do |env|
450
+ env.add "*:Header"
451
+ env.add "*:Body"
452
+ end
453
+ end
454
+ self.class.fire_on_create_document doc # deprecated .. use instance method
455
+ on_create_document(doc)
456
+ if block_given?
457
+ yield doc.find("Body"),doc.find("Header")
458
+ end
459
+ return doc
460
+ end
461
+
462
+ # String -> [XmlDocument | nil]
463
+ def parse_soap_response_document(http_body)
464
+ begin
465
+ Handsoap::XmlQueryFront.parse_string(http_body, Handsoap.xml_query_driver)
466
+ rescue Handsoap::XmlQueryFront::ParseError => ex
467
+ nil
468
+ end
469
+ end
470
+
471
+ # XmlDocument -> [Fault | nil]
472
+ def parse_soap_fault(document)
473
+ unless document.nil?
474
+ node = document.xpath('/env:Envelope/env:Body/descendant-or-self::env:Fault', { 'env' => self.envelope_namespace }).first
475
+ Fault.from_xml(node, :namespace => self.envelope_namespace) unless node.nil?
476
+ end
477
+ end
478
+ end
479
+
480
+ def self.pretty_format_envelope(xml_string)
481
+ if /^<.*:Envelope/.match(xml_string)
482
+ begin
483
+ doc = Handsoap::XmlQueryFront.parse_string(xml_string, Handsoap.xml_query_driver)
484
+ rescue
485
+ return xml_string
486
+ end
487
+ return doc.to_xml
488
+ # return "\n\e[1;33m" + doc.to_s + "\e[0m"
489
+ end
490
+ return xml_string
491
+ end
492
+ end
493
+
494
+ # Legacy/BC code here. This shouldn't be used in new applications.
495
+ module Handsoap
496
+ class Service
497
+ # Registers a simple method mapping without any arguments and no parsing of response.
498
+ #
499
+ # This is deprecated
500
+ def self.map_method(mapping)
501
+ if @mapping.nil?
502
+ @mapping = {}
503
+ end
504
+ @mapping.merge! mapping
505
+ end
506
+ def self.get_mapping(name)
507
+ @mapping[name] if @mapping
508
+ end
509
+ def method_missing(method, *args, &block)
510
+ action = self.class.get_mapping(method)
511
+ if action
512
+ invoke(action, *args, &block)
513
+ else
514
+ super
515
+ end
516
+ end
517
+ # Registers a block to call when a request document is created.
518
+ #
519
+ # This is deprecated, in favour of #on_create_document
520
+ def self.on_create_document(&block)
521
+ @create_document_callback = block
522
+ end
523
+ def self.fire_on_create_document(doc)
524
+ if @create_document_callback
525
+ @create_document_callback.call doc
526
+ end
527
+ end
528
+ private
529
+ # Helper to serialize a node into a ruby string
530
+ #
531
+ # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_s
532
+ def xml_to_str(node, xquery = nil)
533
+ n = xquery ? node.xpath(xquery, ns).first : node
534
+ return if n.nil?
535
+ n.to_s
536
+ end
537
+ alias_method :xml_to_s, :xml_to_str
538
+ # Helper to serialize a node into a ruby integer
539
+ #
540
+ # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_i
541
+ def xml_to_int(node, xquery = nil)
542
+ n = xquery ? node.xpath(xquery, ns).first : node
543
+ return if n.nil?
544
+ n.to_s.to_i
545
+ end
546
+ alias_method :xml_to_i, :xml_to_int
547
+ # Helper to serialize a node into a ruby float
548
+ #
549
+ # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_f
550
+ def xml_to_float(node, xquery = nil)
551
+ n = xquery ? node.xpath(xquery, ns).first : node
552
+ return if n.nil?
553
+ n.to_s.to_f
554
+ end
555
+ alias_method :xml_to_f, :xml_to_float
556
+ # Helper to serialize a node into a ruby boolean
557
+ #
558
+ # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_boolean
559
+ def xml_to_bool(node, xquery = nil)
560
+ n = xquery ? node.xpath(xquery, ns).first : node
561
+ return if n.nil?
562
+ n.to_s == "true"
563
+ end
564
+ # Helper to serialize a node into a ruby Time object
565
+ #
566
+ # *deprecated*. Use Handsoap::XmlQueryFront::XmlElement#to_date
567
+ def xml_to_date(node, xquery = nil)
568
+ n = xquery ? node.xpath(xquery, ns).first : node
569
+ return if n.nil?
570
+ Time.iso8601(n.to_s)
571
+ end
572
+ end
573
+ end