1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

fix WSDL generation, change the way protocols are instantiated, and add

the ability to override the namespace used in WSDL instead of always forcing
'urn:ActionWebService'


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1501 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Leon Breedt 2005-06-25 06:27:39 +00:00
parent af33a6a6b1
commit ebb6fb0928
14 changed files with 112 additions and 56 deletions

View file

@ -1,5 +1,9 @@
*SVN*
* Fix WSDL generation by aliasing #inherited instead of trying to overwrite it, or the WSDL action may end up not being defined in the controller
* Add ActionController::Base.wsdl_namespace option, to allow overriding of the namespace used in generated WSDL and SOAP messages. This is equivalent to the [WebService(Namespace = "Value")] attribute in .NET.
* Add workaround for Ruby 1.8.3's SOAP4R changing the return value of SOAP::Mapping::Registry#find_mapped_soap_class #1414 [Shugo Maeda]
* Fix moduled controller URLs in WSDL, and add unit test to verify the generated URL #1428

View file

@ -78,15 +78,15 @@ modes of dispatching protocol requests, _Direct_, and _Delegated_.
=== Direct dispatching
This is the default mode. In this mode, controller actions implement the API
methods, and parameters for incoming method calls will be placed in
<tt>@params</tt> (keyed by name), and <tt>@method_params</tt> (ordered list).
This is the default mode. In this mode, public controller instance methods
implement the API methods, and parameters are passed through to the methods in
accordance with the API specification.
The return value of the action is sent back as the return value to the
The return value of the method is sent back as the return value to the
caller.
In this mode, a special <tt>api</tt> action is generated in the target
controller to unwrap the protocol request, forward it on to the relevant action
controller to unwrap the protocol request, forward it on to the relevant method
and send back the wrapped return value. <em>This action must not be
overridden.</em>
@ -108,7 +108,7 @@ overridden.</em>
For this example, protocol requests for +Add+ and +Remove+ methods sent to
<tt>/person/api</tt> will be routed to the actions +add+ and +remove+.
<tt>/person/api</tt> will be routed to the controller methods +add+ and +remove+.
=== Delegated dispatching
@ -196,6 +196,25 @@ For this example, an XML-RPC call for a method with a name like
method on the <tt>:mt</tt> service.
== Customizing WSDL generation
You can customize the names used for the SOAP bindings in the generated
WSDL by using the wsdl_service_name option in a controller:
class WsController < ApplicationController
wsdl_service_name 'MyApp'
end
You can also customize the namespace used in the generated WSDL for
custom types and message definition types:
class WsController < ApplicationController
wsdl_namespace 'http://my.company.com/app/wsapi'
end
The default namespace used is 'urn:ActionWebService', if you don't supply
one.
== Testing your APIs

View file

@ -24,10 +24,10 @@ module ActionWebService # :nodoc:
# will be sent with HTTP POST.
#
# Valid options:
# [<tt>:type_namespace</tt>] If the remote server has used a custom namespace to
# declare its custom types, you can specify it here
# [<tt>:method_namespace</tt>] If the remote server has used a custom namespace to
# declare its methods, you can specify it here
# [<tt>:namespace</tt>] If the remote server has used a custom namespace to
# declare its custom types, you can specify it here. This would
# be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute
# in .NET, for example.
# [<tt>:driver_options</tt>] If you want to supply any custom SOAP RPC driver
# options, you can provide them as a Hash here
#
@ -43,10 +43,9 @@ module ActionWebService # :nodoc:
# client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts)
def initialize(api, endpoint_uri, options={})
super(api, endpoint_uri)
@type_namespace = options[:type_namespace] || 'urn:ActionWebService'
@method_namespace = options[:method_namespace] || 'urn:ActionWebService'
@namespace = options[:namespace] || 'urn:ActionWebService'
@driver_options = options[:driver_options] || {}
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace
@soap_action_base = options[:soap_action_base]
@soap_action_base ||= URI.parse(endpoint_uri).path
@driver = create_soap_rpc_driver(api, endpoint_uri)
@ -73,7 +72,7 @@ module ActionWebService # :nodoc:
driver = SoapDriver.new(endpoint_uri, nil)
driver.mapping_registry = @protocol.marshaler.registry
api.api_methods.each do |name, method|
qname = XSD::QName.new(@method_namespace, method.public_name)
qname = XSD::QName.new(@namespace, method.public_name)
action = soap_action(method.public_name)
expects = method.expects
returns = method.returns

