diff options
author | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2003-09-24 15:18:44 +0000 |
---|---|---|
committer | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2003-09-24 15:18:44 +0000 |
commit | db9445103c082a306ba085f7677da02ea94b8841 (patch) | |
tree | a311d59f031ae5def87f68be71ed1f58abadc031 /lib/soap/rpc | |
parent | 8c2fb77787d1f20b4c19c9c52552856c339b86e9 (diff) | |
download | ruby-db9445103c082a306ba085f7677da02ea94b8841.tar.gz |
* lib/soap/* (29 files): SOAP4R added.
* lib/wsdl/* (42 files): WSDL4R added.
* lib/xsd/* (12 files): XSD4R added.
* test/soap/* (16 files): added.
* test/wsdl/* (2 files): added.
* test/xsd/* (3 files): added.
* sample/soap/* (27 files): added.
* sample/wsdl/* (13 files): added.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4591 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/soap/rpc')
-rw-r--r-- | lib/soap/rpc/cgistub.rb | 214 | ||||
-rw-r--r-- | lib/soap/rpc/driver.rb | 189 | ||||
-rw-r--r-- | lib/soap/rpc/element.rb | 278 | ||||
-rw-r--r-- | lib/soap/rpc/proxy.rb | 147 | ||||
-rw-r--r-- | lib/soap/rpc/router.rb | 176 | ||||
-rw-r--r-- | lib/soap/rpc/rpc.rb | 36 | ||||
-rw-r--r-- | lib/soap/rpc/soaplet.rb | 167 | ||||
-rw-r--r-- | lib/soap/rpc/standaloneServer.rb | 116 |
8 files changed, 1323 insertions, 0 deletions
diff --git a/lib/soap/rpc/cgistub.rb b/lib/soap/rpc/cgistub.rb new file mode 100644 index 0000000000..f016e31057 --- /dev/null +++ b/lib/soap/rpc/cgistub.rb @@ -0,0 +1,214 @@ +=begin +SOAP4R - CGI stub library +Copyright (C) 2001, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/streamHandler' +require 'webrick/httpresponse' +require 'webrick/httpstatus' +require 'logger' +require 'soap/rpc/router' + + +module SOAP +module RPC + + +### +# SYNOPSIS +# CGIStub.new +# +# DESCRIPTION +# To be written... +# +class CGIStub < Logger::Application + include SOAP + + # There is a client which does not accept the media-type which is defined in + # SOAP spec. + attr_accessor :mediatype + + class CGIError < Error; end + + class SOAPRequest + ALLOWED_LENGTH = 1024 * 1024 + + def initialize(stream = $stdin) + @method = ENV['REQUEST_METHOD'] + @size = ENV['CONTENT_LENGTH'].to_i || 0 + @contenttype = ENV['CONTENT_TYPE'] + @charset = nil + @soapaction = ENV['HTTP_SOAPAction'] + @source = stream + @body = nil + end + + def init + validate + @charset = StreamHandler.parse_media_type(@contenttype) + @body = @source.read(@size) + self + end + + def dump + @body.dup + end + + def soapaction + @soapaction + end + + def charset + @charset + end + + def to_s + "method: #{ @method }, size: #{ @size }" + end + + private + + def validate # raise CGIError + if @method != 'POST' + raise CGIError.new("Method '#{ @method }' not allowed.") + end + + if @size > ALLOWED_LENGTH + raise CGIError.new("Content-length too long.") + end + end + end + + def initialize(appname, default_namespace) + super(appname) + set_log(STDERR) + self.level = INFO + @default_namespace = default_namespace + @router = SOAP::RPC::Router.new(appname) + @remote_user = ENV['REMOTE_USER'] || 'anonymous' + @remote_host = ENV['REMOTE_HOST'] || ENV['REMOTE_ADDR'] || 'unknown' + @request = nil + @response = nil + @mediatype = MediaType + on_init + end + + def add_servant(obj, namespace = @default_namespace, soapaction = nil) + RPC.defined_methods(obj).each do |name| + qname = XSD::QName.new(namespace, name) + param_size = obj.method(name).arity.abs + params = (1..param_size).collect { |i| "p#{ i }" } + param_def = SOAP::RPC::SOAPMethod.create_param_def(params) + @router.add_method(obj, qname, soapaction, name, param_def) + end + end + + def on_init + # Override this method in derived class to call 'add_method' to add methods. + end + + def mapping_registry + @router.mapping_registry + end + + def mapping_registry=(value) + @router.mapping_registry = value + end + + def add_method(receiver, name, *param) + add_method_with_namespace_as(@default_namespace, receiver, + name, name, *param) + end + + def add_method_as(receiver, name, name_as, *param) + add_method_with_namespace_as(@default_namespace, receiver, + name, name_as, *param) + end + + def add_method_with_namespace(namespace, receiver, name, *param) + add_method_with_namespace_as(namespace, receiver, name, name, *param) + end + + def add_method_with_namespace_as(namespace, receiver, name, name_as, *param) + param_def = if param.size == 1 and param[0].is_a?(Array) + param[0] + else + SOAP::RPC::SOAPMethod.create_param_def(param) + end + qname = XSD::QName.new(namespace, name_as) + @router.add_method(receiver, qname, nil, name, param_def) + end + + def route(request_string, charset) + @router.route(request_string, charset) + end + + def create_fault_response(e) + @router.create_fault_response(e) + end + +private + + def run + prologue + + httpversion = WEBrick::HTTPVersion.new('1.0') + @response = WEBrick::HTTPResponse.new({:HTTPVersion => httpversion}) + begin + log(INFO) { "Received a request from '#{ @remote_user }@#{ @remote_host }'." } + # SOAP request parsing. + @request = SOAPRequest.new.init + req_charset = @request.charset + req_string = @request.dump + log(DEBUG) { "XML Request: #{req_string}" } + res_string, is_fault = route(req_string, req_charset) + log(DEBUG) { "XML Response: #{res_string}" } + + @response['Cache-Control'] = 'private' + if req_charset + @response['content-type'] = "#{@mediatype}; charset=\"#{req_charset}\"" + else + @response['content-type'] = @mediatype + end + if is_fault + @response.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR + end + @response.body = res_string + rescue Exception + res_string = create_fault_response($!) + @response['Cache-Control'] = 'private' + @response['content-type'] = @mediatype + @response.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR + ensure + buf = '' + @response.send_response(buf) + buf.sub!(/^[^\r]+\r\n/, '') # Trim status line. + log(DEBUG) { "SOAP CGI Response:\n#{ buf }" } + print buf + epilogue + end + + 0 + end + + def prologue; end + def epilogue; end +end + + +end +end diff --git a/lib/soap/rpc/driver.rb b/lib/soap/rpc/driver.rb new file mode 100644 index 0000000000..76fd14e34b --- /dev/null +++ b/lib/soap/rpc/driver.rb @@ -0,0 +1,189 @@ +=begin +SOAP4R - SOAP RPC driver +Copyright (C) 2000, 2001, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/soap' +require 'soap/mapping' +require 'soap/rpc/rpc' +require 'soap/rpc/proxy' +require 'soap/rpc/element' +require 'soap/streamHandler' + + +module SOAP +module RPC + + +class Driver +public + class EmptyResponseError < Error; end + + attr_accessor :mapping_registry + attr_accessor :soapaction + attr_reader :endpoint_url + attr_reader :wiredump_dev + attr_reader :wiredump_file_base + attr_reader :httpproxy + + def initialize(endpoint_url, namespace, soapaction = nil) + @endpoint_url = endpoint_url + @namespace = namespace + @mapping_registry = nil # for unmarshal + @soapaction = soapaction + @wiredump_dev = nil + @wiredump_file_base = nil + @httpproxy = ENV['httpproxy'] || ENV['HTTP_PROXY'] + @handler = HTTPPostStreamHandler.new(@endpoint_url, @httpproxy, + XSD::Charset.encoding_label) + @proxy = Proxy.new(@handler, @soapaction) + @proxy.allow_unqualified_element = true + end + + def endpoint_url=(endpoint_url) + @endpoint_url = endpoint_url + if @handler + @handler.endpoint_url = @endpoint_url + @handler.reset + end + end + + def wiredump_dev=(dev) + @wiredump_dev = dev + if @handler + @handler.wiredump_dev = @wiredump_dev + @handler.reset + end + end + + def wiredump_file_base=(base) + @wiredump_file_base = base + end + + def httpproxy=(httpproxy) + @httpproxy = httpproxy + if @handler + @handler.proxy = @httpproxy + @handler.reset + end + end + + def default_encodingstyle + @proxy.default_encodingstyle + end + + def default_encodingstyle=(encodingstyle) + @proxy.default_encodingstyle = encodingstyle + end + + + ### + ## Method definition interfaces. + # + # params: [[param_def...]] or [paramname, paramname, ...] + # param_def: See proxy.rb. Sorry. + + def add_method(name, *params) + add_method_with_soapaction_as(name, name, @soapaction, *params) + end + + def add_method_as(name, name_as, *params) + add_method_with_soapaction_as(name, name_as, @soapaction, *params) + end + + def add_method_with_soapaction(name, soapaction, *params) + add_method_with_soapaction_as(name, name, soapaction, *params) + end + + def add_method_with_soapaction_as(name, name_as, soapaction, *params) + param_def = if params.size == 1 and params[0].is_a?(Array) + params[0] + else + SOAPMethod.create_param_def(params) + end + qname = XSD::QName.new(@namespace, name_as) + @proxy.add_method(qname, soapaction, name, param_def) + add_rpc_method_interface(name, param_def) + end + + + ### + ## Driving interface. + # + def invoke(headers, body) + if @wiredump_file_base + @handler.wiredump_file_base = + @wiredump_file_base + '_' << body.elename.name + end + @proxy.invoke(headers, body) + end + + def call(name, *params) + # Convert parameters: params array => SOAPArray => members array + params = Mapping.obj2soap(params, @mapping_registry).to_a + if @wiredump_file_base + @handler.wiredump_file_base = @wiredump_file_base + '_' << name + end + + # Then, call @proxy.call like the following. + header, body = @proxy.call(nil, name, *params) + unless body + raise EmptyResponseError.new("Empty response.") + end + + begin + @proxy.check_fault(body) + rescue SOAP::FaultError => e + Mapping.fault2exception(e) + end + + ret = body.response ? Mapping.soap2obj(body.response, @mapping_registry) : nil + if body.outparams + outparams = body.outparams.collect { |outparam| Mapping.soap2obj(outparam) } + return [ret].concat(outparams) + else + return ret + end + end + + def reset_stream + @handler.reset + end + +private + + def add_rpc_method_interface(name, param_def) + param_names = [] + i = 0 + @proxy.method[name].each_param_name(RPC::SOAPMethod::IN, + RPC::SOAPMethod::INOUT) do |param_name| + i += 1 + param_names << "arg#{ i }" + end + + callparam = (param_names.collect { |pname| ", " + pname }).join + self.instance_eval <<-EOS + def #{ name }(#{ param_names.join(", ") }) + call("#{ name }"#{ callparam }) + end + EOS + end +end + + +end +end diff --git a/lib/soap/rpc/element.rb b/lib/soap/rpc/element.rb new file mode 100644 index 0000000000..d1e1931fa3 --- /dev/null +++ b/lib/soap/rpc/element.rb @@ -0,0 +1,278 @@ +=begin +SOAP4R - RPC element definition. +Copyright (C) 2000, 2001, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/baseData' + + +module SOAP + +# Add method definitions for RPC to common definition in element.rb +class SOAPBody < SOAPStruct + public + + def request + root_node + end + + def response + if !@is_fault + if void? + nil + else + # Initial element is [retval]. + root_node[0] + end + else + root_node + end + end + + def outparams + if !@is_fault and !void? + op = root_node[1..-1] + op = nil if op && op.empty? + op + else + nil + end + end + + def void? + root_node.nil? # || root_node.is_a?(SOAPNil) + end + + def fault + if @is_fault + self['fault'] + else + nil + end + end + + def fault=(fault) + @is_fault = true + add_member('fault', fault) + end +end + + +module RPC + + +class RPCError < Error; end +class MethodDefinitionError < RPCError; end +class ParameterError < RPCError; end + +class SOAPMethod < SOAPStruct + RETVAL = 'retval' + IN = 'in' + OUT = 'out' + INOUT = 'inout' + + attr_reader :param_def + attr_reader :inparam + attr_reader :outparam + + def initialize(qname, param_def = nil) + super(nil) + @elename = qname + @encodingstyle = nil + + @param_def = param_def + + @signature = [] + @inparam_names = [] + @inoutparam_names = [] + @outparam_names = [] + + @inparam = {} + @outparam = {} + @retval_name = nil + + init_param(@param_def) if @param_def + end + + def have_outparam? + @outparam_names.size > 0 + end + + def each_param_name(*type) + @signature.each do |io_type, name, param_type| + if type.include?(io_type) + yield(name) + end + end + end + + def set_param(params) + params.each do |param, data| + @inparam[param] = data + data.elename.name = param + end + end + + def set_outparam(params) + params.each do |param, data| + @outparam[param] = data + data.elename.name = param + end + end + + def SOAPMethod.create_param_def(param_names) + param_def = [] + param_names.each do |param_name| + param_def.push([IN, param_name, nil]) + end + param_def.push([RETVAL, 'return', nil]) + param_def + end + +private + + def init_param(param_def) + param_def.each do |io_type, name, param_type| + case io_type + when IN + @signature.push([IN, name, param_type]) + @inparam_names.push(name) + when OUT + @signature.push([OUT, name, param_type]) + @outparam_names.push(name) + when INOUT + @signature.push([INOUT, name, param_type]) + @inoutparam_names.push(name) + when RETVAL + if (@retval_name) + raise MethodDefinitionError.new('Duplicated retval') + end + @retval_name = name + else + raise MethodDefinitionError.new("Unknown type: #{ io_type }") + end + end + end +end + + +class SOAPMethodRequest < SOAPMethod + attr_accessor :soapaction + + def SOAPMethodRequest.create_request(qname, *params) + param_def = [] + param_value = [] + i = 0 + params.each do |param| + param_name = "p#{ i }" + i += 1 + param_def << [IN, nil, param_name] + param_value << [param_name, param] + end + param_def << [RETVAL, nil, 'return'] + o = new(qname, param_def) + o.set_param(param_value) + o + end + + def initialize(qname, param_def = nil, soapaction = nil) + check_elename(qname) + super(qname, param_def) + @soapaction = soapaction + end + + def each + each_param_name(IN, INOUT) do |name| + unless @inparam[name] + raise ParameterError.new("Parameter: #{ name } was not given.") + end + yield(name, @inparam[name]) + end + end + + def dup + req = self.class.new(@elename.dup, @param_def, @soapaction) + req.encodingstyle = @encodingstyle + req + end + + def create_method_response + SOAPMethodResponse.new( + XSD::QName.new(@elename.namespace, @elename.name + 'Response'), + @param_def) + end + +private + + def check_elename(qname) + # NCName & ruby's method name + unless /\A[\w_][\w\d_\-]*\z/ =~ qname.name + raise MethodDefinitionError.new("Element name '#{qname.name}' not allowed") + end + end +end + + +class SOAPMethodResponse < SOAPMethod + + def initialize(qname, param_def = nil) + super(qname, param_def) + @retval = nil + end + + def retval=(retval) + @retval = retval + @retval.elename = @retval.elename.dup_name('return') + end + + def each + if @retval_name and !@retval.is_a?(SOAPVoid) + yield(@retval_name, @retval) + end + + each_param_name(OUT, INOUT) do |param_name| + unless @outparam[param_name] + raise ParameterError.new("Parameter: #{ param_name } was not given.") + end + yield(param_name, @outparam[param_name]) + end + end +end + + +# To return(?) void explicitly. +# def foo(input_var) +# ... +# return SOAP::RPC::SOAPVoid.new +# end +class SOAPVoid < XSD::XSDAnySimpleType + include SOAPBasetype + extend SOAPModuleUtils + Name = XSD::QName.new(Mapping::RubyCustomTypeNamespace, nil) + +public + def initialize() + @elename = Name + @id = nil + @precedents = [] + @parent = nil + end +end + + +end +end diff --git a/lib/soap/rpc/proxy.rb b/lib/soap/rpc/proxy.rb new file mode 100644 index 0000000000..39a095838c --- /dev/null +++ b/lib/soap/rpc/proxy.rb @@ -0,0 +1,147 @@ +=begin +SOAP4R - RPC Proxy library. +Copyright (C) 2000, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/soap' +require 'soap/processor' +require 'soap/mapping' +require 'soap/rpc/rpc' +require 'soap/rpc/element' +require 'soap/streamHandler' + + +module SOAP +module RPC + + +class Proxy + include SOAP + +public + + attr_accessor :soapaction + attr_accessor :allow_unqualified_element, :default_encodingstyle + attr_reader :method + + def initialize(stream_handler, soapaction = nil) + @handler = stream_handler + @soapaction = soapaction + @method = {} + @allow_unqualified_element = false + @default_encodingstyle = nil + end + + class Request + include RPC + + public + + attr_reader :method + attr_reader :namespace + attr_reader :name + + def initialize(model, values) + @method = model.dup + @namespace = @method.elename.namespace + @name = @method.elename.name + + params = {} + + if ((values.size == 1) and (values[0].is_a?(Hash))) + params = values[0] + else + i = 0 + @method.each_param_name(SOAPMethod::IN, SOAPMethod::INOUT) do |name| + params[name] = values[i] || SOAPNil.new + i += 1 + end + end + @method.set_param(params) + end + end + + def add_method(qname, soapaction, name, param_def) + @method[name] = SOAPMethodRequest.new(qname, param_def, soapaction) + end + + def create_request(name, *values) + if (@method.key?(name)) + method = @method[name] + method.encodingstyle = @default_encodingstyle if @default_encodingstyle + else + raise SOAP::RPC::MethodDefinitionError.new( + "Method: #{ name } not defined.") + end + + Request.new(method, values) + end + + def invoke(req_header, req_body, soapaction = nil) + if req_header and !req_header.is_a?(SOAPHeader) + req_header = create_header(req_header) + end + if !req_body.is_a?(SOAPBody) + req_body = SOAPBody.new(req_body) + end + opt = create_options + send_string = Processor.marshal(req_header, req_body, opt) + data = @handler.send(send_string, soapaction) + if data.receive_string.empty? + return nil, nil + end + res_charset = StreamHandler.parse_media_type(data.receive_contenttype) + opt = create_options + opt[:charset] = res_charset + res_header, res_body = Processor.unmarshal(data.receive_string, opt) + return res_header, res_body + end + + def call(headers, name, *values) + req = create_request(name, *values) + return invoke(headers, req.method, req.method.soapaction || @soapaction) + end + + def check_fault(body) + if body.fault + raise SOAP::FaultError.new(body.fault) + end + end + +private + + def create_header(headers) + header = SOAPHeader.new() + headers.each do |content, mustunderstand, encodingstyle| + header.add(SOAPHeaderItem.new(content, mustunderstand, encodingstyle)) + end + header + end + + def create_options + opt = {} + opt[:default_encodingstyle] = @default_encodingstyle + if @allow_unqualified_element + opt[:allow_unqualified_element] = true + end + opt + end +end + + +end +end diff --git a/lib/soap/rpc/router.rb b/lib/soap/rpc/router.rb new file mode 100644 index 0000000000..20396a4a7d --- /dev/null +++ b/lib/soap/rpc/router.rb @@ -0,0 +1,176 @@ +=begin +SOAP4R - RPC Routing library +Copyright (C) 2001, 2002 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/soap' +require 'soap/processor' +require 'soap/mapping' +require 'soap/rpc/rpc' +require 'soap/rpc/element' + + +module SOAP +module RPC + + +class Router + include SOAP + + attr_reader :actor + attr_accessor :allow_unqualified_element + attr_accessor :default_encodingstyle + attr_accessor :mapping_registry + + def initialize(actor) + @actor = actor + @receiver = {} + @method_name = {} + @method = {} + @allow_unqualified_element = false + @default_encodingstyle = nil + @mapping_registry = nil + end + + def add_method(receiver, qname, soapaction, name, param_def) + fqname = fqname(qname) + @receiver[fqname] = receiver + @method_name[fqname] = name + @method[fqname] = RPC::SOAPMethodRequest.new(qname, param_def, soapaction) + end + + def add_header_handler + raise NotImplementedError.new + end + + # Routing... + def route(soap_string, charset = nil) + opt = options + opt[:charset] = charset + is_fault = false + begin + header, body = Processor.unmarshal(soap_string, opt) + # So far, header is omitted... + soap_request = body.request + unless soap_request.is_a?(SOAPStruct) + raise RPCRoutingError.new("Not an RPC style.") + end + soap_response = dispatch(soap_request) + rescue Exception + soap_response = fault($!) + is_fault = true + end + + header = SOAPHeader.new + body = SOAPBody.new(soap_response) + response_string = Processor.marshal(header, body, opt) + + return response_string, is_fault + end + + # Create fault response string. + def create_fault_response(e, charset = nil) + header = SOAPHeader.new + soap_response = fault(e) + body = SOAPBody.new(soap_response) + opt = options + opt[:charset] = charset + Processor.marshal(header, body, opt) + end + +private + + # Create new response. + def create_response(qname, result) + name = fqname(qname) + if (@method.key?(name)) + method = @method[name] + else + raise RPCRoutingError.new("Method: #{ name } not defined.") + end + + soap_response = method.create_method_response + if soap_response.have_outparam? + unless result.is_a?(Array) + raise RPCRoutingError.new("Out parameter was not returned.") + end + outparams = {} + i = 1 + soap_response.each_param_name('out', 'inout') do |outparam| + outparams[outparam] = Mapping.obj2soap(result[i], @mapping_registry) + i += 1 + end + soap_response.set_outparam(outparams) + soap_response.retval = Mapping.obj2soap(result[0], @mapping_registry) + else + soap_response.retval = Mapping.obj2soap(result, @mapping_registry) + end + soap_response + end + + # Create fault response. + def fault(e) + detail = Mapping::SOAPException.new(e) + SOAPFault.new( + SOAPString.new('Server'), + SOAPString.new(e.to_s), + SOAPString.new(@actor), + Mapping.obj2soap(detail, @mapping_registry)) + end + + # Dispatch to defined method. + def dispatch(soap_method) + request_struct = Mapping.soap2obj(soap_method, @mapping_registry) + values = soap_method.collect { |key, value| request_struct[key] } + method = lookup(soap_method.elename, values) + unless method + raise RPCRoutingError.new( + "Method: #{ soap_method.elename } not supported.") + end + + result = method.call(*values) + create_response(soap_method.elename, result) + end + + # Method lookup + def lookup(qname, values) + name = fqname(qname) + # It may be necessary to check all part of method signature... + if @method.member?(name) + @receiver[name].method(@method_name[name].intern) + else + nil + end + end + + def fqname(qname) + "#{ qname.namespace }:#{ qname.name }" + end + + def options + opt = {} + opt[:default_encodingstyle] = @default_encodingstyle + if @allow_unqualified_element + opt[:allow_unqualified_element] = true + end + opt + end +end + + +end +end diff --git a/lib/soap/rpc/rpc.rb b/lib/soap/rpc/rpc.rb new file mode 100644 index 0000000000..c3cb3228f5 --- /dev/null +++ b/lib/soap/rpc/rpc.rb @@ -0,0 +1,36 @@ +=begin +SOAP4R - RPC utility. +Copyright (C) 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +module SOAP + + +module RPC + ServerException = Mapping::MappedException + + def self.defined_methods(obj) + if obj.is_a?(Module) + obj.methods - Module.methods + else + obj.methods - Kernel.instance_methods(true) + end + end +end + + +end diff --git a/lib/soap/rpc/soaplet.rb b/lib/soap/rpc/soaplet.rb new file mode 100644 index 0000000000..1a4ef99b76 --- /dev/null +++ b/lib/soap/rpc/soaplet.rb @@ -0,0 +1,167 @@ +=begin +SOAP4R - SOAP handler servlet for WEBrick +Copyright (C) 2001, 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'webrick/httpservlet/abstract' +require 'webrick/httpstatus' +require 'soap/rpc/router' +require 'soap/streamHandler' + +module SOAP +module RPC + + +class SOAPlet < WEBrick::HTTPServlet::AbstractServlet +public + attr_reader :app_scope_router + + def initialize + @router_map = {} + @app_scope_router = ::SOAP::RPC::Router.new(self.class.name) + end + + # Add servant klass whose object has request scope. A servant object is + # instanciated for each request. + # + # Bare in mind that servant klasses are distinguished by HTTP SOAPAction + # header in request. Client which calls request-scoped servant must have a + # SOAPAction header which is a namespace of the servant klass. + # I mean, use Driver#add_method_with_soapaction instead of Driver#add_method + # at client side. + # + def add_rpc_request_servant(klass, namespace, mapping_registry = nil) + router = RequestRouter.new(klass, namespace, mapping_registry) + add_router(namespace, router) + end + + # Add servant object which has application scope. + def add_rpc_servant(obj, namespace) + router = @app_scope_router + SOAPlet.add_servant_to_router(router, obj, namespace) + add_router(namespace, router) + end + alias add_servant add_rpc_servant + + + ### + ## Servlet interfaces for WEBrick. + # + def get_instance(config, *options) + @config = config + self + end + + def require_path_info? + false + end + + def do_GET(req, res) + res.header['Allow'] = 'POST' + raise WEBrick::HTTPStatus::MethodNotAllowed, "GET request not allowed." + end + + def do_POST(req, res) + namespace = parse_soapaction(req.meta_vars['HTTP_SOAPACTION']) + router = lookup_router(namespace) + + is_fault = false + + charset = ::SOAP::StreamHandler.parse_media_type(req['content-type']) + begin + response_stream, is_fault = router.route(req.body, charset) + rescue Exception => e + response_stream = router.create_fault_response(e) + is_fault = true + end + + res.body = response_stream + res['content-type'] = "text/xml; charset=\"#{charset}\"" + if response_stream.is_a?(IO) + res.chunked = true + end + + if is_fault + res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR + end + end + +private + + class RequestRouter < ::SOAP::RPC::Router + def initialize(klass, namespace, mapping_registry = nil) + super(namespace) + if mapping_registry + self.mapping_registry = mapping_registry + end + @klass = klass + @namespace = namespace + end + + def route(soap_string) + obj = @klass.new + namespace = self.actor + router = ::SOAP::RPC::Router.new(@namespace) + SOAPlet.add_servant_to_router(router, obj, namespace) + router.route(soap_string) + end + end + + def add_router(namespace, router) + @router_map[namespace] = router + end + + def parse_soapaction(soapaction) + if /^"(.*)"$/ =~ soapaction + soapaction = $1 + end + if soapaction.empty? + return nil + end + soapaction + end + + def lookup_router(namespace) + if namespace + @router_map[namespace] || @app_scope_router + else + @app_scope_router + end + end + + class << self + public + def add_servant_to_router(router, obj, namespace) + ::SOAP::RPC.defined_methods(obj).each do |name| + add_servant_method_to_router(router, obj, namespace, name) + end + end + + def add_servant_method_to_router(router, obj, namespace, name) + qname = XSD::QName.new(namespace, name) + soapaction = nil + method = obj.method(name) + param_def = ::SOAP::RPC::SOAPMethod.create_param_def( + (1..method.arity.abs).collect { |i| "p#{ i }" }) + router.add_method(obj, qname, soapaction, name, param_def) + end + end +end + + +end +end diff --git a/lib/soap/rpc/standaloneServer.rb b/lib/soap/rpc/standaloneServer.rb new file mode 100644 index 0000000000..bc8ab18f4c --- /dev/null +++ b/lib/soap/rpc/standaloneServer.rb @@ -0,0 +1,116 @@ +=begin +SOAP4R - WEBrick Server +Copyright (C) 2003 by NAKAMURA, Hiroshi + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'logger' +require 'soap/rpc/soaplet' +require 'soap/streamHandler' + +# require 'webrick' +require 'webrick/compat.rb' +require 'webrick/version.rb' +require 'webrick/config.rb' +require 'webrick/log.rb' +require 'webrick/server.rb' +require 'webrick/utils.rb' +require 'webrick/accesslog' +# require 'webrick/htmlutils.rb' +require 'webrick/httputils.rb' +# require 'webrick/cookie.rb' +require 'webrick/httpversion.rb' +require 'webrick/httpstatus.rb' +require 'webrick/httprequest.rb' +require 'webrick/httpresponse.rb' +require 'webrick/httpserver.rb' +# require 'webrick/httpservlet.rb' +# require 'webrick/httpauth.rb' + + +module SOAP +module RPC + + +class StandaloneServer < Logger::Application + attr_reader :server + + def initialize(app_name, namespace, host = "0.0.0.0", port = 8080) + super(app_name) + @logdev = Logger.new(STDERR) + @logdev.level = INFO + @namespace = namespace + @server = WEBrick::HTTPServer.new( + :BindAddress => host, + :Logger => logdev, + :AccessLog => [[logdev, WEBrick::AccessLog::COMBINED_LOG_FORMAT]], + :Port => port + ) + @soaplet = ::SOAP::RPC::SOAPlet.new + on_init + @server.mount('/', @soaplet) + end + + def on_init + # define extra methods in derived class. + end + + def add_rpc_request_servant(klass, namespace = @namespace, mapping_registry = nil) + @soaplet.add_rpc_request_servant(klass, namespace, mapping_registry) + end + + def add_rpc_servant(obj, namespace = @namespace) + @soaplet.add_rpc_servant(obj, namespace) + end + alias add_servant add_rpc_servant + + def mapping_registry + @soaplet.app_scope_router.mapping_registry + end + + def mapping_registry=(mapping_registry) + @soaplet.app_scope_router.mapping_registry = mapping_registry + end + + def add_method(obj, name, *param) + add_method_as(obj, name, name, *param) + end + + def add_method_as(obj, name, name_as, *param) + qname = XSD::QName.new(@namespace, name_as) + soapaction = nil + method = obj.method(name) + param_def = if param.size == 1 and param[0].is_a?(Array) + param[0] + elsif param.empty? + ::SOAP::RPC::SOAPMethod.create_param_def( + (1..method.arity.abs).collect { |i| "p#{ i }" }) + else + SOAP::RPC::SOAPMethod.create_param_def(param) + end + @soaplet.app_scope_router.add_method(obj, qname, soapaction, name, param_def) + end + +private + + def run + @server.start + end +end + + +end +end |