1 | # SOAP4R - RPC Proxy library.
|
---|
2 | # Copyright (C) 2000, 2003-2005 NAKAMURA, Hiroshi <[email protected]>.
|
---|
3 |
|
---|
4 | # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
---|
5 | # redistribute it and/or modify it under the same terms of Ruby's license;
|
---|
6 | # either the dual license version in 2003, or any later version.
|
---|
7 |
|
---|
8 |
|
---|
9 | require 'soap/soap'
|
---|
10 | require 'soap/processor'
|
---|
11 | require 'soap/mapping'
|
---|
12 | require 'soap/rpc/rpc'
|
---|
13 | require 'soap/rpc/element'
|
---|
14 | require 'soap/streamHandler'
|
---|
15 | require 'soap/mimemessage'
|
---|
16 |
|
---|
17 |
|
---|
18 | module SOAP
|
---|
19 | module RPC
|
---|
20 |
|
---|
21 |
|
---|
22 | class Proxy
|
---|
23 | include SOAP
|
---|
24 |
|
---|
25 | public
|
---|
26 |
|
---|
27 | attr_accessor :soapaction
|
---|
28 | attr_accessor :mandatorycharset
|
---|
29 | attr_accessor :allow_unqualified_element
|
---|
30 | attr_accessor :default_encodingstyle
|
---|
31 | attr_accessor :generate_explicit_type
|
---|
32 | attr_reader :headerhandler
|
---|
33 | attr_reader :streamhandler
|
---|
34 |
|
---|
35 | attr_accessor :mapping_registry
|
---|
36 | attr_accessor :literal_mapping_registry
|
---|
37 |
|
---|
38 | attr_reader :operation
|
---|
39 |
|
---|
40 | def initialize(endpoint_url, soapaction, options)
|
---|
41 | @endpoint_url = endpoint_url
|
---|
42 | @soapaction = soapaction
|
---|
43 | @options = options
|
---|
44 | @streamhandler = HTTPStreamHandler.new(
|
---|
45 | @options["protocol.http"] ||= ::SOAP::Property.new)
|
---|
46 | @operation = {}
|
---|
47 | @mandatorycharset = nil
|
---|
48 | @allow_unqualified_element = true
|
---|
49 | @default_encodingstyle = nil
|
---|
50 | @generate_explicit_type = true
|
---|
51 | @headerhandler = Header::HandlerSet.new
|
---|
52 | @mapping_registry = nil
|
---|
53 | @literal_mapping_registry = ::SOAP::Mapping::WSDLLiteralRegistry.new
|
---|
54 | end
|
---|
55 |
|
---|
56 | def inspect
|
---|
57 | "#<#{self.class}:#{@endpoint_url}>"
|
---|
58 | end
|
---|
59 |
|
---|
60 | def endpoint_url
|
---|
61 | @endpoint_url
|
---|
62 | end
|
---|
63 |
|
---|
64 | def endpoint_url=(endpoint_url)
|
---|
65 | @endpoint_url = endpoint_url
|
---|
66 | reset_stream
|
---|
67 | end
|
---|
68 |
|
---|
69 | def reset_stream
|
---|
70 | @streamhandler.reset(@endpoint_url)
|
---|
71 | end
|
---|
72 |
|
---|
73 | def set_wiredump_file_base(wiredump_file_base)
|
---|
74 | @streamhandler.wiredump_file_base = wiredump_file_base
|
---|
75 | end
|
---|
76 |
|
---|
77 | def test_loopback_response
|
---|
78 | @streamhandler.test_loopback_response
|
---|
79 | end
|
---|
80 |
|
---|
81 | def add_rpc_operation(qname, soapaction, name, param_def, opt = {})
|
---|
82 | opt[:request_qname] = qname
|
---|
83 | opt[:request_style] ||= :rpc
|
---|
84 | opt[:response_style] ||= :rpc
|
---|
85 | opt[:request_use] ||= :encoded
|
---|
86 | opt[:response_use] ||= :encoded
|
---|
87 | @operation[name] = Operation.new(soapaction, param_def, opt)
|
---|
88 | end
|
---|
89 |
|
---|
90 | def add_document_operation(soapaction, name, param_def, opt = {})
|
---|
91 | opt[:request_style] ||= :document
|
---|
92 | opt[:response_style] ||= :document
|
---|
93 | opt[:request_use] ||= :literal
|
---|
94 | opt[:response_use] ||= :literal
|
---|
95 | # default values of these values are unqualified in XML Schema.
|
---|
96 | # set true for backward compatibility.
|
---|
97 | unless opt.key?(:elementformdefault)
|
---|
98 | opt[:elementformdefault] = true
|
---|
99 | end
|
---|
100 | unless opt.key?(:attributeformdefault)
|
---|
101 | opt[:attributeformdefault] = true
|
---|
102 | end
|
---|
103 | @operation[name] = Operation.new(soapaction, param_def, opt)
|
---|
104 | end
|
---|
105 |
|
---|
106 | # add_method is for shortcut of typical rpc/encoded method definition.
|
---|
107 | alias add_method add_rpc_operation
|
---|
108 | alias add_rpc_method add_rpc_operation
|
---|
109 | alias add_document_method add_document_operation
|
---|
110 |
|
---|
111 | def invoke(req_header, req_body, opt = nil)
|
---|
112 | opt ||= create_encoding_opt
|
---|
113 | route(req_header, req_body, opt, opt)
|
---|
114 | end
|
---|
115 |
|
---|
116 | def call(name, *params)
|
---|
117 | unless op_info = @operation[name]
|
---|
118 | raise MethodDefinitionError, "method: #{name} not defined"
|
---|
119 | end
|
---|
120 | mapping_opt = create_mapping_opt
|
---|
121 | req_header = create_request_header
|
---|
122 | req_body = SOAPBody.new(
|
---|
123 | op_info.request_body(params, @mapping_registry,
|
---|
124 | @literal_mapping_registry, mapping_opt)
|
---|
125 | )
|
---|
126 | reqopt = create_encoding_opt(
|
---|
127 | :soapaction => op_info.soapaction || @soapaction,
|
---|
128 | :envelopenamespace => @options["soap.envelope.requestnamespace"],
|
---|
129 | :default_encodingstyle =>
|
---|
130 | @default_encodingstyle || op_info.request_default_encodingstyle,
|
---|
131 | :elementformdefault => op_info.elementformdefault,
|
---|
132 | :attributeformdefault => op_info.attributeformdefault
|
---|
133 | )
|
---|
134 | resopt = create_encoding_opt(
|
---|
135 | :envelopenamespace => @options["soap.envelope.responsenamespace"],
|
---|
136 | :default_encodingstyle =>
|
---|
137 | @default_encodingstyle || op_info.response_default_encodingstyle,
|
---|
138 | :elementformdefault => op_info.elementformdefault,
|
---|
139 | :attributeformdefault => op_info.attributeformdefault
|
---|
140 | )
|
---|
141 | env = route(req_header, req_body, reqopt, resopt)
|
---|
142 | raise EmptyResponseError unless env
|
---|
143 | receive_headers(env.header)
|
---|
144 | begin
|
---|
145 | check_fault(env.body)
|
---|
146 | rescue ::SOAP::FaultError => e
|
---|
147 | op_info.raise_fault(e, @mapping_registry, @literal_mapping_registry)
|
---|
148 | end
|
---|
149 | op_info.response_obj(env.body, @mapping_registry,
|
---|
150 | @literal_mapping_registry, mapping_opt)
|
---|
151 | end
|
---|
152 |
|
---|
153 | def route(req_header, req_body, reqopt, resopt)
|
---|
154 | req_env = ::SOAP::SOAPEnvelope.new(req_header, req_body)
|
---|
155 | unless reqopt[:envelopenamespace].nil?
|
---|
156 | set_envelopenamespace(req_env, reqopt[:envelopenamespace])
|
---|
157 | end
|
---|
158 | reqopt[:external_content] = nil
|
---|
159 | conn_data = marshal(req_env, reqopt)
|
---|
160 | if ext = reqopt[:external_content]
|
---|
161 | mime = MIMEMessage.new
|
---|
162 | ext.each do |k, v|
|
---|
163 | mime.add_attachment(v.data)
|
---|
164 | end
|
---|
165 | mime.add_part(conn_data.send_string + "\r\n")
|
---|
166 | mime.close
|
---|
167 | conn_data.send_string = mime.content_str
|
---|
168 | conn_data.send_contenttype = mime.headers['content-type'].str
|
---|
169 | end
|
---|
170 | conn_data = @streamhandler.send(@endpoint_url, conn_data,
|
---|
171 | reqopt[:soapaction])
|
---|
172 | if conn_data.receive_string.empty?
|
---|
173 | return nil
|
---|
174 | end
|
---|
175 | unmarshal(conn_data, resopt)
|
---|
176 | end
|
---|
177 |
|
---|
178 | def check_fault(body)
|
---|
179 | if body.fault
|
---|
180 | raise SOAP::FaultError.new(body.fault)
|
---|
181 | end
|
---|
182 | end
|
---|
183 |
|
---|
184 | private
|
---|
185 |
|
---|
186 | def set_envelopenamespace(env, namespace)
|
---|
187 | env.elename = XSD::QName.new(namespace, env.elename.name)
|
---|
188 | if env.header
|
---|
189 | env.header.elename = XSD::QName.new(namespace, env.header.elename.name)
|
---|
190 | end
|
---|
191 | if env.body
|
---|
192 | env.body.elename = XSD::QName.new(namespace, env.body.elename.name)
|
---|
193 | end
|
---|
194 | end
|
---|
195 |
|
---|
196 | def create_request_header
|
---|
197 | headers = @headerhandler.on_outbound
|
---|
198 | if headers.empty?
|
---|
199 | nil
|
---|
200 | else
|
---|
201 | h = ::SOAP::SOAPHeader.new
|
---|
202 | headers.each do |header|
|
---|
203 | h.add(header.elename.name, header)
|
---|
204 | end
|
---|
205 | h
|
---|
206 | end
|
---|
207 | end
|
---|
208 |
|
---|
209 | def receive_headers(headers)
|
---|
210 | @headerhandler.on_inbound(headers) if headers
|
---|
211 | end
|
---|
212 |
|
---|
213 | def marshal(env, opt)
|
---|
214 | send_string = Processor.marshal(env, opt)
|
---|
215 | StreamHandler::ConnectionData.new(send_string)
|
---|
216 | end
|
---|
217 |
|
---|
218 | def unmarshal(conn_data, opt)
|
---|
219 | contenttype = conn_data.receive_contenttype
|
---|
220 | if /#{MIMEMessage::MultipartContentType}/i =~ contenttype
|
---|
221 | opt[:external_content] = {}
|
---|
222 | mime = MIMEMessage.parse("Content-Type: " + contenttype,
|
---|
223 | conn_data.receive_string)
|
---|
224 | mime.parts.each do |part|
|
---|
225 | value = Attachment.new(part.content)
|
---|
226 | value.contentid = part.contentid
|
---|
227 | obj = SOAPAttachment.new(value)
|
---|
228 | opt[:external_content][value.contentid] = obj if value.contentid
|
---|
229 | end
|
---|
230 | opt[:charset] = @mandatorycharset ||
|
---|
231 | StreamHandler.parse_media_type(mime.root.headers['content-type'].str)
|
---|
232 | env = Processor.unmarshal(mime.root.content, opt)
|
---|
233 | else
|
---|
234 | opt[:charset] = @mandatorycharset ||
|
---|
235 | ::SOAP::StreamHandler.parse_media_type(contenttype)
|
---|
236 | env = Processor.unmarshal(conn_data.receive_string, opt)
|
---|
237 | end
|
---|
238 | unless env.is_a?(::SOAP::SOAPEnvelope)
|
---|
239 | raise ResponseFormatError.new(
|
---|
240 | "response is not a SOAP envelope: #{conn_data.receive_string}")
|
---|
241 | end
|
---|
242 | env
|
---|
243 | end
|
---|
244 |
|
---|
245 | def create_header(headers)
|
---|
246 | header = SOAPHeader.new()
|
---|
247 | headers.each do |content, mustunderstand, encodingstyle|
|
---|
248 | header.add(SOAPHeaderItem.new(content, mustunderstand, encodingstyle))
|
---|
249 | end
|
---|
250 | header
|
---|
251 | end
|
---|
252 |
|
---|
253 | def create_encoding_opt(hash = nil)
|
---|
254 | opt = {}
|
---|
255 | opt[:default_encodingstyle] = @default_encodingstyle
|
---|
256 | opt[:allow_unqualified_element] = @allow_unqualified_element
|
---|
257 | opt[:generate_explicit_type] = @generate_explicit_type
|
---|
258 | opt[:no_indent] = @options["soap.envelope.no_indent"]
|
---|
259 | opt[:use_numeric_character_reference] =
|
---|
260 | @options["soap.envelope.use_numeric_character_reference"]
|
---|
261 | opt.update(hash) if hash
|
---|
262 | opt
|
---|
263 | end
|
---|
264 |
|
---|
265 | def create_mapping_opt(hash = nil)
|
---|
266 | opt = {
|
---|
267 | :external_ces => @options["soap.mapping.external_ces"]
|
---|
268 | }
|
---|
269 | opt.update(hash) if hash
|
---|
270 | opt
|
---|
271 | end
|
---|
272 |
|
---|
273 | class Operation
|
---|
274 | attr_reader :soapaction
|
---|
275 | attr_reader :request_style
|
---|
276 | attr_reader :response_style
|
---|
277 | attr_reader :request_use
|
---|
278 | attr_reader :response_use
|
---|
279 | attr_reader :elementformdefault
|
---|
280 | attr_reader :attributeformdefault
|
---|
281 |
|
---|
282 | def initialize(soapaction, param_def, opt)
|
---|
283 | @soapaction = soapaction
|
---|
284 | @request_style = opt[:request_style]
|
---|
285 | @response_style = opt[:response_style]
|
---|
286 | @request_use = opt[:request_use]
|
---|
287 | @response_use = opt[:response_use]
|
---|
288 | # set nil(unqualified) by default
|
---|
289 | @elementformdefault = opt[:elementformdefault]
|
---|
290 | @attributeformdefault = opt[:attributeformdefault]
|
---|
291 | check_style(@request_style)
|
---|
292 | check_style(@response_style)
|
---|
293 | check_use(@request_use)
|
---|
294 | check_use(@response_use)
|
---|
295 | if @request_style == :rpc
|
---|
296 | @rpc_request_qname = opt[:request_qname]
|
---|
297 | if @rpc_request_qname.nil?
|
---|
298 | raise MethodDefinitionError.new("rpc_request_qname must be given")
|
---|
299 | end
|
---|
300 | @rpc_method_factory =
|
---|
301 | RPC::SOAPMethodRequest.new(@rpc_request_qname, param_def, @soapaction)
|
---|
302 | else
|
---|
303 | @doc_request_qnames = []
|
---|
304 | @doc_request_qualified = []
|
---|
305 | @doc_response_qnames = []
|
---|
306 | @doc_response_qualified = []
|
---|
307 | param_def.each do |inout, paramname, typeinfo, eleinfo|
|
---|
308 | klass_not_used, nsdef, namedef = typeinfo
|
---|
309 | qualified = eleinfo
|
---|
310 | if namedef.nil?
|
---|
311 | raise MethodDefinitionError.new("qname must be given")
|
---|
312 | end
|
---|
313 | case inout
|
---|
314 | when SOAPMethod::IN
|
---|
315 | @doc_request_qnames << XSD::QName.new(nsdef, namedef)
|
---|
316 | @doc_request_qualified << qualified
|
---|
317 | when SOAPMethod::OUT
|
---|
318 | @doc_response_qnames << XSD::QName.new(nsdef, namedef)
|
---|
319 | @doc_response_qualified << qualified
|
---|
320 | else
|
---|
321 | raise MethodDefinitionError.new(
|
---|
322 | "illegal inout definition for document style: #{inout}")
|
---|
323 | end
|
---|
324 | end
|
---|
325 | end
|
---|
326 | end
|
---|
327 |
|
---|
328 | def request_default_encodingstyle
|
---|
329 | (@request_use == :encoded) ? EncodingNamespace : LiteralNamespace
|
---|
330 | end
|
---|
331 |
|
---|
332 | def response_default_encodingstyle
|
---|
333 | (@response_use == :encoded) ? EncodingNamespace : LiteralNamespace
|
---|
334 | end
|
---|
335 |
|
---|
336 | def request_body(values, mapping_registry, literal_mapping_registry, opt)
|
---|
337 | if @request_style == :rpc
|
---|
338 | request_rpc(values, mapping_registry, literal_mapping_registry, opt)
|
---|
339 | else
|
---|
340 | request_doc(values, mapping_registry, literal_mapping_registry, opt)
|
---|
341 | end
|
---|
342 | end
|
---|
343 |
|
---|
344 | def response_obj(body, mapping_registry, literal_mapping_registry, opt)
|
---|
345 | if @response_style == :rpc
|
---|
346 | response_rpc(body, mapping_registry, literal_mapping_registry, opt)
|
---|
347 | else
|
---|
348 | response_doc(body, mapping_registry, literal_mapping_registry, opt)
|
---|
349 | end
|
---|
350 | end
|
---|
351 |
|
---|
352 | def raise_fault(e, mapping_registry, literal_mapping_registry)
|
---|
353 | if @response_style == :rpc
|
---|
354 | Mapping.fault2exception(e, mapping_registry)
|
---|
355 | else
|
---|
356 | Mapping.fault2exception(e, literal_mapping_registry)
|
---|
357 | end
|
---|
358 | end
|
---|
359 |
|
---|
360 | private
|
---|
361 |
|
---|
362 | def check_style(style)
|
---|
363 | unless [:rpc, :document].include?(style)
|
---|
364 | raise MethodDefinitionError.new("unknown style: #{style}")
|
---|
365 | end
|
---|
366 | end
|
---|
367 |
|
---|
368 | def check_use(use)
|
---|
369 | unless [:encoded, :literal].include?(use)
|
---|
370 | raise MethodDefinitionError.new("unknown use: #{use}")
|
---|
371 | end
|
---|
372 | end
|
---|
373 |
|
---|
374 | def request_rpc(values, mapping_registry, literal_mapping_registry, opt)
|
---|
375 | if @request_use == :encoded
|
---|
376 | request_rpc_enc(values, mapping_registry, opt)
|
---|
377 | else
|
---|
378 | request_rpc_lit(values, literal_mapping_registry, opt)
|
---|
379 | end
|
---|
380 | end
|
---|
381 |
|
---|
382 | def request_doc(values, mapping_registry, literal_mapping_registry, opt)
|
---|
383 | if @request_use == :encoded
|
---|
384 | request_doc_enc(values, mapping_registry, opt)
|
---|
385 | else
|
---|
386 | request_doc_lit(values, literal_mapping_registry, opt)
|
---|
387 | end
|
---|
388 | end
|
---|
389 |
|
---|
390 | def request_rpc_enc(values, mapping_registry, opt)
|
---|
391 | method = @rpc_method_factory.dup
|
---|
392 | names = method.input_params
|
---|
393 | obj = create_request_obj(names, values)
|
---|
394 | soap = Mapping.obj2soap(obj, mapping_registry, @rpc_request_qname, opt)
|
---|
395 | method.set_param(soap)
|
---|
396 | method
|
---|
397 | end
|
---|
398 |
|
---|
399 | def request_rpc_lit(values, mapping_registry, opt)
|
---|
400 | method = @rpc_method_factory.dup
|
---|
401 | params = {}
|
---|
402 | idx = 0
|
---|
403 | method.input_params.each do |name|
|
---|
404 | params[name] = Mapping.obj2soap(values[idx], mapping_registry,
|
---|
405 | XSD::QName.new(nil, name), opt)
|
---|
406 | idx += 1
|
---|
407 | end
|
---|
408 | method.set_param(params)
|
---|
409 | method
|
---|
410 | end
|
---|
411 |
|
---|
412 | def request_doc_enc(values, mapping_registry, opt)
|
---|
413 | (0...values.size).collect { |idx|
|
---|
414 | ele = Mapping.obj2soap(values[idx], mapping_registry, nil, opt)
|
---|
415 | ele.elename = @doc_request_qnames[idx]
|
---|
416 | ele
|
---|
417 | }
|
---|
418 | end
|
---|
419 |
|
---|
420 | def request_doc_lit(values, mapping_registry, opt)
|
---|
421 | (0...values.size).collect { |idx|
|
---|
422 | ele = Mapping.obj2soap(values[idx], mapping_registry,
|
---|
423 | @doc_request_qnames[idx], opt)
|
---|
424 | ele.encodingstyle = LiteralNamespace
|
---|
425 | if ele.respond_to?(:qualified)
|
---|
426 | ele.qualified = @doc_request_qualified[idx]
|
---|
427 | end
|
---|
428 | ele
|
---|
429 | }
|
---|
430 | end
|
---|
431 |
|
---|
432 | def response_rpc(body, mapping_registry, literal_mapping_registry, opt)
|
---|
433 | if @response_use == :encoded
|
---|
434 | response_rpc_enc(body, mapping_registry, opt)
|
---|
435 | else
|
---|
436 | response_rpc_lit(body, literal_mapping_registry, opt)
|
---|
437 | end
|
---|
438 | end
|
---|
439 |
|
---|
440 | def response_doc(body, mapping_registry, literal_mapping_registry, opt)
|
---|
441 | if @response_use == :encoded
|
---|
442 | return *response_doc_enc(body, mapping_registry, opt)
|
---|
443 | else
|
---|
444 | return *response_doc_lit(body, literal_mapping_registry, opt)
|
---|
445 | end
|
---|
446 | end
|
---|
447 |
|
---|
448 | def response_rpc_enc(body, mapping_registry, opt)
|
---|
449 | ret = nil
|
---|
450 | if body.response
|
---|
451 | ret = Mapping.soap2obj(body.response, mapping_registry,
|
---|
452 | @rpc_method_factory.retval_class_name, opt)
|
---|
453 | end
|
---|
454 | if body.outparams
|
---|
455 | outparams = body.outparams.collect { |outparam|
|
---|
456 | Mapping.soap2obj(outparam, mapping_registry, nil, opt)
|
---|
457 | }
|
---|
458 | [ret].concat(outparams)
|
---|
459 | else
|
---|
460 | ret
|
---|
461 | end
|
---|
462 | end
|
---|
463 |
|
---|
464 | def response_rpc_lit(body, mapping_registry, opt)
|
---|
465 | body.root_node.collect { |key, value|
|
---|
466 | Mapping.soap2obj(value, mapping_registry,
|
---|
467 | @rpc_method_factory.retval_class_name, opt)
|
---|
468 | }
|
---|
469 | end
|
---|
470 |
|
---|
471 | def response_doc_enc(body, mapping_registry, opt)
|
---|
472 | body.collect { |key, value|
|
---|
473 | Mapping.soap2obj(value, mapping_registry, nil, opt)
|
---|
474 | }
|
---|
475 | end
|
---|
476 |
|
---|
477 | def response_doc_lit(body, mapping_registry, opt)
|
---|
478 | body.collect { |key, value|
|
---|
479 | Mapping.soap2obj(value, mapping_registry)
|
---|
480 | }
|
---|
481 | end
|
---|
482 |
|
---|
483 | def create_request_obj(names, params)
|
---|
484 | o = Object.new
|
---|
485 | idx = 0
|
---|
486 | while idx < params.length
|
---|
487 | o.instance_variable_set('@' + names[idx], params[idx])
|
---|
488 | idx += 1
|
---|
489 | end
|
---|
490 | o
|
---|
491 | end
|
---|
492 | end
|
---|
493 | end
|
---|
494 |
|
---|
495 |
|
---|
496 | end
|
---|
497 | end
|
---|