View file

@ -6,9 +6,11 @@ module ActionWebService # :nodoc:
module ActionController # :nodoc:
def self.append_features(base) # :nodoc:
super
base.extend(ClassMethods)
base.class_eval do
class << self
alias_method :inherited_without_action_controller, :inherited
alias_method :inherited, :inherited_with_action_controller
end
alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
end
@ -24,12 +26,11 @@ module ActionWebService # :nodoc:
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)
def inherited_with_action_controller(child)
inherited_without_action_controller(child)
child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
end
@ -174,7 +175,7 @@ module ActionWebService # :nodoc:
xml = ''
dispatching_mode = web_service_dispatching_mode
global_service_name = wsdl_service_name
namespace = 'urn:ActionWebService'
namespace = wsdl_namespace || 'urn:ActionWebService'
soap_action_base = "/#{controller_name}"
marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)

View file

@ -4,6 +4,9 @@ module ActionWebService # :nodoc:
end
class AbstractProtocol # :nodoc:
def setup(controller)
end
def decode_action_pack_request(action_pack_request)
end

View file

@ -16,7 +16,7 @@ module ActionWebService # :nodoc:
private
def discover_web_service_request(action_pack_request)
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
protocol = protocol.new
protocol = protocol.create(self)
request = protocol.decode_action_pack_request(action_pack_request)
return request unless request.nil?
end
@ -25,7 +25,7 @@ module ActionWebService # :nodoc:
def create_web_service_client(api, protocol_name, endpoint_uri, options)
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
protocol = protocol.new
protocol = protocol.create(self)
client = protocol.protocol_client(api, protocol_name, endpoint_uri, options)
return client unless client.nil?
end

View file

@ -7,13 +7,21 @@ module ActionWebService # :nodoc:
def self.included(base)
base.register_protocol(SoapProtocol)
base.class_inheritable_option(:wsdl_service_name)
base.class_inheritable_option(:wsdl_namespace)
end
class SoapProtocol < AbstractProtocol # :nodoc:
DefaultEncoding = 'utf-8'
def marshaler
@marshaler ||= SoapMarshaler.new
attr :marshaler
def initialize(namespace=nil)
namespace ||= 'urn:ActionWebService'
@marshaler = SoapMarshaler.new namespace
end
def self.create(controller)
SoapProtocol.new(controller.wsdl_namespace)
end
def decode_action_pack_request(action_pack_request)
@ -47,7 +55,7 @@ module ActionWebService # :nodoc:
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)
qname = XSD::QName.new(marshaler.namespace, method_name)
param_def = []
if param_types
params = param_types.zip(params).map do |type, param|
@ -79,7 +87,7 @@ module ActionWebService # :nodoc:
return_binding = marshaler.register_type(return_type)
marshaler.annotate_arrays(return_binding, return_value)
end
qname = XSD::QName.new(marshaler.type_namespace, method_name)
qname = XSD::QName.new(marshaler.namespace, method_name)
if return_value.nil?
response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
else

View file

@ -17,11 +17,11 @@ module ActionWebService
end
class SoapMarshaler
attr :type_namespace
attr :namespace
attr :registry
def initialize(type_namespace=nil)
@type_namespace = type_namespace || 'urn:ActionWebService'
def initialize(namespace=nil)
@namespace = namespace || 'urn:ActionWebService'
@registry = Registry.new
@type2binding = {}
register_static_factories
@ -46,7 +46,7 @@ module ActionWebService
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))
qname = XSD::QName.new(@namespace, soap_type_name(type_class.name))
@registry.add(type_class,
SOAP::SOAPStruct,
typed_struct_factory(type_class),
@ -58,7 +58,7 @@ module ActionWebService
array_binding = nil
if type.array?
array_mapping = @registry.find_mapped_soap_class(Array)
qname = XSD::QName.new(@type_namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding)
end
@ -88,7 +88,7 @@ module ActionWebService
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))
qname = XSD::QName.new(@namespace, soap_type_name(type_class.name))
type_class.instance_variable_set('@qname', qname)
return SoapActiveRecordStructFactory.new
end

