From 9d79880e81a035c96ca70d7e342549236ed1f242 Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Tue, 16 Aug 2005 05:45:03 +0000 Subject: [PATCH] add 'system.multicall' support to XML-RPC. boxcarred methods must still exist on the target service(s), value casting will still be performed, and recursive 'system.multicall' calls are not allowed. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2021 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionwebservice/CHANGELOG | 2 + .../action_web_service/dispatcher/abstract.rb | 66 +++++++++++++++++-- .../action_web_service/protocol/abstract.rb | 2 +- actionwebservice/test/abstract_dispatcher.rb | 27 +++++++- ...ispatcher_action_controller_xmlrpc_test.rb | 19 ++++++ 5 files changed, 107 insertions(+), 9 deletions(-) diff --git a/actionwebservice/CHANGELOG b/actionwebservice/CHANGELOG index a7bd6d77ec..66bbf7c871 100644 --- a/actionwebservice/CHANGELOG +++ b/actionwebservice/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Add XML-RPC 'system.multicall' support #1941 [jbonnar] + * Fix duplicate XSD entries for custom types shared across delegated/layered services #1729 [Tyler Kovacs] * Allow multiple invocations in the same test method #1720 [dkhawk] diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index 9f2f474ffb..7e43892b24 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -16,11 +16,10 @@ module ActionWebService # :nodoc: private def invoke_web_service_request(protocol_request) invocation = web_service_invocation(protocol_request) - case web_service_dispatching_mode - when :direct - web_service_direct_invoke(invocation) - when :delegated, :layered - web_service_delegated_invoke(invocation) + if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol) + xmlrpc_multicall_invoke(invocation) + else + web_service_invoke(invocation) end end @@ -47,10 +46,43 @@ module ActionWebService # :nodoc: if cancellation_reason raise(DispatcherError, "request canceled: #{cancellation_reason}") end + return_value + end + + def web_service_invoke(invocation) + case web_service_dispatching_mode + when :direct + return_value = web_service_direct_invoke(invocation) + when :delegated, :layered + return_value = web_service_delegated_invoke(invocation) + end web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value) end + + def xmlrpc_multicall_invoke(invocations) + responses = [] + invocations.each do |invocation| + begin + case web_service_dispatching_mode + when :direct + return_value = web_service_direct_invoke(invocation) + when :delegated, :layered + return_value = web_service_delegated_invoke(invocation) + end + api_method = invocation.api_method + if invocation.api.has_api_method?(api_method.name) + return_value = api_method.cast_returns(return_value) + end + responses << [return_value] + rescue Exception => e + responses << { 'faultCode' => 3, 'faultString' => e.message } + end + end + invocation = invocations[0] + invocation.protocol.encode_response('system.multicall', responses, nil, invocation.protocol_options) + end - def web_service_invocation(request) + def web_service_invocation(request, level = 0) public_method_name = request.method_name invocation = Invocation.new invocation.protocol = request.protocol @@ -70,6 +102,28 @@ module ActionWebService # :nodoc: end end end + if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol + if public_method_name == 'multicall' && invocation.service_name == 'system' + if level > 0 + raise(DispatcherError, "Recursive system.multicall invocations not allowed") + end + multicall = request.method_params.dup + unless multicall.is_a?(Array) && multicall[0].is_a?(Array) + raise(DispatcherError, "Malformed multicall (expected array of Hash elements)") + end + multicall = multicall[0] + return multicall.map do |item| + raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash) + raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName') + method_name = item['methodName'] + params = item.has_key?('params') ? item['params'] : [] + multicall_request = request.dup + multicall_request.method_name = method_name + multicall_request.method_params = params + web_service_invocation(multicall_request, level + 1) + end + end + end case web_service_dispatching_mode when :direct invocation.api = self.class.web_service_api diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index be5bda2d41..fff5f622c9 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -41,7 +41,7 @@ module ActionWebService # :nodoc: class Request # :nodoc: attr :protocol - attr :method_name + attr_accessor :method_name attr_accessor :method_params attr :service_name attr_accessor :api diff --git a/actionwebservice/test/abstract_dispatcher.rb b/actionwebservice/test/abstract_dispatcher.rb index 4f8cd1fb4c..94edb213fa 100644 --- a/actionwebservice/test/abstract_dispatcher.rb +++ b/actionwebservice/test/abstract_dispatcher.rb @@ -107,11 +107,15 @@ module DispatcherTest class MTAPI < ActionWebService::API::Base inflect_names false api_method :getCategories, :returns => [[:string]] + api_method :bool, :returns => [:bool] + api_method :alwaysFail end class BloggerAPI < ActionWebService::API::Base inflect_names false api_method :getCategories, :returns => [[:string]] + api_method :str, :expects => [:int], :returns => [:string] + api_method :alwaysFail end class MTService < ActionWebService::Base @@ -120,6 +124,14 @@ module DispatcherTest def getCategories ["mtCat1", "mtCat2"] end + + def bool + 'y' + end + + def alwaysFail + raise "MT AlwaysFail" + end end class BloggerService < ActionWebService::Base @@ -128,6 +140,17 @@ module DispatcherTest def getCategories ["bloggerCat1", "bloggerCat2"] end + + def str(int) + unless int.is_a?(Integer) + raise "Not an integer!" + end + 500 + int + end + + def alwaysFail + raise "Blogger AlwaysFail" + end end class AbstractController < ActionController::Base @@ -439,8 +462,8 @@ module DispatcherCommonTests public_method_name = real_method_name request_env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{real_method_name}" end - api = container.web_service_object(service_name.to_sym).class.web_service_api - method = api.public_api_method_instance(real_method_name) + api = container.web_service_object(service_name.to_sym).class.web_service_api rescue nil + method = api.public_api_method_instance(real_method_name) rescue nil service_name = self.service_name(container) end protocol.register_api(api) diff --git a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb index be8d553fc9..8309b0e16d 100644 --- a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb @@ -19,6 +19,25 @@ class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats) end + def test_multicall + response = do_method_call(@layered_controller, 'system.multicall', [ + {'methodName' => 'mt.getCategories'}, + {'methodName' => 'blogger.getCategories'}, + {'methodName' => 'mt.bool'}, + {'methodName' => 'blogger.str', 'params' => ['2000']}, + {'methodName' => 'mt.alwaysFail'}, + {'methodName' => 'blogger.alwaysFail'} + ]) + assert_equal [ + [["mtCat1", "mtCat2"]], + [["bloggerCat1", "bloggerCat2"]], + [true], + ["2500"], + {"faultCode" => 3, "faultString" => "MT AlwaysFail"}, + {"faultCode" => 3, "faultString" => "Blogger AlwaysFail"} + ], response + end + protected def exception_message(xmlrpc_fault_exception) xmlrpc_fault_exception.faultString