diff options
Diffstat (limited to 'actionwebservice/lib/action_web_service/dispatcher')
-rw-r--r-- | actionwebservice/lib/action_web_service/dispatcher/abstract.rb | 175 | ||||
-rw-r--r-- | actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb | 315 |
2 files changed, 251 insertions, 239 deletions
diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index 2262cd1cdd..b7560afc87 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -14,136 +14,103 @@ module ActionWebService # :nodoc: module InstanceMethods # :nodoc: private - def dispatch_web_service_request(action_pack_request) - protocol_request = protocol_response = nil - bm = Benchmark.measure do - protocol_request = probe_request_protocol(action_pack_request) - protocol_response = dispatch_protocol_request(protocol_request) - end - [protocol_request, protocol_response, bm.real, nil] - rescue Exception => e - protocol_response = prepare_exception_response(protocol_request, e) - [protocol_request, prepare_exception_response(protocol_request, e), nil, e] - end - - def dispatch_protocol_request(protocol_request) + def invoke_web_service_request(protocol_request) + invocation = web_service_invocation(protocol_request) case web_service_dispatching_mode when :direct - dispatch_direct_request(protocol_request) + web_service_direct_invoke(invocation) when :delegated - dispatch_delegated_request(protocol_request) - else - raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}") + web_service_delegated_invoke(invocation) end end - - def dispatch_direct_request(protocol_request) - request = prepare_dispatch_request(protocol_request) - return_value = direct_invoke(request) - protocol_request.marshal(return_value) - end - - def dispatch_delegated_request(protocol_request) - request = prepare_dispatch_request(protocol_request) - return_value = delegated_invoke(request) - protocol_request.marshal(return_value) - end - - def direct_invoke(request) - return nil unless before_direct_invoke(request) - return_value = send(request.method_name) - after_direct_invoke(request) - return_value - end - - def before_direct_invoke(request) - @method_params = request.params - end - - def after_direct_invoke(request) + + def web_service_direct_invoke(invocation) + @method_params = invocation.method_ordered_params + return_value = self.__send__(invocation.api_method_name) + returns = invocation.returns ? invocation.returns[0] : nil + invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) end - def delegated_invoke(request) + def web_service_delegated_invoke(invocation) cancellation_reason = nil - web_service = request.web_service - return_value = web_service.perform_invocation(request.method_name, request.params) do |x| + return_value = invocation.service.perform_invocation(invocation.api_method_name, invocation.method_ordered_params) do |x| cancellation_reason = x end if cancellation_reason raise(DispatcherError, "request canceled: #{cancellation_reason}") end - return_value + returns = invocation.returns ? invocation.returns[0] : nil + invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns) end - def prepare_dispatch_request(protocol_request) - api = method_name = web_service_name = web_service = params = nil - public_method_name = protocol_request.public_method_name + def web_service_invocation(request) + invocation = Invocation.new + invocation.protocol = request.protocol + invocation.service_name = request.service_name case web_service_dispatching_mode when :direct - api = self.class.web_service_api + invocation.api = self.class.web_service_api + invocation.service = self when :delegated - web_service_name = protocol_request.web_service_name - web_service = web_service_object(web_service_name) - api = web_service.class.web_service_api - end - method_name = api.api_method_name(public_method_name) - signature = nil - if method_name - signature = api.api_methods[method_name] - protocol_request.type = Protocol::CheckedMessage - protocol_request.signature = signature[:expects] - protocol_request.return_signature = signature[:returns] - else - method_name = api.default_api_method - if method_name - protocol_request.type = Protocol::UncheckedMessage - else - raise(DispatcherError, "no such method #{web_service_name}##{public_method_name}") + invocation.service = web_service_object(request.service_name) rescue nil + unless invocation.service + raise(DispatcherError, "failed to instantiate service #{invocation.service_name}") end + invocation.api = invocation.service.class.web_service_api end - params = protocol_request.unmarshal - DispatchRequest.new( - :api => api, - :public_method_name => public_method_name, - :method_name => method_name, - :signature => signature, - :web_service_name => web_service_name, - :web_service => web_service, - :params => params) - end - - def prepare_exception_response(protocol_request, exception) - if protocol_request && exception - case web_service_dispatching_mode - when :direct - if web_service_exception_reporting - return protocol_request.protocol.marshal_exception(exception) - end - when :delegated - web_service = web_service_object(protocol_request.web_service_name) - if web_service && web_service.class.web_service_exception_reporting - return protocol_request.protocol.marshal_exception(exception) + public_method_name = request.method_name + unless invocation.api.has_public_api_method?(public_method_name) + raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}") + end + invocation.public_method_name = public_method_name + invocation.api_method_name = invocation.api.api_method_name(public_method_name) + info = invocation.api.api_methods[invocation.api_method_name] + invocation.expects = info[:expects] + invocation.returns = info[:returns] + if invocation.expects + i = 0 + invocation.method_ordered_params = request.method_params.map do |param| + if invocation.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol) + marshaler = invocation.protocol.marshaler + decoded_param = WS::Encoding::XmlRpcDecodedParam.new(param.info.name, param.value) + marshaled_param = marshaler.typed_unmarshal(decoded_param, invocation.expects[i]) rescue nil + param = marshaled_param ? marshaled_param : param end + i += 1 + param.value + end + i = 0 + params = [] + invocation.expects.each do |spec| + type_binding = invocation.protocol.register_signature_type(spec) + info = WS::ParamInfo.create(spec, i, type_binding) + params << WS::Param.new(invocation.method_ordered_params[i], info) + i += 1 + end + invocation.method_ws_params = params + invocation.method_named_params = {} + invocation.method_ws_params.each do |param| + invocation.method_named_params[param.info.name] = param.value end else - protocol_request.protocol.marshal_exception(RuntimeError.new("missing protocol request or exception")) + invocation.method_ordered_params = [] + invocation.method_named_params = {} end - rescue Exception - nil + invocation end - class DispatchRequest - attr :api - attr :public_method_name - attr :method_name - attr :signature - attr :web_service_name - attr :web_service - attr :params - - def initialize(values={}) - values.each{|k,v| instance_variable_set("@#{k.to_s}", v)} - end + class Invocation + attr_accessor :protocol + attr_accessor :service_name + attr_accessor :api + attr_accessor :public_method_name + attr_accessor :api_method_name + attr_accessor :method_ordered_params + attr_accessor :method_named_params + attr_accessor :method_ws_params + attr_accessor :expects + attr_accessor :returns + attr_accessor :service end end end diff --git a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb index 511d00ca44..a35029b40d 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -1,3 +1,6 @@ +require 'benchmark' +require 'builder/xmlmarkup' + module ActionWebService # :nodoc: module Dispatcher # :nodoc: module ActionController # :nodoc: @@ -7,106 +10,121 @@ module ActionWebService # :nodoc: class << self alias_method :inherited_without_action_controller, :inherited end - alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke - alias_method :after_direct_invoke_without_action_controller, :after_direct_invoke + alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke end base.add_web_service_api_callback do |klass, api| if klass.web_service_dispatching_mode == :direct - klass.class_eval <<-EOS - def api - controller_dispatch_web_service_request - end - EOS + klass.class_eval 'def api; dispatch_web_service_request; end' end end base.add_web_service_definition_callback do |klass, name, info| if klass.web_service_dispatching_mode == :delegated - klass.class_eval <<-EOS - def #{name} - controller_dispatch_web_service_request - end - EOS + klass.class_eval "def #{name}; dispatch_web_service_request; end" end end base.extend(ClassMethods) - base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation) + base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods) end - module ClassMethods # :nodoc: + module ClassMethods def inherited(child) inherited_without_action_controller(child) - child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration) + child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction) end end - module Invocation # :nodoc: + module InstanceMethods private - def controller_dispatch_web_service_request - request, response, elapsed, exception = dispatch_web_service_request(@request) - if response - begin - log_request(request) - log_error(exception) if exception && logger - log_response(response, elapsed) - response_options = { :type => response.content_type, :disposition => 'inline' } - send_data(response.raw_body, response_options) - rescue Exception => e - log_error(e) unless logger.nil? - render_text("Internal protocol error", "500 Internal Server Error") + def dispatch_web_service_request + request = discover_web_service_request(@request) + if request + log_request(request, @request.raw_post) + response = nil + exception = nil + bm = Benchmark.measure do + begin + response = invoke_web_service_request(request) + rescue Exception => e + exception = e + end + end + if exception + log_error(exception) unless logger.nil? + send_web_service_error_response(request, exception) + else + send_web_service_response(response, bm.real) end else - logger.error("No response available") unless logger.nil? - render_text("Internal protocol error", "500 Internal Server Error") + exception = DispatcherError.new("Malformed SOAP or XML-RPC protocol message") + send_web_service_error_response(request, exception) end + rescue Exception => e + log_error(e) unless logger.nil? + send_web_service_error_response(request, e) end - def before_direct_invoke(request) - before_direct_invoke_without_action_controller(request) - @params ||= {} - signature = request.signature - if signature && (expects = request.signature[:expects]) - (0..(@method_params.size-1)).each do |i| - if expects[i].is_a?(Hash) - @params[expects[i].keys[0].to_s] = @method_params[i] - else - @params['param%d' % i] = @method_params[i] - end + def send_web_service_response(response, elapsed=nil) + log_response(response, elapsed) + options = { :type => response.content_type, :disposition => 'inline' } + send_data(response.body, options) + end + + def send_web_service_error_response(request, exception) + if request + unless self.class.web_service_exception_reporting + exception = DispatcherError.new("Internal server error (exception raised)") end + response = request.protocol.marshal_response(request.method_name, exception, exception.class) + send_web_service_response(response) + else + if self.class.web_service_exception_reporting + message = exception.message + else + message = "Exception raised" + end + render_text("Internal protocol error: #{message}", "500 #{message}") end - @params['action'] = request.method_name.to_s - @session ||= {} - @assigns ||= {} - return nil if before_action == false - true end - def after_direct_invoke(request) - after_direct_invoke_without_action_controller(request) + def web_service_direct_invoke(invocation) + @params ||= {} + invocation.method_named_params.each do |name, value| + @params[name] = value + end + @session ||= {} + @assigns ||= {} + @params['action'] = invocation.api_method_name.to_s + if before_action == false + raise(DispatcherError, "Method filtered") + end + return_value = web_service_direct_invoke_without_controller(invocation) after_action + return_value end - def log_request(request) - unless logger.nil? || request.nil? - logger.debug("\nWeb Service Request:") - indented = request.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n") - logger.debug(indented) + def log_request(request, body) + unless logger.nil? + name = request.method_name + params = request.method_params.map{|x| x.value.inspect} + service = request.service_name + logger.debug("\nWeb Service Request: #{name}(#{params}) #{service}") + logger.debug(indent(body)) end end - def log_response(response, elapsed) - unless logger.nil? || response.nil? - logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":")) - indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n") - logger.debug(indented) + def log_response(response, elapsed=nil) + unless logger.nil? + logger.debug("\nWeb Service Response (%f):" + (elapsed ? " (%f):" % elapsed : ":")) + logger.debug(indent(response.body)) end end - unless method_defined?(:logger) - def logger; @logger; end + def indent(body) + body.split(/\n/).map{|x| " #{x}"}.join("\n") end end - module WsdlGeneration # :nodoc: + module WsdlAction XsdNs = 'http://www.w3.org/2001/XMLSchema' WsdlNs = 'http://schemas.xmlsoap.org/wsdl/' SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/' @@ -117,40 +135,53 @@ module ActionWebService # :nodoc: case @request.method when :get begin - host_name = @request.env['HTTP_HOST'] || @request.env['SERVER_NAME'] - uri = "http://#{host_name}/#{controller_name}/" - soap_action_base = "/#{controller_name}" - xml = to_wsdl(self, uri, soap_action_base) - send_data(xml, :type => 'text/xml', :disposition => 'inline') + options = { :type => 'text/xml', :disposition => 'inline' } + send_data(to_wsdl, options) rescue Exception => e - log_error e unless logger.nil? - render_text('', "500 #{e.message}") + log_error(e) unless logger.nil? end when :post - render_text('', "500 POST not supported") + render_text('POST not supported', '500 POST not supported') end end private - def to_wsdl(container, uri, soap_action_base) - wsdl = "" - - web_service_dispatching_mode = container.web_service_dispatching_mode - mapper = container.class.soap_mapper - namespace = mapper.custom_namespace - wsdl_service_name = namespace.split(/:/)[1] - - services = {} - mapper.map_container_services(container) do |name, api, api_methods| - services[name] = [api, api_methods] + def base_uri + host = @request ? (@request.env['HTTP_HOST'] || @request.env['SERVER_NAME']) : 'localhost' + 'http://%s/%s/' % [host, controller_name] + end + + def to_wsdl + xml = '' + dispatching_mode = web_service_dispatching_mode + global_service_name = wsdl_service_name + namespace = "urn:#{global_service_name}" + soap_action_base = "/#{controller_name}" + + marshaler = WS::Marshaling::SoapMarshaler.new(namespace) + apis = {} + case dispatching_mode + when :direct + api = self.class.web_service_api + web_service_name = controller_class_name.sub(/Controller$/, '').underscore + apis[web_service_name] = [api, register_api(marshaler, api)] + when :delegated + self.class.web_services.each do |web_service_name, info| + service = web_service_object(web_service_name) + api = service.class.web_service_api + apis[web_service_name] = [api, register_api(marshaler, api)] + end end - custom_types = mapper.custom_types - - - xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2) + custom_types = [] + apis.values.each do |api, bindings| + bindings.each do |b| + custom_types << b if b.is_custom_type? + end + end + + xm = Builder::XmlMarkup.new(:target => xml, :indent => 2) xm.instruct! - - xm.definitions('name' => wsdl_service_name, + xm.definitions('name' => wsdl_service_name, 'targetNamespace' => namespace, 'xmlns:typens' => namespace, 'xmlns:xsd' => XsdNs, @@ -158,95 +189,95 @@ module ActionWebService # :nodoc: 'xmlns:soapenc' => SoapEncodingNs, 'xmlns:wsdl' => WsdlNs, 'xmlns' => WsdlNs) do - - # Custom type XSD generation + # Generate XSD if custom_types.size > 0 xm.types do xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do - custom_types.each do |klass, mapping| + custom_types.each do |binding| case - when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping) - xm.xsd(:complexType, 'name' => mapping.type_name) do + when binding.is_typed_array? + xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:complexContent) do xm.xsd(:restriction, 'base' => 'soapenc:Array') do xm.xsd(:attribute, 'ref' => 'soapenc:arrayType', - 'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]') + 'wsdl:arrayType' => binding.element_binding.qualified_type_name + '[]') end end end - when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping) - xm.xsd(:complexType, 'name' => mapping.type_name) do + when binding.is_typed_struct? + xm.xsd(:complexType, 'name' => binding.type_name) do xm.xsd(:all) do - mapping.each_attribute do |name, type_name| + binding.each_member do |name, type_name| xm.xsd(:element, 'name' => name, 'type' => type_name) end end end - else - raise(WsdlError, "unsupported mapping type #{mapping.class.name}") end end end end end - - services.each do |service_name, service_values| - service_api, api_methods = service_values - # Parameter list message definitions - api_methods.each do |method_name, method_signature| + + # APIs + apis.each do |api_name, values| + api = values[0] + api.api_methods.each do |name, info| gen = lambda do |msg_name, direction| xm.message('name' => msg_name) do sym = nil if direction == :out - if method_signature[:returns] - xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name) + returns = info[:returns] + if returns + binding = marshaler.register_type(returns[0]) + xm.part('name' => 'return', 'type' => binding.qualified_type_name) end else - mapping_list = method_signature[:expects] + expects = info[:expects] i = 1 - mapping_list.each do |mapping| - if mapping.is_a?(Hash) - param_name = mapping.keys.shift - mapping = mapping.values.shift + expects.each do |type| + if type.is_a?(Hash) + param_name = type.keys.shift + type = type.values.shift else param_name = "param#{i}" end - xm.part('name' => param_name, 'type' => mapping.qualified_type_name) + binding = marshaler.register_type(type) + xm.part('name' => param_name, 'type' => binding.qualified_type_name) i += 1 - end if mapping_list + end if expects end end end - public_name = service_api.public_api_method_name(method_name) + public_name = api.public_api_method_name(name) gen.call(public_name, :in) gen.call("#{public_name}Response", :out) end - - # Declare the port - port_name = port_name_for(wsdl_service_name, service_name) + + # Port + port_name = port_name_for(global_service_name, api_name) xm.portType('name' => port_name) do - api_methods.each do |method_name, method_signature| - public_name = service_api.public_api_method_name(method_name) + api.api_methods.each do |name, info| + public_name = api.public_api_method_name(name) xm.operation('name' => public_name) do xm.input('message' => "typens:#{public_name}") xm.output('message' => "typens:#{public_name}Response") end end end - - # Bind the port to SOAP - binding_name = binding_name_for(wsdl_service_name, service_name) + + # Bind it + binding_name = binding_name_for(global_service_name, api_name) xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport) - api_methods.each do |method_name, method_signature| - public_name = service_api.public_api_method_name(method_name) + api.api_methods.each do |name, info| + public_name = api.public_api_method_name(name) xm.operation('name' => public_name) do case web_service_dispatching_mode when :direct soap_action = soap_action_base + "/api/" + public_name when :delegated soap_action = soap_action_base \ - + "/" + service_name.to_s \ + + "/" + api_name.to_s \ + "/" + public_name end xm.soap(:operation, 'soapAction' => soap_action) @@ -266,32 +297,46 @@ module ActionWebService # :nodoc: end end end - - # Define the service - xm.service('name' => "#{wsdl_service_name}Service") do - services.each do |service_name, service_values| - port_name = port_name_for(wsdl_service_name, service_name) - binding_name = binding_name_for(wsdl_service_name, service_name) + + # Define it + xm.service('name' => "#{global_service_name}Service") do + apis.each do |api_name, values| + port_name = port_name_for(global_service_name, api_name) + binding_name = binding_name_for(global_service_name, api_name) case web_service_dispatching_mode when :direct binding_target = 'api' when :delegated - binding_target = service_name.to_s + binding_target = api_name.to_s end xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do - xm.soap(:address, 'location' => "#{uri}#{binding_target}") + xm.soap(:address, 'location' => "#{base_uri}#{binding_target}") end end end end end - def port_name_for(wsdl_service_name, service_name) - "#{wsdl_service_name}#{service_name.to_s.camelize}Port" + def port_name_for(global_service, service) + "#{global_service}#{service.to_s.camelize}Port" + end + + def binding_name_for(global_service, service) + "#{global_service}#{service.to_s.camelize}Binding" end - def binding_name_for(wsdl_service_name, service_name) - "#{wsdl_service_name}#{service_name.to_s.camelize}Binding" + def register_api(marshaler, api) + type_bindings = [] + api.api_methods.each do |name, info| + expects, returns = info[:expects], info[:returns] + if expects + expects.each{|type| type_bindings << marshaler.register_type(type)} + end + if returns + returns.each{|type| type_bindings << marshaler.register_type(type)} + end + end + type_bindings end end end |