diff options
Diffstat (limited to 'actionwebservice/lib/action_service/client')
-rw-r--r-- | actionwebservice/lib/action_service/client/base.rb | 35 | ||||
-rw-r--r-- | actionwebservice/lib/action_service/client/soap.rb | 87 | ||||
-rw-r--r-- | actionwebservice/lib/action_service/client/xmlrpc.rb | 76 |
3 files changed, 198 insertions, 0 deletions
diff --git a/actionwebservice/lib/action_service/client/base.rb b/actionwebservice/lib/action_service/client/base.rb new file mode 100644 index 0000000000..955887a4d8 --- /dev/null +++ b/actionwebservice/lib/action_service/client/base.rb @@ -0,0 +1,35 @@ +module ActionService # :nodoc: + module Client # :nodoc: + class ClientError < StandardError # :nodoc: + end + + class Base # :nodoc: + def initialize(api, endpoint_uri) + @api = api + @endpoint_uri = endpoint_uri + end + + def method_missing(name, *args) # :nodoc: + call_name = method_name(name) + return super(name, *args) if call_name.nil? + perform_invocation(call_name, args) + end + + protected + def perform_invocation(method_name, args) # :nodoc: + raise NotImplementedError, "use a protocol-specific client" + end + + private + def method_name(name) + if @api.has_api_method?(name.to_sym) + name.to_s + elsif @api.has_public_api_method?(name.to_s) + @api.api_method_name(name.to_s).to_s + else + nil + end + end + end + end +end diff --git a/actionwebservice/lib/action_service/client/soap.rb b/actionwebservice/lib/action_service/client/soap.rb new file mode 100644 index 0000000000..c617f36589 --- /dev/null +++ b/actionwebservice/lib/action_service/client/soap.rb @@ -0,0 +1,87 @@ +require 'soap/rpc/driver' +require 'uri' + +module ActionService # :nodoc: + module Client # :nodoc: + + # Implements SOAP client support (using RPC encoding for the messages). + # + # ==== Example Usage + # + # class PersonAPI < ActionService::API::Base + # api_method :find_all, :returns => [[Person]] + # end + # + # soap_client = ActionService::Client::Soap.new(PersonAPI, "http://...") + # persons = soap_client.find_all + # + class Soap < Base + + # Creates a new web service client using the SOAP RPC protocol. + # + # +api+ must be an ActionService::API::Base derivative, and + # +endpoint_uri+ must point at the relevant URL to which protocol requests + # will be sent with HTTP POST. + # + # Valid options: + # [<tt>:service_name</tt>] If the remote server has used a custom +wsdl_service_name+ + # option, you must specify it here + def initialize(api, endpoint_uri, options={}) + super(api, endpoint_uri) + @service_name = options[:service_name] || 'ActionService' + @namespace = "urn:#{@service_name}" + @mapper = ActionService::Protocol::Soap::SoapMapper.new(@namespace) + @protocol = ActionService::Protocol::Soap::SoapProtocol.new(@mapper) + @soap_action_base = options[:soap_action_base] + @soap_action_base ||= URI.parse(endpoint_uri).path + @driver = create_soap_rpc_driver(api, endpoint_uri) + end + + protected + def perform_invocation(method_name, args) + @driver.send(method_name, *args) + end + + def soap_action(method_name) + "#{@soap_action_base}/#{method_name}" + end + + private + def create_soap_rpc_driver(api, endpoint_uri) + @mapper.map_api(api) + driver = SoapDriver.new(endpoint_uri, nil) + driver.mapping_registry = @mapper.registry + api.api_methods.each do |name, info| + public_name = api.public_api_method_name(name) + qname = XSD::QName.new(@namespace, public_name) + action = soap_action(public_name) + expects = info[:expects] + returns = info[:returns] + param_def = [] + i = 1 + if expects + expects.each do |klass| + param_name = klass.is_a?(Hash) ? klass.keys[0] : "param#{i}" + mapping = @mapper.lookup(klass) + param_def << ['in', param_name, mapping.registry_mapping] + i += 1 + end + end + if returns + mapping = @mapper.lookup(returns[0]) + param_def << ['retval', 'return', mapping.registry_mapping] + end + driver.add_method(qname, action, name.to_s, param_def) + end + driver + end + + class SoapDriver < SOAP::RPC::Driver # :nodoc: + def add_method(qname, soapaction, name, param_def) + @proxy.add_rpc_method(qname, soapaction, name, param_def) + add_rpc_method_interface(name, param_def) + end + end + end + end +end diff --git a/actionwebservice/lib/action_service/client/xmlrpc.rb b/actionwebservice/lib/action_service/client/xmlrpc.rb new file mode 100644 index 0000000000..d0d007f871 --- /dev/null +++ b/actionwebservice/lib/action_service/client/xmlrpc.rb @@ -0,0 +1,76 @@ +require 'uri' +require 'xmlrpc/client' + +module ActionService # :nodoc: + module Client # :nodoc: + + # Implements XML-RPC client support + # + # ==== Example Usage + # + # class BloggerAPI < ActionService::API::Base + # inflect_names false + # api_method :getRecentPosts, :returns => [[Blog::Post]] + # end + # + # blog = ActionService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger") + # posts = blog.getRecentPosts + class XmlRpc < Base + + # Creates a new web service client using the XML-RPC protocol. + # + # +api+ must be an ActionService::API::Base derivative, and + # +endpoint_uri+ must point at the relevant URL to which protocol requests + # will be sent with HTTP POST. + # + # Valid options: + # [<tt>:handler_name</tt>] If the remote server defines its services inside special + # handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example), + # provide it here, or your method calls will fail + def initialize(api, endpoint_uri, options={}) + @api = api + @handler_name = options[:handler_name] + @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout]) + end + + protected + def perform_invocation(method_name, args) + args = transform_outgoing_method_params(method_name, args) + ok, return_value = @client.call2(public_name(method_name), *args) + return transform_return_value(method_name, return_value) if ok + raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}") + end + + def transform_outgoing_method_params(method_name, params) + info = @api.api_methods[method_name.to_sym] + signature = info[:expects] + signature_length = signature.nil?? 0 : signature.length + if signature_length != params.length + raise(ProtocolError, "API declares #{public_name(method_name)} to accept " + + "#{signature_length} parameters, but #{params.length} parameters " + + "were supplied") + end + if signature_length > 0 + signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature) + (1..signature.size).each do |i| + i -= 1 + params[i] = Protocol::XmlRpc::XmlRpcProtocol.ruby_to_xmlrpc(params[i], signature[i]) + end + end + params + end + + def transform_return_value(method_name, return_value) + info = @api.api_methods[method_name.to_sym] + return true unless signature = info[:returns] + signature = Protocol::XmlRpc::XmlRpcProtocol.transform_array_types(signature) + Protocol::XmlRpc::XmlRpcProtocol.xmlrpc_to_ruby(return_value, signature[0]) + end + + def public_name(method_name) + public_name = @api.public_api_method_name(method_name) + @handler_name ? "#{@handler_name}.#{public_name}" : public_name + end + end + end +end |