View file

@ -12,6 +12,10 @@ module ActionWebService # :nodoc:
end
class XmlRpcProtocol < AbstractProtocol # :nodoc:
def self.create(controller)
XmlRpcProtocol.new
end
def decode_action_pack_request(action_pack_request)
service_name = action_pack_request.parameters['action']
decode_request(action_pack_request.raw_post, service_name)

View file

@ -61,9 +61,9 @@ module ActionWebService
protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap
case protocol_name
when :soap
@protocol = Protocol::Soap::SoapProtocol.new
@protocol = Protocol::Soap::SoapProtocol.create(self)
when :xmlrpc
@protocol = Protocol::XmlRpc::XmlRpcProtocol.new
@protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self)
end
@invocation_cgi = request.respond_to?(:cgi) ? request.cgi : nil
bm = Benchmark.measure do

View file

@ -81,13 +81,13 @@ module Test # :nodoc:
def protocol
if @protocol.nil?
@protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new
@protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new(@controller)
else
case @protocol
when :xmlrpc
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@controller)
when :soap
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
else
@protocol
end

View file

@ -4,6 +4,8 @@ require 'stringio'
class ActionController::Base; def rescue_action(e) raise e end; end
module DispatcherTest
WsdlNamespace = 'http://rubyonrails.com/some/namespace'
class Node < ActiveRecord::Base
def initialize(*args)
super(*args)
@ -135,12 +137,14 @@ module DispatcherTest
class DelegatedController < AbstractController
web_service_dispatching_mode :delegated
wsdl_namespace WsdlNamespace
web_service(:test_service) { @service ||= Service.new; @service }
end
class LayeredController < AbstractController
web_service_dispatching_mode :layered
wsdl_namespace WsdlNamespace
web_service(:mt) { @mt_service ||= MTService.new; @mt_service }
web_service(:blogger) { @blogger_service ||= BloggerService.new; @blogger_service }
@ -149,6 +153,7 @@ module DispatcherTest
class DirectController < AbstractController
web_service_api DirectAPI
web_service_dispatching_mode :direct
wsdl_namespace WsdlNamespace
before_filter :alwaysfail, :only => [:before_filtered]
after_filter :alwaysok, :only => [:after_filtered]
@ -239,6 +244,7 @@ module DispatcherTest
class VirtualController < AbstractController
web_service_api VirtualAPI
wsdl_namespace WsdlNamespace
def fallback
"fallback!"
@ -307,7 +313,7 @@ module DispatcherCommonTests
controller.class.web_service_exception_reporting = true
send_garbage_request = lambda do
service_name = service_name(controller)
request = @protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest)
request = protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest)
response = ActionController::TestResponse.new
controller.process(request, response)
# puts response.body
@ -348,21 +354,21 @@ module DispatcherCommonTests
assert_equal person, @direct_controller.struct_pass_value
assert !person.equal?(@direct_controller.struct_pass_value)
result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test'})
case @protocol
when ActionWebService::Protocol::Soap::SoapProtocol
case
when soap?
assert_equal(person, @direct_controller.struct_pass_value)
assert !person.equal?(@direct_controller.struct_pass_value)
when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
when xmlrpc?
assert_equal(person, @direct_controller.struct_pass_value)
assert !person.equal?(@direct_controller.struct_pass_value)
end
assert_equal person, do_method_call(@direct_controller, 'HashStructReturn')[0]
result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test', 'nonexistent_attribute' => 'value'})
case @protocol
when ActionWebService::Protocol::Soap::SoapProtocol
case
when soap?
assert_equal(person, @direct_controller.struct_pass_value)
assert !person.equal?(@direct_controller.struct_pass_value)
when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
when xmlrpc?
assert_equal(person, @direct_controller.struct_pass_value)
assert !person.equal?(@direct_controller.struct_pass_value)
end
@ -398,6 +404,18 @@ module DispatcherCommonTests
def check_response(ap_response)
end
def protocol
@protocol
end
def soap?
protocol.is_a? ActionWebService::Protocol::Soap::SoapProtocol
end
def xmlrpc?
protocol.is_a? ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
end
def do_method_call(container, public_method_name, *params)
request_env = {}
mode = container.web_service_dispatching_mode
@ -417,7 +435,7 @@ module DispatcherCommonTests
service_name = $1
real_method_name = $2
end
if @protocol.is_a? ActionWebService::Protocol::Soap::SoapProtocol
if soap?
public_method_name = real_method_name
request_env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{real_method_name}"
end
@ -425,26 +443,26 @@ module DispatcherCommonTests
method = api.public_api_method_instance(real_method_name)
service_name = self.service_name(container)
end
@protocol.register_api(api)
protocol.register_api(api)
virtual = false
unless method
virtual = true
method ||= ActionWebService::API::Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
end
body = @protocol.encode_request(public_method_name, params.dup, method.expects)
body = protocol.encode_request(public_method_name, params.dup, method.expects)
# puts body
ap_request = @protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest)
ap_request = protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest)
ap_request.env.update(request_env)
update_request(ap_request)
ap_response = ActionController::TestResponse.new
container.process(ap_request, ap_response)
# puts ap_response.body
check_response(ap_response)
public_method_name, return_value = @protocol.decode_response(ap_response.body)
public_method_name, return_value = protocol.decode_response(ap_response.body)
unless is_exception?(return_value) || virtual
return_value = method.cast_returns(return_value)
end
if @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
if soap?
# http://dev.rubyonrails.com/changeset/920
assert_match(/Response$/, public_method_name) unless public_method_name == "fault"
end

