pgericson-handsoap 1.1.7

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.
@@ -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