diff options
Diffstat (limited to 'actionwebservice/lib/action_web_service/protocol')
5 files changed, 396 insertions, 64 deletions
diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index 0ff4feef84..70b922ce73 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -3,22 +3,11 @@ module ActionWebService # :nodoc: class ProtocolError < ActionWebServiceError # :nodoc: end - class AbstractProtocol - attr :marshaler - attr :encoder - - def unmarshal_request(ap_request) - end - - def marshal_response(method, return_value) - body = method.encode_rpc_response(marshaler, encoder, return_value) - Response.new(body, 'text/xml') + class AbstractProtocol # :nodoc: + def decode_action_pack_request(action_pack_request) end - def protocol_client(api, protocol_name, endpoint_uri, options) - end - - def create_action_pack_request(service_name, public_method_name, raw_body, options={}) + def encode_action_pack_request(service_name, public_method_name, raw_body, options={}) klass = options[:request_class] || SimpleActionPackRequest request = klass.new request.request_parameters['action'] = service_name.to_s @@ -27,50 +16,30 @@ module ActionWebService # :nodoc: request.env['HTTP_CONTENT_TYPE'] = 'text/xml' request end - end - class SimpleActionPackRequest < ActionController::AbstractRequest - def initialize - @env = {} - @qparams = {} - @rparams = {} - @cookies = {} - reset_session + def decode_request(raw_request, service_name) end - def query_parameters - @qparams + def encode_request(method_name, params, param_types) end - def request_parameters - @rparams + def decode_response(raw_response) end - def env - @env - end - - def host - '' - end - - def cookies - @cookies + def encode_response(method_name, return_value, return_type) end - def session - @session + def protocol_client(api, protocol_name, endpoint_uri, options) end - def reset_session - @session = {} + def register_api(api) end end class Request # :nodoc: attr :protocol attr :method_name - attr :method_params + attr_accessor :method_params attr :service_name attr_accessor :api attr_accessor :api_method @@ -88,10 +57,50 @@ module ActionWebService # :nodoc: class Response # :nodoc: attr :body attr :content_type + attr :return_value - def initialize(body, content_type) + def initialize(body, content_type, return_value) @body = body @content_type = content_type + @return_value = return_value + end + end + + class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc: + def initialize + @env = {} + @qparams = {} + @rparams = {} + @cookies = {} + reset_session + end + + def query_parameters + @qparams + end + + def request_parameters + @rparams + end + + def env + @env + end + + def host + '' + end + + def cookies + @cookies + end + + def session + @session + end + + def reset_session + @session = {} end end end diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb index 40875975bf..a911c7d017 100644 --- a/actionwebservice/lib/action_web_service/protocol/discovery.rb +++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -14,10 +14,10 @@ module ActionWebService # :nodoc: module InstanceMethods # :nodoc: private - def discover_web_service_request(ap_request) + def discover_web_service_request(action_pack_request) (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol| protocol = protocol.new - request = protocol.unmarshal_request(ap_request) + request = protocol.decode_action_pack_request(action_pack_request) return request unless request.nil? end nil diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index 5e56748ae3..cc3e90a4cc 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -1,3 +1,5 @@ +require 'action_web_service/protocol/soap_protocol/marshaler' + module ActionWebService # :nodoc: module Protocol # :nodoc: module Soap # :nodoc: @@ -7,28 +9,105 @@ module ActionWebService # :nodoc: end class SoapProtocol < AbstractProtocol # :nodoc: - def initialize - @encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService' - @marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService' + def marshaler + @marshaler ||= SoapMarshaler.new + end + + def decode_action_pack_request(action_pack_request) + return nil unless has_valid_soap_action?(action_pack_request) + service_name = action_pack_request.parameters['action'] + decode_request(action_pack_request.raw_post, service_name) + end + + def encode_action_pack_request(service_name, public_method_name, raw_body, options={}) + request = super + request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name] + request end - def unmarshal_request(ap_request) - return nil unless has_valid_soap_action?(ap_request) - method_name, params = @encoder.decode_rpc_call(ap_request.raw_post) - params = params.map{|x| @marshaler.unmarshal(x)} - service_name = ap_request.parameters['action'] + def decode_request(raw_request, service_name) + envelope = SOAP::Processor.unmarshal(raw_request) + unless envelope + raise ProtocolError, "Failed to parse SOAP request message" + end + request = envelope.body.request + method_name = request.elename.name + params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) } Request.new(self, method_name, params, service_name) end + def encode_request(method_name, params, param_types) + param_types.each{ |type| marshaler.register_type(type) } if param_types + qname = XSD::QName.new(marshaler.type_namespace, method_name) + param_def = [] + i = 0 + if param_types + params = params.map do |param| + param_type = param_types[i] + param_def << ['in', param_type.name, marshaler.lookup_type(param_type).mapping] + i += 1 + [param_type.name, marshaler.ruby_to_soap(param)] + end + else + params = [] + end + request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def) + request.set_param(params) + envelope = create_soap_envelope(request) + SOAP::Processor.marshal(envelope) + end + + def decode_response(raw_response) + envelope = SOAP::Processor.unmarshal(raw_response) + unless envelope + raise ProtocolError, "Failed to parse SOAP request message" + end + method_name = envelope.body.request.elename.name + return_value = envelope.body.response + return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil? + [method_name, return_value] + end + + def encode_response(method_name, return_value, return_type) + if return_type + return_binding = marshaler.register_type(return_type) + marshaler.annotate_arrays(return_binding, return_value) + end + qname = XSD::QName.new(marshaler.type_namespace, method_name) + if return_value.nil? + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + else + if return_value.is_a?(Exception) + detail = SOAP::Mapping::SOAPException.new(return_value) + response = SOAP::SOAPFault.new( + SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']), + SOAP::SOAPString.new(return_value.to_s), + SOAP::SOAPString.new(self.class.name), + marshaler.ruby_to_soap(detail)) + else + if return_type + param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]] + response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def) + response.retval = marshaler.ruby_to_soap(return_value) + else + response = SOAP::RPC::SOAPMethodResponse.new(qname, nil) + end + end + end + envelope = create_soap_envelope(response) + Response.new(SOAP::Processor.marshal(envelope), 'text/xml', return_value) + end + def protocol_client(api, protocol_name, endpoint_uri, options={}) return nil unless protocol_name == :soap ActionWebService::Client::Soap.new(api, endpoint_uri, options) end - def create_action_pack_request(service_name, public_method_name, raw_body, options={}) - request = super - request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name] - request + def register_api(api) + api.api_methods.each do |name, method| + method.expects.each{ |type| marshaler.register_type(type) } if method.expects + method.returns.each{ |type| marshaler.register_type(type) } if method.returns + end end private @@ -43,7 +122,13 @@ module ActionWebService # :nodoc: return nil if soap_action.empty? soap_action end - end + + def create_soap_envelope(body) + header = SOAP::SOAPHeader.new + body = SOAP::SOAPBody.new(body) + SOAP::SOAPEnvelope.new(header, body) + end + end end end end diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb new file mode 100644 index 0000000000..5d9a80b007 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb @@ -0,0 +1,197 @@ +require 'soap/mapping' + +module ActionWebService + module Protocol + module Soap + class SoapMarshaler + attr :type_namespace + attr :registry + + def initialize(type_namespace=nil) + @type_namespace = type_namespace || 'urn:ActionWebService' + @registry = SOAP::Mapping::Registry.new + @type2binding = {} + end + + def soap_to_ruby(obj) + SOAP::Mapping.soap2obj(obj, @registry) + end + + def ruby_to_soap(obj) + SOAP::Mapping.obj2soap(obj, @registry) + end + + def register_type(type) + return @type2binding[type] if @type2binding.has_key?(type) + + type_class = type.array?? type.element_type.type_class : type.type_class + type_type = type.array?? type.element_type : type + type_binding = nil + if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil) + qname = mapping[2] ? mapping[2][:type] : nil + qname ||= soap_base_type_name(mapping[0]) + type_binding = SoapBinding.new(self, qname, type_type, mapping) + else + qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name)) + @registry.add(type_class, + SOAP::SOAPStruct, + typed_struct_factory(type_class), + { :type => qname }) + mapping = @registry.find_mapped_soap_class(type_class) + type_binding = SoapBinding.new(self, qname, type_type, mapping) + end + + array_binding = nil + if type.array? + array_mapping = @registry.find_mapped_soap_class(Array) rescue nil + if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil? + @registry.set(Array, + SOAP::SOAPArray, + SoapTypedArrayFactory.new) + array_mapping = @registry.find_mapped_soap_class(Array) + end + qname = XSD::QName.new(@type_namespace, soap_type_name(type.element_type.type_class.name) + 'Array') + array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding) + end + + @type2binding[type] = array_binding ? array_binding : type_binding + @type2binding[type] + end + alias :lookup_type :register_type + + def annotate_arrays(binding, value) + if binding.type.array? + mark_typed_array(value, binding.element_binding.qname) + if binding.element_binding.type.custom? + value.each do |element| + annotate_arrays(binding.element_binding, element) + end + end + elsif binding.type.structured? + binding.type.each_member do |name, type| + member_binding = register_type(type) + member_value = value.respond_to?('[]') ? value[name] : value.send(name) + annotate_arrays(member_binding, member_value) if type.custom? + end + end + end + + private + def typed_struct_factory(type_class) + if Object.const_defined?('ActiveRecord') + if type_class.ancestors.include?(ActiveRecord::Base) + qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name)) + type_class.instance_variable_set('@qname', qname) + return SoapActiveRecordStructFactory.new + end + end + SOAP::Mapping::Registry::TypedStructFactory + end + + def mark_typed_array(array, qname) + (class << array; self; end).class_eval do + define_method(:arytype) do + qname + end + end + end + + def soap_base_type_name(type) + xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' } + xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type + end + + def soap_type_name(type_name) + type_name.gsub(/::/, '..') + end + end + + class SoapBinding + attr :qname + attr :type + attr :mapping + attr :element_binding + + def initialize(marshaler, qname, type, mapping, element_binding=nil) + @marshaler = marshaler + @qname = qname + @type = type + @mapping = mapping + @element_binding = element_binding + end + + def type_name + @type.custom? ? @qname.name : nil + end + + def qualified_type_name(ns=nil) + if @type.custom? + "#{ns ? ns : @qname.namespace}:#{@qname.name}" + else + ns = XSD::NS.new + ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag) + xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')} + return ns.name(XSD::AnyTypeName) unless xsd_klass + ns.name(xsd_klass.const_get('Type')) + end + end + + def eql?(other) + @qname == other.qname + end + alias :== :eql? + + def hash + @qname.hash + end + end + + class SoapActiveRecordStructFactory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.is_a?(ActiveRecord::Base) + return nil + end + soap_obj = soap_class.new(obj.class.instance_variable_get('@qname')) + obj.class.columns.each do |column| + key = column.name.to_s + value = obj.send(key) + soap_obj[key] = SOAP::Mapping._obj2soap(value, map) + end + soap_obj + end + + def soap2obj(obj_class, node, info, map) + unless node.type == obj_class.instance_variable_get('@qname') + return false + end + obj = obj_class.new + node.each do |key, value| + obj[key] = value.data + end + obj.instance_variable_set('@new_record', false) + return true, obj + end + end + + class SoapTypedArrayFactory < SOAP::Mapping::Factory + def obj2soap(soap_class, obj, info, map) + unless obj.respond_to?(:arytype) + return nil + end + soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype) + mark_marshalled_obj(obj, soap_obj) + obj.each do |item| + child = SOAP::Mapping._obj2soap(item, map) + soap_obj.add(child) + end + soap_obj + end + + def soap2obj(obj_class, node, info, map) + return false + end + end + + end + end +end diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index f8ff12cfa3..de6c8c8a30 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -1,3 +1,5 @@ +require 'xmlrpc/marshal' + module ActionWebService # :nodoc: module Protocol # :nodoc: module XmlRpc # :nodoc: @@ -6,22 +8,61 @@ module ActionWebService # :nodoc: end class XmlRpcProtocol < AbstractProtocol # :nodoc: - def initialize - @encoder = WS::Encoding::XmlRpcEncoding.new - @marshaler = WS::Marshaling::XmlRpcMarshaler.new + def decode_action_pack_request(action_pack_request) + service_name = action_pack_request.parameters['action'] + decode_request(action_pack_request.raw_post, service_name) end - def unmarshal_request(ap_request) - method_name, params = @encoder.decode_rpc_call(ap_request.raw_post) - params = params.map{|x| @marshaler.unmarshal(x)} - service_name = ap_request.parameters['action'] + def decode_request(raw_request, service_name) + method_name, params = XMLRPC::Marshal.load_call(raw_request) Request.new(self, method_name, params, service_name) end + def encode_request(method_name, params, param_types) + if param_types + params = params.dup + param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) } + end + XMLRPC::Marshal.dump_call(method_name, *params) + end + + def decode_response(raw_response) + [nil, XMLRPC::Marshal.load_response(raw_response)] + end + + def encode_response(method_name, return_value, return_type) + return_value = true if return_value.nil? + if return_type + return_value = value_to_xmlrpc_wire_format(return_value, return_type) + end + raw_response = XMLRPC::Marshal.dump_response(return_value) + Response.new(raw_response, 'text/xml', return_value) + end + def protocol_client(api, protocol_name, endpoint_uri, options={}) return nil unless protocol_name == :xmlrpc ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options) end + + def value_to_xmlrpc_wire_format(value, value_type) + if value_type.array? + value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) } + else + if value.is_a?(ActionWebService::Struct) + struct = {} + value.class.members.each do |name, type| + struct[name.to_s] = value_to_xmlrpc_wire_format(value[name], type) + end + struct + elsif value.is_a?(ActiveRecord::Base) + value.attributes.dup + elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException) + XMLRPC::FaultException.new(2, value.message) + else + value + end + end + end end end end |