mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
887e67c80e
See ticket #1428. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1408 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
376 lines
16 KiB
Ruby
376 lines
16 KiB
Ruby
require 'benchmark'
|
|
require 'builder/xmlmarkup'
|
|
|
|
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
|
|
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 '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 "def #{name}; dispatch_web_service_request; end"
|
|
elsif klass.web_service_dispatching_mode == :layered
|
|
klass.class_eval 'def api; dispatch_web_service_request; end'
|
|
end
|
|
end
|
|
base.extend(ClassMethods)
|
|
base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
|
|
end
|
|
|
|
module ClassMethods # :nodoc:
|
|
def inherited(child)
|
|
inherited_without_action_controller(child)
|
|
child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
|
|
end
|
|
end
|
|
|
|
module InstanceMethods # :nodoc:
|
|
private
|
|
def dispatch_web_service_request
|
|
exception = nil
|
|
begin
|
|
ws_request = discover_web_service_request(request)
|
|
rescue Exception => e
|
|
exception = e
|
|
end
|
|
if ws_request
|
|
ws_response = nil
|
|
exception = nil
|
|
bm = Benchmark.measure do
|
|
begin
|
|
ws_response = invoke_web_service_request(ws_request)
|
|
rescue Exception => e
|
|
exception = e
|
|
end
|
|
end
|
|
log_request(ws_request, request.raw_post)
|
|
if exception
|
|
log_error(exception) unless logger.nil?
|
|
send_web_service_error_response(ws_request, exception)
|
|
else
|
|
send_web_service_response(ws_response, bm.real)
|
|
end
|
|
else
|
|
exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
|
|
log_error(exception) unless logger.nil?
|
|
send_web_service_error_response(ws_request, exception)
|
|
end
|
|
rescue Exception => e
|
|
log_error(e) unless logger.nil?
|
|
send_web_service_error_response(ws_request, e)
|
|
end
|
|
|
|
def send_web_service_response(ws_response, elapsed=nil)
|
|
log_response(ws_response, elapsed)
|
|
options = { :type => ws_response.content_type, :disposition => 'inline' }
|
|
send_data(ws_response.body, options)
|
|
end
|
|
|
|
def send_web_service_error_response(ws_request, exception)
|
|
if ws_request
|
|
unless self.class.web_service_exception_reporting
|
|
exception = DispatcherError.new("Internal server error (exception raised)")
|
|
end
|
|
api_method = ws_request.api_method
|
|
public_method_name = api_method ? api_method.public_name : ws_request.method_name
|
|
return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
|
|
ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
|
|
send_web_service_response(ws_response)
|
|
else
|
|
if self.class.web_service_exception_reporting
|
|
message = exception.message
|
|
backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
|
|
else
|
|
message = "Exception raised"
|
|
backtrace = ""
|
|
end
|
|
render_text("Internal protocol error: #{message}#{backtrace}", "500 Internal Protocol Error")
|
|
end
|
|
end
|
|
|
|
def web_service_direct_invoke(invocation)
|
|
invocation.method_named_params.each do |name, value|
|
|
params[name] = value
|
|
end
|
|
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(ws_request, body)
|
|
unless logger.nil?
|
|
name = ws_request.method_name
|
|
api_method = ws_request.api_method
|
|
params = ws_request.method_params
|
|
if api_method && api_method.expects
|
|
params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
|
|
else
|
|
params = params.map{ |param| param.inspect }
|
|
end
|
|
service = ws_request.service_name
|
|
logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
|
|
logger.debug(indent(body))
|
|
end
|
|
end
|
|
|
|
def log_response(ws_response, elapsed=nil)
|
|
unless logger.nil?
|
|
elapsed = (elapsed ? " (%f):" % elapsed : ":")
|
|
logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
|
|
logger.debug(indent(ws_response.body))
|
|
end
|
|
end
|
|
|
|
def indent(body)
|
|
body.split(/\n/).map{|x| " #{x}"}.join("\n")
|
|
end
|
|
end
|
|
|
|
module WsdlAction # :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
|
|
options = { :type => 'text/xml', :disposition => 'inline' }
|
|
send_data(to_wsdl, options)
|
|
rescue Exception => e
|
|
log_error(e) unless logger.nil?
|
|
end
|
|
when :post
|
|
render_text('POST not supported', '500 POST not supported')
|
|
end
|
|
end
|
|
|
|
private
|
|
def base_uri
|
|
host = request.env['HTTP_HOST'] || request.env['SERVER_NAME'] || 'localhost'
|
|
relative_url_root = request.relative_url_root
|
|
scheme = request.ssl? ? 'https' : 'http'
|
|
'%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
|
|
end
|
|
|
|
def to_wsdl
|
|
xml = ''
|
|
dispatching_mode = web_service_dispatching_mode
|
|
global_service_name = wsdl_service_name
|
|
namespace = 'urn:ActionWebService'
|
|
soap_action_base = "/#{controller_name}"
|
|
|
|
marshaler = ActionWebService::Protocol::Soap::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(api, marshaler)]
|
|
when :delegated, :layered
|
|
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(api, marshaler)]
|
|
end
|
|
end
|
|
custom_types = []
|
|
apis.values.each do |api, bindings|
|
|
bindings.each do |b|
|
|
custom_types << b
|
|
end
|
|
end
|
|
|
|
xm = Builder::XmlMarkup.new(:target => xml, :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
|
|
# Generate XSD
|
|
if custom_types.size > 0
|
|
xm.types do
|
|
xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
|
|
custom_types.each do |binding|
|
|
case
|
|
when binding.type.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' => binding.element_binding.qualified_type_name('typens') + '[]')
|
|
end
|
|
end
|
|
end
|
|
when binding.type.structured?
|
|
xm.xsd(:complexType, 'name' => binding.type_name) do
|
|
xm.xsd(:all) do
|
|
binding.type.each_member do |name, type|
|
|
b = marshaler.register_type(type)
|
|
xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# APIs
|
|
apis.each do |api_name, values|
|
|
api = values[0]
|
|
api.api_methods.each do |name, method|
|
|
gen = lambda do |msg_name, direction|
|
|
xm.message('name' => message_name_for(api_name, msg_name)) do
|
|
sym = nil
|
|
if direction == :out
|
|
returns = method.returns
|
|
if returns
|
|
binding = marshaler.register_type(returns[0])
|
|
xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
|
|
end
|
|
else
|
|
expects = method.expects
|
|
expects.each do |type|
|
|
binding = marshaler.register_type(type)
|
|
xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
|
|
end if expects
|
|
end
|
|
end
|
|
end
|
|
public_name = method.public_name
|
|
gen.call(public_name, :in)
|
|
gen.call("#{public_name}Response", :out)
|
|
end
|
|
|
|
# Port
|
|
port_name = port_name_for(global_service_name, api_name)
|
|
xm.portType('name' => port_name) do
|
|
api.api_methods.each do |name, method|
|
|
xm.operation('name' => method.public_name) do
|
|
xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
|
|
xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
|
|
end
|
|
end
|
|
end
|
|
|
|
# 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.api_methods.each do |name, method|
|
|
xm.operation('name' => method.public_name) do
|
|
case web_service_dispatching_mode
|
|
when :direct
|
|
soap_action = soap_action_base + "/api/" + method.public_name
|
|
when :delegated, :layered
|
|
soap_action = soap_action_base \
|
|
+ "/" + api_name.to_s \
|
|
+ "/" + method.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 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, :layered
|
|
binding_target = 'api'
|
|
when :delegated
|
|
binding_target = api_name.to_s
|
|
end
|
|
xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
|
|
xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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 message_name_for(api_name, message_name)
|
|
mode = web_service_dispatching_mode
|
|
if mode == :layered || mode == :delegated
|
|
api_name.to_s + '-' + message_name
|
|
else
|
|
message_name
|
|
end
|
|
end
|
|
|
|
def register_api(api, marshaler)
|
|
bindings = {}
|
|
traverse_custom_types(api, marshaler) do |binding|
|
|
bindings[binding] = nil unless bindings.has_key?(binding)
|
|
element_binding = binding.element_binding
|
|
bindings[binding.element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
|
|
end
|
|
bindings.keys
|
|
end
|
|
|
|
def traverse_custom_types(api, marshaler, &block)
|
|
api.api_methods.each do |name, method|
|
|
expects, returns = method.expects, method.returns
|
|
expects.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if expects
|
|
returns.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if returns
|
|
end
|
|
end
|
|
|
|
def traverse_type(marshaler, type, &block)
|
|
yield marshaler.register_type(type)
|
|
if type.array?
|
|
yield marshaler.register_type(type.element_type)
|
|
type = type.element_type
|
|
end
|
|
type.each_member{ |name, type| traverse_type(marshaler, type, &block) } if type.structured?
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|