View file

@ -27,12 +27,12 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
@delegated_controller = DelegatedController.new
@virtual_controller = VirtualController.new
@layered_controller = LayeredController.new
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@direct_controller)
end
def test_wsdl_generation
ensure_valid_wsdl_generation DelegatedController.new
ensure_valid_wsdl_generation DirectController.new
ensure_valid_wsdl_generation DelegatedController.new, DispatcherTest::WsdlNamespace
ensure_valid_wsdl_generation DirectController.new, DispatcherTest::WsdlNamespace
end
def test_wsdl_action
@ -90,12 +90,12 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
container.is_a?(DelegatedController) ? 'test_service' : 'api'
end
def ensure_valid_wsdl_generation(controller)
def ensure_valid_wsdl_generation(controller, expected_namespace)
wsdl = controller.generate_wsdl
ensure_valid_wsdl(controller, wsdl)
ensure_valid_wsdl(controller, wsdl, expected_namespace)
end
def ensure_valid_wsdl(controller, wsdl)
def ensure_valid_wsdl(controller, wsdl, expected_namespace)
definitions = WSDL::Parser.new.parse(wsdl)
assert(definitions.is_a?(WSDL::Definitions))
definitions.bindings.each do |binding|
@ -108,7 +108,7 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
end
types = definitions.collect_complextypes.map{|x| x.name}
types.each do |type|
assert(type.namespace == 'urn:ActionWebService')
assert(type.namespace == expected_namespace)
end
location = definitions.services[0].ports[0].soap_address.location
if controller.is_a?(DelegatedController)
@ -125,6 +125,6 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
test_request.env['HTTP_HOST'] = 'localhost'
test_response = ActionController::TestResponse.new
wsdl = controller.process(test_request, test_response).body
ensure_valid_wsdl(controller, wsdl)
ensure_valid_wsdl(controller, wsdl, DispatcherTest::WsdlNamespace)
end
end

View file

@ -5,11 +5,11 @@ class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase
include DispatcherCommonTests
def setup
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
@direct_controller = DirectController.new
@delegated_controller = DelegatedController.new
@layered_controller = LayeredController.new
@virtual_controller = VirtualController.new
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@direct_controller)
end
def test_layered_dispatching