aboutsummaryrefslogtreecommitdiffstats
path: root/actionwebservice/lib/action_web_service/dispatcher
diff options
context:
space:
mode:
authorLeon Breedt <bitserf@gmail.com>2005-02-19 08:29:42 +0000
committerLeon Breedt <bitserf@gmail.com>2005-02-19 08:29:42 +0000
commit418d487020d24e69b528fdbedfecb20a87f99fcb (patch)
tree1956d6982123df1638bdef8274dff50ae71b25c2 /actionwebservice/lib/action_web_service/dispatcher
parente7499638d06023ae493d14ec1dc4f58bad8ac168 (diff)
downloadrails-418d487020d24e69b528fdbedfecb20a87f99fcb.tar.gz
rails-418d487020d24e69b528fdbedfecb20a87f99fcb.tar.bz2
rails-418d487020d24e69b528fdbedfecb20a87f99fcb.zip
refactoring:
* move dispatching out of the Container into Dispatcher, it makes more sense for Container to only contain the list of web services defined in it. * collapse Wsdl and ActionController "routers" into an ActionController-specific module, no advantage to having them seperate as they were quite tightly coupled. rename to Dispatcher, to avoi confusion with Routing. * add a "_thing" suffix to concept-specific filenames. this is so that we don't end up with many soap.rb files, for example. * remove "virtual invocation" support. adds complexity, and it doesn't seem to add any value. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@679 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionwebservice/lib/action_web_service/dispatcher')
-rw-r--r--actionwebservice/lib/action_web_service/dispatcher/abstract.rb158
-rw-r--r--actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb301
2 files changed, 459 insertions, 0 deletions
diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
new file mode 100644
index 0000000000..e03446924a
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb
@@ -0,0 +1,158 @@
+require 'benchmark'
+
+module ActionWebService # :nodoc:
+ module Dispatcher # :nodoc:
+ class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
+ end
+
+ def self.append_features(base) # :nodoc:
+ super
+ base.class_inheritable_option(:web_service_dispatching_mode, :direct)
+ base.class_inheritable_option(:web_service_exception_reporting, true)
+ base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
+ end
+
+ 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)
+ case web_service_dispatching_mode
+ when :direct
+ dispatch_direct_request(protocol_request)
+ when :delegated
+ dispatch_delegated_request(protocol_request)
+ else
+ raise(ContainerError, "unsupported dispatching mode :#{web_service_dispatching_mode}")
+ 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)
+ end
+
+ def delegated_invoke(request)
+ cancellation_reason = nil
+ web_service = request.web_service
+ return_value = web_service.perform_invocation(request.method_name, request.params) do |x|
+ cancellation_reason = x
+ end
+ if cancellation_reason
+ raise(DispatcherError, "request canceled: #{cancellation_reason}")
+ end
+ return_value
+ end
+
+ def fallback_invoke(dispatch_request)
+ raise NotImplementedError
+ 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
+ case web_service_dispatching_mode
+ when :direct
+ api = self.class.web_service_api
+ 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}")
+ end
+ 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)
+ else
+ raise 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)
+ else
+ raise exception
+ end
+ end
+ else
+ protocol_request.protocol.marshal_exception(RuntimeError.new("missing protocol request or exception"))
+ end
+ rescue Exception
+ nil
+ 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
+ end
+ 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
new file mode 100644
index 0000000000..68c0f4ffb3
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
@@ -0,0 +1,301 @@
+module ActionWebService # :nodoc:
+ module Dispatcher # :nodoc:
+ module ActionController # :nodoc:
+ def self.append_features(base) # :nodoc:
+ super
+ base.class_eval do
+ class << self
+ alias_method :inherited_without_action_controller, :inherited
+ end
+ end
+ base.class_eval do
+ alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke
+ alias_method :after_direct_invoke_without_action_controller, :after_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
+ 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
+ end
+ end
+ base.extend(ClassMethods)
+ base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation)
+ end
+
+ module ClassMethods # :nodoc:
+ def inherited(child)
+ inherited_without_action_controller(child)
+ child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration)
+ end
+ end
+
+ module Invocation # :nodoc:
+ 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")
+ end
+ else
+ logger.error("No response available") unless logger.nil?
+ render_text("Internal protocol error", "500 Internal Server Error")
+ end
+ 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
+ end
+ 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)
+ after_action
+ 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)
+ end
+ end
+
+ def log_response(response, elapsed)
+ unless logger.nil? || response.nil?
+ logger.debug("\nWeb Service Response (%f):" % elapsed)
+ indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
+ logger.debug(indented)
+ end
+ end
+
+ unless method_defined?(:logger)
+ def logger; @logger; end
+ end
+ end
+
+ module WsdlGeneration # :nodoc:
+ XsdNs = 'http://www.w3.org/2001/XMLSchema'
+ WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
+ SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
+ SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
+ SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
+
+ def wsdl
+ 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')
+ rescue Exception => e
+ log_error e unless logger.nil?
+ render_text('', "500 #{e.message}")
+ end
+ when :post
+ render_text('', "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]
+ end
+ custom_types = mapper.custom_types
+
+
+ xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2)
+ xm.instruct!
+
+ xm.definitions('name' => wsdl_service_name,
+ 'targetNamespace' => namespace,
+ 'xmlns:typens' => namespace,
+ 'xmlns:xsd' => XsdNs,
+ 'xmlns:soap' => SoapNs,
+ 'xmlns:soapenc' => SoapEncodingNs,
+ 'xmlns:wsdl' => WsdlNs,
+ 'xmlns' => WsdlNs) do
+
+ # Custom type XSD generation
+ if custom_types.size > 0
+ xm.types do
+ xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
+ custom_types.each do |klass, mapping|
+ case
+ when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping)
+ xm.xsd(:complexType, 'name' => mapping.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 + '[]')
+ end
+ end
+ end
+ when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping)
+ xm.xsd(:complexType, 'name' => mapping.type_name) do
+ xm.xsd(:all) do
+ mapping.each_attribute 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|
+ 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)
+ end
+ else
+ mapping_list = method_signature[:expects]
+ i = 1
+ mapping_list.each do |mapping|
+ if mapping.is_a?(Hash)
+ param_name = mapping.keys.shift
+ mapping = mapping.values.shift
+ else
+ param_name = "param#{i}"
+ end
+ xm.part('name' => param_name, 'type' => mapping.qualified_type_name)
+ i += 1
+ end if mapping_list
+ end
+ end
+ end
+ public_name = service_api.public_api_method_name(method_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)
+ xm.portType('name' => port_name) do
+ api_methods.each do |method_name, method_signature|
+ public_name = service_api.public_api_method_name(method_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)
+ 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)
+ 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 \
+ + "/" + public_name
+ end
+ xm.soap(:operation, 'soapAction' => soap_action)
+ xm.input do
+ xm.soap(:body,
+ 'use' => 'encoded',
+ 'namespace' => namespace,
+ 'encodingStyle' => SoapEncodingNs)
+ end
+ xm.output do
+ xm.soap(:body,
+ 'use' => 'encoded',
+ 'namespace' => namespace,
+ 'encodingStyle' => SoapEncodingNs)
+ end
+ end
+ 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)
+ case web_service_dispatching_mode
+ when :direct
+ binding_target = 'api'
+ when :delegated
+ binding_target = service_name.to_s
+ end
+ xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
+ xm.soap(:address, 'location' => "#{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"
+ end
+
+ def binding_name_for(wsdl_service_name, service_name)
+ "#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
+ end
+ end
+ end
+ end
+end