diff options
author | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-12-20 13:50:15 +0000 |
---|---|---|
committer | nahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-12-20 13:50:15 +0000 |
commit | e5a3aba50e2bd7e0327aacd8fe1ab7963fc4a007 (patch) | |
tree | b56a6e784df74331c28e4a94901b96837f214e35 /lib/soap/rpc | |
parent | 643dc132113489749c333a6bda14730d9b175ed1 (diff) | |
download | ruby-e5a3aba50e2bd7e0327aacd8fe1ab7963fc4a007.tar.gz |
* added files:
* lib/soap/mapping/wsdl*.rb
* lib/wsdl/soap/element.rb
* lib/wsdl/xmlSchema/simpleContent.rb
* modified files:
* lib/soap/*
* lib/wsdl/*
* lib/xsd/*
* test/soap/*
* test/wsdl/*
* test/xsd/*
* summary
* imported from the soap4r repository. Version: 1.5.3-ruby1.8.2
* added several XSD basetype support: nonPositiveInteger,
negativeInteger, nonNegativeInteger, unsignedLong, unsignedInt,
unsignedShort, unsignedByte, positiveInteger
* HTTP client connection/send/receive timeout support.
* HTTP client/server gzipped content encoding support.
* improved WSDL schema definition support; still is far from
complete, but is making step by step improovement.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@7612 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/soap/rpc')
-rw-r--r-- | lib/soap/rpc/cgistub.rb | 8 | ||||
-rw-r--r-- | lib/soap/rpc/driver.rb | 239 | ||||
-rw-r--r-- | lib/soap/rpc/httpserver.rb | 57 | ||||
-rw-r--r-- | lib/soap/rpc/proxy.rb | 264 | ||||
-rw-r--r-- | lib/soap/rpc/router.rb | 221 | ||||
-rw-r--r-- | lib/soap/rpc/rpc.rb | 2 | ||||
-rw-r--r-- | lib/soap/rpc/soaplet.rb | 92 |
7 files changed, 614 insertions, 269 deletions
diff --git a/lib/soap/rpc/cgistub.rb b/lib/soap/rpc/cgistub.rb index 55437bac59..e545d53c42 100644 --- a/lib/soap/rpc/cgistub.rb +++ b/lib/soap/rpc/cgistub.rb @@ -163,16 +163,16 @@ private @response = WEBrick::HTTPResponse.new({:HTTPVersion => httpversion}) conn_data = nil begin - log(INFO) { "Received a request from '#{ @remote_user }@#{ @remote_host }'." } + @log.info { "Received a request from '#{ @remote_user }@#{ @remote_host }'." } # SOAP request parsing. @request = SOAPRequest.new.init @response['Status'] = 200 conn_data = ::SOAP::StreamHandler::ConnectionData.new conn_data.receive_string = @request.dump conn_data.receive_contenttype = @request.contenttype - log(DEBUG) { "XML Request: #{conn_data.receive_string}" } + @log.debug { "XML Request: #{conn_data.receive_string}" } conn_data = route(conn_data) - log(DEBUG) { "XML Response: #{conn_data.send_string}" } + @log.debug { "XML Response: #{conn_data.send_string}" } if conn_data.is_fault @response['Status'] = 500 end @@ -189,7 +189,7 @@ private buf = '' @response.send_response(buf) buf.sub!(/^[^\r]+\r\n/, '') # Trim status line. - log(DEBUG) { "SOAP CGI Response:\n#{ buf }" } + @log.debug { "SOAP CGI Response:\n#{ buf }" } print buf epilogue end diff --git a/lib/soap/rpc/driver.rb b/lib/soap/rpc/driver.rb index 0e59dde9be..028addc3eb 100644 --- a/lib/soap/rpc/driver.rb +++ b/lib/soap/rpc/driver.rb @@ -8,6 +8,7 @@ require 'soap/soap' require 'soap/mapping' +require 'soap/mapping/wsdlliteralregistry' require 'soap/rpc/rpc' require 'soap/rpc/proxy' require 'soap/rpc/element' @@ -43,42 +44,45 @@ class Driver __attr_proxy :options __attr_proxy :headerhandler + __attr_proxy :streamhandler __attr_proxy :test_loopback_response __attr_proxy :endpoint_url, true __attr_proxy :mapping_registry, true __attr_proxy :soapaction, true __attr_proxy :default_encodingstyle, true + __attr_proxy :generate_explicit_type, true + __attr_proxy :allow_unqualified_element, true def httpproxy - @servant.options["protocol.http.proxy"] + options["protocol.http.proxy"] end def httpproxy=(httpproxy) - @servant.options["protocol.http.proxy"] = httpproxy + options["protocol.http.proxy"] = httpproxy end def wiredump_dev - @servant.options["protocol.http.wiredump_dev"] + options["protocol.http.wiredump_dev"] end def wiredump_dev=(wiredump_dev) - @servant.options["protocol.http.wiredump_dev"] = wiredump_dev + options["protocol.http.wiredump_dev"] = wiredump_dev end def mandatorycharset - @servant.options["protocol.mandatorycharset"] + options["protocol.mandatorycharset"] end def mandatorycharset=(mandatorycharset) - @servant.options["protocol.mandatorycharset"] = mandatorycharset + options["protocol.mandatorycharset"] = mandatorycharset end def wiredump_file_base - @servant.options["protocol.wiredump_file_base"] + options["protocol.wiredump_file_base"] end def wiredump_file_base=(wiredump_file_base) - @servant.options["protocol.wiredump_file_base"] = wiredump_file_base + options["protocol.wiredump_file_base"] = wiredump_file_base end def initialize(endpoint_url, namespace, soapaction = nil) @@ -94,32 +98,59 @@ class Driver end def inspect - "#<#{self.class}:#{@servant.streamhandler.inspect}>" + "#<#{self.class}:#{@servant.inspect}>" end - def add_method(name, *params) - add_method_with_soapaction_as(name, name, @servant.soapaction, *params) + def add_rpc_method(name, *params) + param_def = create_rpc_param_def(params) + @servant.add_rpc_method(name, @servant.soapaction, name, param_def) end - def add_method_as(name, name_as, *params) - add_method_with_soapaction_as(name, name_as, @servant.soapaction, *params) + def add_rpc_method_as(name, name_as, *params) + param_def = create_rpc_param_def(params) + @servant.add_rpc_method(name_as, @servant.soapaction, name, param_def) end - def add_method_with_soapaction(name, soapaction, *params) - add_method_with_soapaction_as(name, name, soapaction, *params) + def add_rpc_method_with_soapaction(name, soapaction, *params) + param_def = create_rpc_param_def(params) + @servant.add_rpc_method(name, soapaction, name, param_def) 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 - @servant.add_method(name_as, soapaction, name, param_def) + def add_rpc_method_with_soapaction_as(name, name_as, soapaction, *params) + param_def = create_rpc_param_def(params) + @servant.add_rpc_method(name_as, soapaction, name, param_def) + end + + # add_method is for shortcut of typical rpc/encoded method definition. + alias add_method add_rpc_method + alias add_method_as add_rpc_method_as + alias add_method_with_soapaction add_rpc_method_with_soapaction + alias add_method_with_soapaction_as add_rpc_method_with_soapaction_as + + def add_document_method(name, req_qname, res_qname) + param_def = create_document_param_def(name, req_qname, res_qname) + @servant.add_document_method(name, @servant.soapaction, name, param_def) + end + + def add_document_method_as(name, name_as, req_qname, res_qname) + param_def = create_document_param_def(name, req_qname, res_qname) + @servant.add_document_method(name_as, @servant.soapaction, name, param_def) + end + + def add_document_method_with_soapaction(name, soapaction, req_qname, + res_qname) + param_def = create_document_param_def(name, req_qname, res_qname) + @servant.add_document_method(name, soapaction, name, param_def) + end + + def add_document_method_with_soapaction_as(name, name_as, soapaction, + req_qname, res_qname) + param_def = create_document_param_def(name, req_qname, res_qname) + @servant.add_document_method(name_as, soapaction, name, param_def) end def reset_stream - @servant.streamhandler.reset + @servant.reset_stream end def invoke(headers, body) @@ -132,53 +163,62 @@ class Driver private + def create_rpc_param_def(params) + if params.size == 1 and params[0].is_a?(Array) + params[0] + else + SOAPMethod.create_param_def(params) + end + end + + def create_document_param_def(name, req_qname, res_qname) + [ + ['input', name, [nil, req_qname.namespace, req_qname.name]], + ['output', name, [nil, res_qname.namespace, res_qname.name]] + ] + end + def add_rpc_method_interface(name, param_def) @servant.add_rpc_method_interface(name, param_def) end + def add_document_method_interface(name, paramname) + @servant.add_document_method_interface(name, paramname) + end + class Servant__ - attr_reader :options - attr_reader :streamhandler - attr_reader :headerhandler attr_reader :proxy + attr_reader :options + attr_accessor :soapaction def initialize(host, endpoint_url, namespace) @host = host @namespace = namespace - @mapping_registry = nil @soapaction = nil - @wiredump_file_base = nil @options = setup_options - @streamhandler = HTTPPostStreamHandler.new(endpoint_url, - @options["protocol.http"] ||= ::SOAP::Property.new) - @headerhandler = Header::HandlerSet.new - @proxy = Proxy.new(@streamhandler, @soapaction) - @proxy.allow_unqualified_element = true + @wiredump_file_base = nil + @endpoint_url = endpoint_url + @proxy = Proxy.new(endpoint_url, @soapaction, @options) + end + + def inspect + "#<#{self.class}:#{@proxy.inspect}>" end def endpoint_url - @streamhandler.endpoint_url + @proxy.endpoint_url end def endpoint_url=(endpoint_url) - @streamhandler.endpoint_url = endpoint_url - @streamhandler.reset + @proxy.endpoint_url = endpoint_url end def mapping_registry - @mapping_registry + @proxy.mapping_registry end def mapping_registry=(mapping_registry) - @mapping_registry = mapping_registry - end - - def soapaction - @soapaction - end - - def soapaction=(soapaction) - @soapaction = soapaction + @proxy.mapping_registry = mapping_registry end def default_encodingstyle @@ -189,11 +229,42 @@ private @proxy.default_encodingstyle = encodingstyle end + def generate_explicit_type + @proxy.generate_explicit_type + end + + def generate_explicit_type=(generate_explicit_type) + @proxy.generate_explicit_type = generate_explicit_type + end + + def allow_unqualified_element + @proxy.allow_unqualified_element + end + + def allow_unqualified_element=(allow_unqualified_element) + @proxy.allow_unqualified_element = allow_unqualified_element + end + + def headerhandler + @proxy.headerhandler + end + + def streamhandler + @proxy.streamhandler + end + def test_loopback_response - @streamhandler.test_loopback_response + @proxy.test_loopback_response + end + + def reset_stream + @proxy.reset_stream end def invoke(headers, body) + if headers and !headers.is_a?(SOAPHeader) + headers = create_header(headers) + end set_wiredump_file_base(body.elename.name) env = @proxy.invoke(headers, body) if env.nil? @@ -205,39 +276,25 @@ private def call(name, *params) set_wiredump_file_base(name) - # Convert parameters: params array => SOAPArray => members array - params = Mapping.obj2soap(params, @mapping_registry).to_a - env = @proxy.call(call_headers, name, *params) - raise EmptyResponseError.new("Empty response.") unless env - receive_headers(env.header) - begin - @proxy.check_fault(env.body) - rescue SOAP::FaultError => e - Mapping.fault2exception(e) - end - - ret = env.body.response ? - Mapping.soap2obj(env.body.response, @mapping_registry) : nil - if env.body.outparams - outparams = env.body.outparams.collect { |outparam| - Mapping.soap2obj(outparam) - } - return [ret].concat(outparams) - else - return ret - end + @proxy.call(name, *params) end - def add_method(name_as, soapaction, name, param_def) + def add_rpc_method(name_as, soapaction, name, param_def) qname = XSD::QName.new(@namespace, name_as) - @proxy.add_method(qname, soapaction, name, param_def) + @proxy.add_rpc_method(qname, soapaction, name, param_def) add_rpc_method_interface(name, param_def) end + def add_document_method(name_as, soapaction, name, param_def) + qname = XSD::QName.new(@namespace, name_as) + @proxy.add_document_method(qname, soapaction, name, param_def) + add_document_method_interface(name, param_def) + end + def add_rpc_method_interface(name, param_def) param_names = [] i = 0 - @proxy.method[name].each_param_name(RPC::SOAPMethod::IN, + @proxy.operation[name].each_param_name(RPC::SOAPMethod::IN, RPC::SOAPMethod::INOUT) do |param_name| i += 1 param_names << "arg#{ i }" @@ -251,41 +308,41 @@ private @host.method(name) end - private - - def call_headers - headers = @headerhandler.on_outbound - if headers.empty? - nil - else - h = ::SOAP::SOAPHeader.new - headers.each do |header| - h.add(header.elename.name, header) - end - h - end + def add_document_method_interface(name, paramname) + @host.instance_eval <<-EOS + def #{ name }(param) + @servant.call(#{ name.dump }, param) + end + EOS + @host.method(name) end - def receive_headers(headers) - @headerhandler.on_inbound(headers) if headers - end + private def set_wiredump_file_base(name) if @wiredump_file_base - @streamhandler.wiredump_file_base = @wiredump_file_base + "_#{ name }" + @proxy.set_wiredump_file_base(@wiredump_file_base + "_#{ name }") + end + end + + def create_header(headers) + header = SOAPHeader.new() + headers.each do |content, mustunderstand, encodingstyle| + header.add(SOAPHeaderItem.new(content, mustunderstand, encodingstyle)) end + header end def setup_options if opt = Property.loadproperty(::SOAP::PropertyName) - opt = opt["client"] + opt = opt["client"] end opt ||= Property.new opt.add_hook("protocol.mandatorycharset") do |key, value| - @proxy.mandatorycharset = value + @proxy.mandatorycharset = value end opt.add_hook("protocol.wiredump_file_base") do |key, value| - @wiredump_file_base = value + @wiredump_file_base = value end opt["protocol.http.charset"] ||= XSD::Charset.encoding_label opt["protocol.http.proxy"] ||= Env::HTTP_PROXY diff --git a/lib/soap/rpc/httpserver.rb b/lib/soap/rpc/httpserver.rb index 7b1f961d9e..dccf950480 100644 --- a/lib/soap/rpc/httpserver.rb +++ b/lib/soap/rpc/httpserver.rb @@ -55,6 +55,8 @@ class HTTPServer < Logger::Application @soaplet.app_scope_router.mapping_registry = mapping_registry end + # servant entry interface + def add_rpc_request_servant(factory, namespace = @default_namespace, mapping_registry = nil) @soaplet.add_rpc_request_servant(factory, namespace, mapping_registry) @@ -72,23 +74,52 @@ class HTTPServer < Logger::Application @soaplet.add_rpc_headerhandler(obj) end - def add_method(obj, name, *param) - add_method_as(obj, name, name, *param) + # method entry interface + + def add_rpc_method(obj, name, *param) + add_rpc_method_as(obj, name, name, *param) + end + alias add_method add_rpc_method + + def add_document_method(obj, name, req_qname, res_qname) + opt = {} + opt[:request_style] = opt[:response_style] = :document + opt[:request_use] = opt[:response_use] = :literal + param_def = [ + ['input', req_qname.name, [nil, req_qname.namespace, req_qname.name]], + ['output', req_qname.name, [nil, res_qname.namespace, res_qname.name]] + ] + @soaplet.app_scope_router.add_operation(req_qname, nil, obj, name, + param_def, opt) end - def add_method_as(obj, name, name_as, *param) + def add_rpc_method_as(obj, name, name_as, *param) qname = XSD::QName.new(@default_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) + param_def = create_param_def(obj, name, param) + add_operation(qname, soapaction, obj, name, param_def) + end + alias add_method_as add_rpc_method_as + + def add_operation(qname, soapaction, obj, name, param_def, opt = {}) + opt[:request_style] ||= :rpc + opt[:response_style] ||= :rpc + opt[:request_use] ||= :encoded + opt[:response_use] ||= :encoded + @soaplet.app_scope_router.add_operation(qname, soapaction, obj, name, + param_def, opt) + end + + def create_param_def(obj, name, param = nil) + if param.nil? or param.empty? + method = obj.method(name) + ::SOAP::RPC::SOAPMethod.create_param_def( + (1..method.arity.abs).collect { |i| "p#{i}" }) + elsif param.size == 1 and param[0].is_a?(Array) + param[0] + else + ::SOAP::RPC::SOAPMethod.create_param_def(param) + end end private diff --git a/lib/soap/rpc/proxy.rb b/lib/soap/rpc/proxy.rb index 355bf2e81a..ca110664f9 100644 --- a/lib/soap/rpc/proxy.rb +++ b/lib/soap/rpc/proxy.rb @@ -28,73 +28,79 @@ public attr_accessor :mandatorycharset attr_accessor :allow_unqualified_element attr_accessor :default_encodingstyle - attr_reader :method + attr_accessor :generate_explicit_type + attr_reader :headerhandler + attr_reader :streamhandler - def initialize(streamhandler, soapaction = nil) - @streamhandler = streamhandler + attr_accessor :mapping_registry + attr_accessor :literal_mapping_registry + + attr_reader :operation + + def initialize(endpoint_url, soapaction, options) + @endpoint_url = endpoint_url @soapaction = soapaction - @method = {} + @options = options + @streamhandler = HTTPStreamHandler.new( + @options["protocol.http"] ||= ::SOAP::Property.new) + @operation = {} @mandatorycharset = nil - @allow_unqualified_element = false + @allow_unqualified_element = true @default_encodingstyle = nil + @generate_explicit_type = true + @headerhandler = Header::HandlerSet.new + @mapping_registry = nil + @literal_mapping_registry = ::SOAP::Mapping::WSDLLiteralRegistry.new end - class Request - include RPC + def inspect + "#<#{self.class}:#{@endpoint_url}>" + end - public + def endpoint_url + @endpoint_url + end - attr_reader :method - attr_reader :namespace - attr_reader :name + def endpoint_url=(endpoint_url) + @endpoint_url = endpoint_url + reset_stream + end - def initialize(model, values) - @method = model.dup - @namespace = @method.elename.namespace - @name = @method.elename.name + def reset_stream + @streamhandler.reset(@endpoint_url) + end - 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 + def set_wiredump_file_base(wiredump_file_base) + @streamhandler.wiredump_file_base = wiredump_file_base end - def add_method(qname, soapaction, name, param_def) - @method[name] = SOAPMethodRequest.new(qname, param_def, soapaction) + def test_loopback_response + @streamhandler.test_loopback_response 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) + def add_rpc_method(qname, soapaction, name, param_def, opt = {}) + opt[:request_style] ||= :rpc + opt[:response_style] ||= :rpc + opt[:request_use] ||= :encoded + opt[:response_use] ||= :encoded + @operation[name] = Operation.new(qname, soapaction, name, param_def, opt) 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 - opt[:external_content] = nil + def add_document_method(qname, soapaction, name, param_def, opt = {}) + opt[:request_style] ||= :document + opt[:response_style] ||= :document + opt[:request_use] ||= :literal + opt[:response_use] ||= :literal + @operation[name] = Operation.new(qname, soapaction, name, param_def, opt) + end + + # add_method is for shortcut of typical rpc/encoded method definition. + alias add_method add_rpc_method + + def invoke(req_header, req_body, opt = create_options) req_env = SOAPEnvelope.new(req_header, req_body) - send_string = Processor.marshal(req_env, opt) - conn_data = StreamHandler::ConnectionData.new(send_string) + opt[:external_content] = nil + conn_data = marshal(req_env, opt) if ext = opt[:external_content] mime = MIMEMessage.new ext.each do |k, v| @@ -105,16 +111,33 @@ public conn_data.send_string = mime.content_str conn_data.send_contenttype = mime.headers['content-type'].str end - conn_data = @streamhandler.send(conn_data, soapaction) + conn_data = @streamhandler.send(@endpoint_url, conn_data, opt[:soapaction]) if conn_data.receive_string.empty? - return nil, nil + return nil end unmarshal(conn_data, opt) end - def call(req_header, name, *values) - req = create_request(name, *values) - invoke(req_header, req.method, req.method.soapaction || @soapaction) + def call(name, *params) + unless op_info = @operation[name] + raise MethodDefinitionError, "Method: #{name} not defined." + end + req_header = create_request_header + req_body = op_info.create_request_body(params, @mapping_registry, + @literal_mapping_registry) + opt = create_options({ + :soapaction => op_info.soapaction || @soapaction, + :default_encodingstyle => op_info.response_default_encodingstyle}) + env = invoke(req_header, req_body, opt) + receive_headers(env.header) + raise EmptyResponseError.new("Empty response.") unless env + begin + check_fault(env.body) + rescue ::SOAP::FaultError => e + Mapping.fault2exception(e) + end + op_info.create_response_obj(env, @mapping_registry, + @literal_mapping_registry) end def check_fault(body) @@ -125,6 +148,28 @@ public private + def create_request_header + headers = @headerhandler.on_outbound + if headers.empty? + nil + else + h = ::SOAP::SOAPHeader.new + headers.each do |header| + h.add(header.elename.name, header) + end + h + end + end + + def receive_headers(headers) + @headerhandler.on_inbound(headers) if headers + end + + def marshal(env, opt) + send_string = Processor.marshal(env, opt) + StreamHandler::ConnectionData.new(send_string) + end + def unmarshal(conn_data, opt) contenttype = conn_data.receive_contenttype if /#{MIMEMessage::MultipartContentType}/i =~ contenttype @@ -156,14 +201,115 @@ private header end - def create_options + def create_options(hash = nil) opt = {} opt[:default_encodingstyle] = @default_encodingstyle - if @allow_unqualified_element - opt[:allow_unqualified_element] = true - end + opt[:allow_unqualified_element] = @allow_unqualified_element + opt[:generate_explicit_type] = @generate_explicit_type + opt.update(hash) if hash opt end + + class Operation + attr_reader :soapaction + attr_reader :request_style + attr_reader :response_style + attr_reader :request_use + attr_reader :response_use + + def initialize(qname, soapaction, name, param_def, opt) + @soapaction = soapaction + @request_style = opt[:request_style] + @response_style = opt[:response_style] + @request_use = opt[:request_use] + @response_use = opt[:response_use] + @rpc_method_factory = @document_method_name = nil + check_style(@request_style) + check_style(@response_style) + if @request_style == :rpc + @rpc_method_factory = SOAPMethodRequest.new(qname, param_def, + @soapaction) + else + @document_method_name = {} + param_def.each do |inout, paramname, typeinfo| + klass, namespace, name = typeinfo + case inout.to_s + when "input" + @document_method_name[:input] = ::XSD::QName.new(namespace, name) + when "output" + @document_method_name[:output] = ::XSD::QName.new(namespace, name) + else + raise MethodDefinitionError, "unknown type: " + inout + end + end + end + end + + def request_default_encodingstyle + (@request_style == :rpc) ? EncodingNamespace : LiteralNamespace + end + + def response_default_encodingstyle + (@response_style == :rpc) ? EncodingNamespace : LiteralNamespace + end + + # for rpc + def each_param_name(*target) + if @request_style == :rpc + @rpc_method_factory.each_param_name(*target) do |name| + yield(name) + end + else + yield(@document_method_name[:input].name) + end + end + + def create_request_body(values, mapping_registry, literal_mapping_registry) + if @request_style == :rpc + values = Mapping.obj2soap(values, mapping_registry).to_a + method = @rpc_method_factory.dup + params = {} + idx = 0 + method.each_param_name(::SOAP::RPC::SOAPMethod::IN, + ::SOAP::RPC::SOAPMethod::INOUT) do |name| + params[name] = values[idx] || SOAPNil.new + idx += 1 + end + method.set_param(params) + SOAPBody.new(method) + else + name = @document_method_name[:input] + document = literal_mapping_registry.obj2soap(values[0], name) + SOAPBody.new(document) + end + end + + def create_response_obj(env, mapping_registry, literal_mapping_registry) + if @response_style == :rpc + ret = env.body.response ? + Mapping.soap2obj(env.body.response, mapping_registry) : nil + if env.body.outparams + outparams = env.body.outparams.collect { |outparam| + Mapping.soap2obj(outparam) + } + [ret].concat(outparams) + else + ret + end + else + Mapping.soap2obj(env.body.root_node, literal_mapping_registry) + end + end + + private + + ALLOWED_STYLE = [:rpc, :document] + def check_style(style) + unless ALLOWED_STYLE.include?(style) + raise MethodDefinitionError, "unknown style: " + style + end + end + end end diff --git a/lib/soap/rpc/router.rb b/lib/soap/rpc/router.rb index 9d8d1c8da6..e9147af13a 100644 --- a/lib/soap/rpc/router.rb +++ b/lib/soap/rpc/router.rb @@ -9,6 +9,7 @@ require 'soap/soap' require 'soap/processor' require 'soap/mapping' +require 'soap/mapping/wsdlliteralregistry' require 'soap/rpc/rpc' require 'soap/rpc/element' require 'soap/streamHandler' @@ -27,26 +28,48 @@ class Router attr_accessor :allow_unqualified_element attr_accessor :default_encodingstyle attr_accessor :mapping_registry + attr_accessor :literal_mapping_registry attr_reader :headerhandler def initialize(actor) @actor = actor - @receiver = {} - @method_name = {} - @method = {} @allow_unqualified_element = false @default_encodingstyle = nil @mapping_registry = nil @headerhandler = Header::HandlerSet.new + @literal_mapping_registry = ::SOAP::Mapping::WSDLLiteralRegistry.new + @operation = {} 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) + def add_rpc_method(receiver, qname, soapaction, name, param_def, opt = {}) + opt[:request_style] ||= :rpc + opt[:response_style] ||= :rpc + opt[:request_use] ||= :encoded + opt[:response_use] ||= :encoded + add_operation(qname, soapaction, receiver, name, param_def, opt) end + def add_document_method(receiver, qname, soapaction, name, param_def, opt = {}) + opt[:request_style] ||= :document + opt[:response_style] ||= :document + opt[:request_use] ||= :encoded + opt[:response_use] ||= :encoded + if opt[:request_style] == :document + inputdef = param_def.find { |inout, paramname, typeinfo| inout == "input" } + klass, nsdef, namedef = inputdef[2] + qname = ::XSD::QName.new(nsdef, namedef) + end + add_operation(qname, soapaction, receiver, name, param_def, opt) + end + + def add_operation(qname, soapaction, receiver, name, param_def, opt) + @operation[fqname(qname)] = Operation.new(qname, soapaction, receiver, + name, param_def, opt) + end + + # add_method is for shortcut of typical use="encoded" method definition. + alias add_method add_rpc_method + def route(conn_data) soap_response = nil begin @@ -55,33 +78,17 @@ class Router raise ArgumentError.new("Illegal SOAP marshal format.") end receive_headers(env.header) - soap_request = env.body.request - unless soap_request.is_a?(SOAPStruct) - raise RPCRoutingError.new("Not an RPC style.") + request = env.body.request + op = @operation[fqname(request.elename)] + unless op + raise RPCRoutingError.new("Method: #{request.elename} not supported.") end - soap_response = dispatch(soap_request) + soap_response = op.call(request, @mapping_registry, @literal_mapping_registry) rescue Exception soap_response = fault($!) conn_data.is_fault = true end - - opt = options - opt[:external_content] = nil - header = call_headers - body = SOAPBody.new(soap_response) - env = SOAPEnvelope.new(header, body) - response_string = Processor.marshal(env, opt) - conn_data.send_string = response_string - if ext = opt[:external_content] - mime = MIMEMessage.new - ext.each do |k, v| - mime.add_attachment(v.data) - end - mime.add_part(conn_data.send_string + "\r\n") - mime.close - conn_data.send_string = mime.content_str - conn_data.send_contenttype = mime.headers['content-type'].str - end + marshal(conn_data, op, soap_response) conn_data end @@ -118,9 +125,9 @@ private else h = ::SOAP::SOAPHeader.new headers.each do |header| - h.add(header.elename.name, header) - end - h + h.add(header.elename.name, header) + end + h end end @@ -153,32 +160,28 @@ private env end - # 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.") + def marshal(conn_data, op, soap_response) + response_opt = options + response_opt[:external_content] = nil + if op and !conn_data.is_fault and op.response_use == :document + response_opt[:default_encodingstyle] = + ::SOAP::EncodingStyle::ASPDotNetHandler::Namespace 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 + header = call_headers + body = SOAPBody.new(soap_response) + env = SOAPEnvelope.new(header, body) + response_string = Processor.marshal(env, response_opt) + conn_data.send_string = response_string + if ext = response_opt[:external_content] + mime = MIMEMessage.new + ext.each do |k, v| + mime.add_attachment(v.data) end - soap_response.set_outparam(outparams) - soap_response.retval = Mapping.obj2soap(result[0], @mapping_registry) - else - soap_response.retval = Mapping.obj2soap(result, @mapping_registry) + mime.add_part(conn_data.send_string + "\r\n") + mime.close + conn_data.send_string = mime.content_str + conn_data.send_contenttype = mime.headers['content-type'].str end - soap_response end # Create fault response. @@ -191,31 +194,6 @@ private 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 @@ -228,6 +206,87 @@ private end opt end + + class Operation + attr_reader :receiver + attr_reader :name + attr_reader :soapaction + attr_reader :request_style + attr_reader :response_style + attr_reader :request_use + attr_reader :response_use + + def initialize(qname, soapaction, receiver, name, param_def, opt) + @soapaction = soapaction + @receiver = receiver + @name = name + @request_style = opt[:request_style] + @response_style = opt[:response_style] + @request_use = opt[:request_use] + @response_use = opt[:response_use] + if @response_style == :rpc + @rpc_response_factory = + RPC::SOAPMethodRequest.new(qname, param_def, @soapaction) + else + outputdef = param_def.find { |inout, paramname, typeinfo| inout == "output" } + klass, nsdef, namedef = outputdef[2] + @document_response_qname = ::XSD::QName.new(nsdef, namedef) + end + end + + def call(request, mapping_registry, literal_mapping_registry) + if @request_style == :rpc + param = Mapping.soap2obj(request, mapping_registry) + result = rpc_call(request, param) + else + param = Mapping.soap2obj(request, literal_mapping_registry) + result = document_call(request, param) + end + if @response_style == :rpc + rpc_response(result, mapping_registry) + else + document_response(result, literal_mapping_registry) + end + end + + private + + def rpc_call(request, param) + unless request.is_a?(SOAPStruct) + raise RPCRoutingError.new("Not an RPC style.") + end + values = request.collect { |key, value| param[key] } + @receiver.method(@name.intern).call(*values) + end + + def document_call(request, param) + @receiver.method(@name.intern).call(param) + end + + def rpc_response(result, mapping_registry) + soap_response = @rpc_response_factory.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 + + def document_response(result, literal_mapping_registry) + literal_mapping_registry.obj2soap(result, @document_response_qname) + end + end end diff --git a/lib/soap/rpc/rpc.rb b/lib/soap/rpc/rpc.rb index 5f77b4d2e9..a48b525dbb 100644 --- a/lib/soap/rpc/rpc.rb +++ b/lib/soap/rpc/rpc.rb @@ -16,7 +16,7 @@ module RPC if obj.is_a?(Module) obj.methods - Module.methods else - obj.methods - Kernel.instance_methods(true) + obj.methods - Object.instance_methods(true) end end end diff --git a/lib/soap/rpc/soaplet.rb b/lib/soap/rpc/soaplet.rb index 0c1427acf5..8f18c53d3a 100644 --- a/lib/soap/rpc/soaplet.rb +++ b/lib/soap/rpc/soaplet.rb @@ -10,6 +10,13 @@ require 'webrick/httpservlet/abstract' require 'webrick/httpstatus' require 'soap/rpc/router' require 'soap/streamHandler' +begin + require 'stringio' + require 'zlib' +rescue LoadError + STDERR.puts "Loading stringio or zlib failed. No gzipped response support." if $DEBUG +end + module SOAP module RPC @@ -18,12 +25,18 @@ module RPC class SOAPlet < WEBrick::HTTPServlet::AbstractServlet public attr_reader :app_scope_router + attr_reader :options def initialize - @router_map = {} + @rpc_router_map = {} @app_scope_router = ::SOAP::RPC::Router.new(self.class.name) @headerhandlerfactory = [] @app_scope_headerhandler = nil + @options = {} + end + + def allow_content_encoding_gzip=(allow) + @options[:allow_content_encoding_gzip] = allow end # Add servant factory whose object has request scope. A servant object is @@ -41,7 +54,7 @@ public unless factory.respond_to?(:create) raise TypeError.new("factory must respond to 'create'") end - router = setup_request_router(namespace) + router = setup_rpc_request_router(namespace) router.factory = factory router.mapping_registry = mapping_registry end @@ -49,8 +62,8 @@ public # 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) + SOAPlet.add_rpc_servant_to_router(router, obj, namespace) + add_rpc_router(namespace, router) end alias add_servant add_rpc_servant @@ -84,19 +97,26 @@ public end def do_POST(req, res) - namespace = parse_soapaction(req.meta_vars['HTTP_SOAPACTION']) - router = lookup_router(namespace) + @config[:Logger].debug { "SOAP request: " + req.body } + soapaction = parse_soapaction(req.meta_vars['HTTP_SOAPACTION']) + router = lookup_router(soapaction) with_headerhandler(router) do |router| begin conn_data = ::SOAP::StreamHandler::ConnectionData.new conn_data.receive_string = req.body conn_data.receive_contenttype = req['content-type'] conn_data = router.route(conn_data) + res['content-type'] = conn_data.send_contenttype if conn_data.is_fault res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR end - res.body = conn_data.send_string - res['content-type'] = conn_data.send_contenttype + if outstring = encode_gzip(req, conn_data.send_string) + res['content-encoding'] = 'gzip' + res['content-length'] = outstring.size + res.body = outstring + else + res.body = conn_data.send_string + end rescue Exception => e conn_data = router.create_fault_response(e) res.status = WEBrick::HTTPStatus::RC_INTERNAL_SERVER_ERROR @@ -107,6 +127,9 @@ public if res.body.is_a?(IO) res.chunked = true + @config[:Logger].debug { "SOAP response: (chunked response not logged)" } + else + @config[:Logger].debug { "SOAP response: " + res.body } end end @@ -115,8 +138,9 @@ private class RequestRouter < ::SOAP::RPC::Router attr_accessor :factory - def initialize(namespace = nil) + def initialize(style = :rpc, namespace = nil) super(namespace) + @style = style @namespace = namespace @factory = nil end @@ -125,19 +149,23 @@ private obj = @factory.create namespace = self.actor router = ::SOAP::RPC::Router.new(@namespace) - SOAPlet.add_servant_to_router(router, obj, namespace) + if @style == :rpc + SOAPlet.add_rpc_servant_to_router(router, obj, namespace) + else + raise RuntimeError.new("'document' style not supported.") + end router.route(soap_string) end end - def setup_request_router(namespace) - router = @router_map[namespace] || RequestRouter.new(namespace) - add_router(namespace, router) + def setup_rpc_request_router(namespace) + router = @rpc_router_map[namespace] || RequestRouter.new(:rpc, namespace) + add_rpc_router(namespace, router) router end - def add_router(namespace, router) - @router_map[namespace] = router + def add_rpc_router(namespace, router) + @rpc_router_map[namespace] = router end def parse_soapaction(soapaction) @@ -152,7 +180,7 @@ private def lookup_router(namespace) if namespace - @router_map[namespace] || @app_scope_router + @rpc_router_map[namespace] || @app_scope_router else @app_scope_router end @@ -172,25 +200,49 @@ private end end + def encode_gzip(req, outstring) + unless encode_gzip?(req) + return nil + end + begin + ostream = StringIO.new + gz = Zlib::GzipWriter.new(ostream) + gz.write(outstring) + ostream.string + ensure + gz.close + end + end + + def encode_gzip?(req) + @options[:allow_content_encoding_gzip] and defined?(::Zlib) and + req['accept-encoding'] and + req['accept-encoding'].split(/,\s*/).include?('gzip') + end + class << self public - def add_servant_to_router(router, obj, namespace) + def add_rpc_servant_to_router(router, obj, namespace) ::SOAP::RPC.defined_methods(obj).each do |name| begin - add_servant_method_to_router(router, obj, namespace, name) + add_rpc_servant_method_to_router(router, obj, namespace, name) rescue SOAP::RPC::MethodDefinitionError => e p e if $DEBUG end end end - def add_servant_method_to_router(router, obj, namespace, name) + def add_rpc_servant_method_to_router(router, obj, namespace, name, + style = :rpc, use = :encoded) 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) + opt = {} + opt[:request_style] = opt[:response_style] = style + opt[:request_use] = opt[:response_use] = use + router.add_operation(qname, soapaction, obj, name, param_def, opt) end end end |