rubyjedi-actionwebservice 2.3.5.20100615120735
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +335 -0
- data/MIT-LICENSE +21 -0
- data/README +381 -0
- data/Rakefile +180 -0
- data/TODO +32 -0
- data/examples/googlesearch/README +143 -0
- data/examples/googlesearch/autoloading/google_search_api.rb +50 -0
- data/examples/googlesearch/autoloading/google_search_controller.rb +57 -0
- data/examples/googlesearch/delegated/google_search_service.rb +108 -0
- data/examples/googlesearch/delegated/search_controller.rb +7 -0
- data/examples/googlesearch/direct/google_search_api.rb +50 -0
- data/examples/googlesearch/direct/search_controller.rb +58 -0
- data/examples/metaWeblog/README +17 -0
- data/examples/metaWeblog/apis/blogger_api.rb +60 -0
- data/examples/metaWeblog/apis/blogger_service.rb +34 -0
- data/examples/metaWeblog/apis/meta_weblog_api.rb +67 -0
- data/examples/metaWeblog/apis/meta_weblog_service.rb +48 -0
- data/examples/metaWeblog/controllers/xmlrpc_controller.rb +16 -0
- data/generators/web_service/USAGE +28 -0
- data/generators/web_service/templates/api_definition.rb +5 -0
- data/generators/web_service/templates/controller.rb +8 -0
- data/generators/web_service/templates/functional_test.rb +19 -0
- data/generators/web_service/web_service_generator.rb +29 -0
- data/lib/action_web_service/acts_as_web_service.rb +24 -0
- data/lib/action_web_service/api.rb +297 -0
- data/lib/action_web_service/base.rb +38 -0
- data/lib/action_web_service/casting.rb +151 -0
- data/lib/action_web_service/client/base.rb +28 -0
- data/lib/action_web_service/client/soap_client.rb +113 -0
- data/lib/action_web_service/client/xmlrpc_client.rb +58 -0
- data/lib/action_web_service/client.rb +3 -0
- data/lib/action_web_service/container/action_controller_container.rb +93 -0
- data/lib/action_web_service/container/delegated_container.rb +86 -0
- data/lib/action_web_service/container/direct_container.rb +69 -0
- data/lib/action_web_service/container.rb +3 -0
- data/lib/action_web_service/dispatcher/abstract.rb +208 -0
- data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +396 -0
- data/lib/action_web_service/dispatcher.rb +2 -0
- data/lib/action_web_service/invocation.rb +202 -0
- data/lib/action_web_service/protocol/abstract.rb +112 -0
- data/lib/action_web_service/protocol/discovery.rb +37 -0
- data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +242 -0
- data/lib/action_web_service/protocol/soap_protocol.rb +176 -0
- data/lib/action_web_service/protocol/xmlrpc_protocol.rb +123 -0
- data/lib/action_web_service/protocol.rb +4 -0
- data/lib/action_web_service/scaffolding.rb +281 -0
- data/lib/action_web_service/simple.rb +53 -0
- data/lib/action_web_service/string_to_datetime_for_soap.rb +16 -0
- data/lib/action_web_service/struct.rb +68 -0
- data/lib/action_web_service/support/class_inheritable_options.rb +26 -0
- data/lib/action_web_service/support/signature_types.rb +261 -0
- data/lib/action_web_service/templates/scaffolds/layout.html.erb +65 -0
- data/lib/action_web_service/templates/scaffolds/methods.html.erb +6 -0
- data/lib/action_web_service/templates/scaffolds/parameters.html.erb +29 -0
- data/lib/action_web_service/templates/scaffolds/result.html.erb +30 -0
- data/lib/action_web_service/test_invoke.rb +110 -0
- data/lib/action_web_service/version.rb +9 -0
- data/lib/action_web_service.rb +60 -0
- data/lib/actionwebservice.rb +1 -0
- data/setup.rb +1379 -0
- data/test/abstract_client.rb +184 -0
- data/test/abstract_dispatcher.rb +549 -0
- data/test/abstract_unit.rb +43 -0
- data/test/actionwebservice_unittest.db +0 -0
- data/test/api_test.rb +102 -0
- data/test/apis/auto_load_api.rb +3 -0
- data/test/apis/broken_auto_load_api.rb +2 -0
- data/test/base_test.rb +42 -0
- data/test/casting_test.rb +95 -0
- data/test/client_soap_test.rb +156 -0
- data/test/client_xmlrpc_test.rb +154 -0
- data/test/container_test.rb +75 -0
- data/test/debug.log +12305 -0
- data/test/dispatcher_action_controller_soap_test.rb +139 -0
- data/test/dispatcher_action_controller_xmlrpc_test.rb +59 -0
- data/test/fixtures/db_definitions/mysql.sql +8 -0
- data/test/fixtures/db_definitions/sqlite3.sql +8 -0
- data/test/fixtures/users.yml +12 -0
- data/test/gencov +3 -0
- data/test/invocation_test.rb +186 -0
- data/test/run +6 -0
- data/test/scaffolded_controller_test.rb +147 -0
- data/test/struct_test.rb +84 -0
- data/test/test_invoke_test.rb +113 -0
- metadata +182 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActionWebService # :nodoc:
|
2
|
+
module Client # :nodoc:
|
3
|
+
class ClientError < StandardError # :nodoc:
|
4
|
+
end
|
5
|
+
|
6
|
+
class Base # :nodoc:
|
7
|
+
def initialize(api, endpoint_uri)
|
8
|
+
@api = api
|
9
|
+
@endpoint_uri = endpoint_uri
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args) # :nodoc:
|
13
|
+
call_name = method_name(name)
|
14
|
+
return super(name, *args) if call_name.nil?
|
15
|
+
self.perform_invocation(call_name, args)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def method_name(name)
|
20
|
+
if @api.has_api_method?(name.to_sym)
|
21
|
+
name.to_s
|
22
|
+
elsif @api.has_public_api_method?(name.to_s)
|
23
|
+
@api.api_method_name(name.to_s).to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'soap/rpc/driver'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module ActionWebService # :nodoc:
|
5
|
+
module Client # :nodoc:
|
6
|
+
|
7
|
+
# Implements SOAP client support (using RPC encoding for the messages).
|
8
|
+
#
|
9
|
+
# ==== Example Usage
|
10
|
+
#
|
11
|
+
# class PersonAPI < ActionWebService::API::Base
|
12
|
+
# api_method :find_all, :returns => [[Person]]
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
|
16
|
+
# persons = soap_client.find_all
|
17
|
+
#
|
18
|
+
class Soap < Base
|
19
|
+
# provides access to the underlying soap driver
|
20
|
+
attr_reader :driver
|
21
|
+
|
22
|
+
# Creates a new web service client using the SOAP RPC protocol.
|
23
|
+
#
|
24
|
+
# +api+ must be an ActionWebService::API::Base derivative, and
|
25
|
+
# +endpoint_uri+ must point at the relevant URL to which protocol requests
|
26
|
+
# will be sent with HTTP POST.
|
27
|
+
#
|
28
|
+
# Valid options:
|
29
|
+
# [<tt>:namespace</tt>] If the remote server has used a custom namespace to
|
30
|
+
# declare its custom types, you can specify it here. This would
|
31
|
+
# be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute
|
32
|
+
# in .NET, for example.
|
33
|
+
# [<tt>:driver_options</tt>] If you want to supply any custom SOAP RPC driver
|
34
|
+
# options, you can provide them as a Hash here
|
35
|
+
#
|
36
|
+
# The <tt>:driver_options</tt> option can be used to configure the backend SOAP
|
37
|
+
# RPC driver. An example of configuring the SOAP backend to do
|
38
|
+
# client-certificate authenticated SSL connections to the server:
|
39
|
+
#
|
40
|
+
# opts = {}
|
41
|
+
# opts['protocol.http.ssl_config.verify_mode'] = 'OpenSSL::SSL::VERIFY_PEER'
|
42
|
+
# opts['protocol.http.ssl_config.client_cert'] = client_cert_file_path
|
43
|
+
# opts['protocol.http.ssl_config.client_key'] = client_key_file_path
|
44
|
+
# opts['protocol.http.ssl_config.ca_file'] = ca_cert_file_path
|
45
|
+
# client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts)
|
46
|
+
def initialize(api, endpoint_uri, options={})
|
47
|
+
super(api, endpoint_uri)
|
48
|
+
@namespace = options[:namespace] || 'urn:ActionWebService'
|
49
|
+
@driver_options = options[:driver_options] || {}
|
50
|
+
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace
|
51
|
+
@soap_action_base = options[:soap_action_base]
|
52
|
+
@soap_action_base ||= URI.parse(endpoint_uri).path
|
53
|
+
@driver = create_soap_rpc_driver(api, endpoint_uri)
|
54
|
+
@driver_options.each do |name, value|
|
55
|
+
@driver.options[name.to_s] = value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
def perform_invocation(method_name, args)
|
61
|
+
method = @api.api_methods[method_name.to_sym]
|
62
|
+
args = method.cast_expects(args.dup) rescue args
|
63
|
+
return_value = @driver.send(method_name, *args)
|
64
|
+
method.cast_returns(return_value.dup) rescue return_value
|
65
|
+
end
|
66
|
+
|
67
|
+
def soap_action(method_name)
|
68
|
+
"#{@soap_action_base}/#{method_name}"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def create_soap_rpc_driver(api, endpoint_uri)
|
73
|
+
@protocol.register_api(api)
|
74
|
+
driver = SoapDriver.new(endpoint_uri, nil)
|
75
|
+
driver.mapping_registry = @protocol.marshaler.registry
|
76
|
+
api.api_methods.each do |name, method|
|
77
|
+
qname = XSD::QName.new(@namespace, method.public_name)
|
78
|
+
action = soap_action(method.public_name)
|
79
|
+
expects = method.expects
|
80
|
+
returns = method.returns
|
81
|
+
param_def = []
|
82
|
+
if expects
|
83
|
+
expects.each do |type|
|
84
|
+
type_binding = @protocol.marshaler.lookup_type(type)
|
85
|
+
if SOAP::Version >= "1.5.5"
|
86
|
+
param_def << ['in', type.name.to_s, [type_binding.type.type_class.to_s]]
|
87
|
+
else
|
88
|
+
param_def << ['in', type.name, type_binding.mapping]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
if returns
|
93
|
+
type_binding = @protocol.marshaler.lookup_type(returns[0])
|
94
|
+
if SOAP::Version >= "1.5.5"
|
95
|
+
param_def << ['retval', 'return', [type_binding.type.type_class.to_s]]
|
96
|
+
else
|
97
|
+
param_def << ['retval', 'return', type_binding.mapping]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
driver.add_method(qname, action, method.name.to_s, param_def)
|
101
|
+
end
|
102
|
+
driver
|
103
|
+
end
|
104
|
+
|
105
|
+
class SoapDriver < SOAP::RPC::Driver # :nodoc:
|
106
|
+
def add_method(qname, soapaction, name, param_def)
|
107
|
+
@proxy.add_rpc_method(qname, soapaction, name, param_def)
|
108
|
+
add_rpc_method_interface(name, param_def)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'xmlrpc/client'
|
3
|
+
|
4
|
+
module ActionWebService # :nodoc:
|
5
|
+
module Client # :nodoc:
|
6
|
+
|
7
|
+
# Implements XML-RPC client support
|
8
|
+
#
|
9
|
+
# ==== Example Usage
|
10
|
+
#
|
11
|
+
# class BloggerAPI < ActionWebService::API::Base
|
12
|
+
# inflect_names false
|
13
|
+
# api_method :getRecentPosts, :returns => [[Blog::Post]]
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
|
17
|
+
# posts = blog.getRecentPosts
|
18
|
+
class XmlRpc < Base
|
19
|
+
|
20
|
+
# Creates a new web service client using the XML-RPC protocol.
|
21
|
+
#
|
22
|
+
# +api+ must be an ActionWebService::API::Base derivative, and
|
23
|
+
# +endpoint_uri+ must point at the relevant URL to which protocol requests
|
24
|
+
# will be sent with HTTP POST.
|
25
|
+
#
|
26
|
+
# Valid options:
|
27
|
+
# [<tt>:handler_name</tt>] If the remote server defines its services inside special
|
28
|
+
# handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example),
|
29
|
+
# provide it here, or your method calls will fail
|
30
|
+
def initialize(api, endpoint_uri, options={})
|
31
|
+
@api = api
|
32
|
+
@handler_name = options[:handler_name]
|
33
|
+
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
|
34
|
+
@client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def perform_invocation(method_name, args)
|
39
|
+
method = @api.api_methods[method_name.to_sym]
|
40
|
+
if method.expects && method.expects.length != args.length
|
41
|
+
raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
|
42
|
+
end
|
43
|
+
args = method.cast_expects(args.dup) rescue args
|
44
|
+
if method.expects
|
45
|
+
method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
|
46
|
+
end
|
47
|
+
ok, return_value = @client.call2(public_name(method_name), *args)
|
48
|
+
return (method.cast_returns(return_value.dup) rescue return_value) if ok
|
49
|
+
raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def public_name(method_name)
|
53
|
+
public_name = @api.public_api_method_name(method_name)
|
54
|
+
@handler_name ? "#{@handler_name}.#{public_name}" : public_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ActionWebService # :nodoc:
|
2
|
+
module Container # :nodoc:
|
3
|
+
module ActionController # :nodoc:
|
4
|
+
def self.included(base) # :nodoc:
|
5
|
+
class << base
|
6
|
+
include ClassMethods
|
7
|
+
alias_method_chain :inherited, :api
|
8
|
+
alias_method_chain :web_service_api, :require
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Creates a client for accessing remote web services, using the
|
14
|
+
# given +protocol+ to communicate with the +endpoint_uri+.
|
15
|
+
#
|
16
|
+
# ==== Example
|
17
|
+
#
|
18
|
+
# class MyController < ActionController::Base
|
19
|
+
# web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# In this example, a protected method named <tt>blogger</tt> will
|
23
|
+
# now exist on the controller, and calling it will return the
|
24
|
+
# XML-RPC client object for working with that remote service.
|
25
|
+
#
|
26
|
+
# +options+ is the set of protocol client specific options (see
|
27
|
+
# a protocol client class for details).
|
28
|
+
#
|
29
|
+
# If your API definition does not exist on the load path with the
|
30
|
+
# correct rules for it to be found using +name+, you can pass in
|
31
|
+
# the API definition class via +options+, using a key of <tt>:api</tt>
|
32
|
+
def web_client_api(name, protocol, endpoint_uri, options={})
|
33
|
+
unless method_defined?(name)
|
34
|
+
api_klass = options.delete(:api) || require_web_service_api(name)
|
35
|
+
class_eval do
|
36
|
+
define_method(name) do
|
37
|
+
create_web_service_client(api_klass, protocol, endpoint_uri, options)
|
38
|
+
end
|
39
|
+
protected name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def web_service_api_with_require(definition=nil) # :nodoc:
|
45
|
+
return web_service_api_without_require if definition.nil?
|
46
|
+
case definition
|
47
|
+
when String, Symbol
|
48
|
+
klass = require_web_service_api(definition)
|
49
|
+
else
|
50
|
+
klass = definition
|
51
|
+
end
|
52
|
+
web_service_api_without_require(klass)
|
53
|
+
end
|
54
|
+
|
55
|
+
def require_web_service_api(name) # :nodoc:
|
56
|
+
case name
|
57
|
+
when String, Symbol
|
58
|
+
file_name = name.to_s.underscore + "_api"
|
59
|
+
class_name = file_name.camelize
|
60
|
+
class_names = [class_name, class_name.sub(/Api$/, 'API')]
|
61
|
+
begin
|
62
|
+
require_dependency(file_name)
|
63
|
+
rescue LoadError => load_error
|
64
|
+
requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
|
65
|
+
msg = requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
|
66
|
+
raise LoadError.new(msg).copy_blame!(load_error)
|
67
|
+
end
|
68
|
+
klass = nil
|
69
|
+
class_names.each do |name|
|
70
|
+
klass = name.constantize rescue nil
|
71
|
+
break unless klass.nil?
|
72
|
+
end
|
73
|
+
unless klass
|
74
|
+
raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
|
75
|
+
end
|
76
|
+
klass
|
77
|
+
else
|
78
|
+
raise(ArgumentError, "expected String or Symbol argument")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def inherited_with_api(child)
|
84
|
+
inherited_without_api(child)
|
85
|
+
begin child.web_service_api(child.controller_path)
|
86
|
+
rescue MissingSourceFile => e
|
87
|
+
raise unless e.is_missing?("apis/#{child.controller_path}_api")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module ActionWebService # :nodoc:
|
2
|
+
module Container # :nodoc:
|
3
|
+
module Delegated # :nodoc:
|
4
|
+
class ContainerError < ActionWebServiceError # :nodoc:
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.included(base) # :nodoc:
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Declares a web service that will provide access to the API of the given
|
14
|
+
# +object+. +object+ must be an ActionWebService::Base derivative.
|
15
|
+
#
|
16
|
+
# Web service object creation can either be _immediate_, where the object
|
17
|
+
# instance is given at class definition time, or _deferred_, where
|
18
|
+
# object instantiation is delayed until request time.
|
19
|
+
#
|
20
|
+
# ==== Immediate web service object example
|
21
|
+
#
|
22
|
+
# class ApiController < ApplicationController
|
23
|
+
# web_service_dispatching_mode :delegated
|
24
|
+
#
|
25
|
+
# web_service :person, PersonService.new
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# For deferred instantiation, a block should be given instead of an
|
29
|
+
# object instance. This block will be executed in controller instance
|
30
|
+
# context, so it can rely on controller instance variables being present.
|
31
|
+
#
|
32
|
+
# ==== Deferred web service object example
|
33
|
+
#
|
34
|
+
# class ApiController < ApplicationController
|
35
|
+
# web_service_dispatching_mode :delegated
|
36
|
+
#
|
37
|
+
# web_service(:person) { PersonService.new(request.env) }
|
38
|
+
# end
|
39
|
+
def web_service(name, object=nil, &block)
|
40
|
+
if (object && block_given?) || (object.nil? && block.nil?)
|
41
|
+
raise(ContainerError, "either service, or a block must be given")
|
42
|
+
end
|
43
|
+
name = name.to_sym
|
44
|
+
if block_given?
|
45
|
+
info = { name => { :block => block } }
|
46
|
+
else
|
47
|
+
info = { name => { :object => object } }
|
48
|
+
end
|
49
|
+
write_inheritable_hash("web_services", info)
|
50
|
+
call_web_service_definition_callbacks(self, name, info)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Whether this service contains a service with the given +name+
|
54
|
+
def has_web_service?(name)
|
55
|
+
web_services.has_key?(name.to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def web_services # :nodoc:
|
59
|
+
read_inheritable_attribute("web_services") || {}
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_web_service_definition_callback(&block) # :nodoc:
|
63
|
+
write_inheritable_array("web_service_definition_callbacks", [block])
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
|
68
|
+
(read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
|
69
|
+
block.call(container_class, web_service_name, service_info)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods # :nodoc:
|
75
|
+
def web_service_object(web_service_name)
|
76
|
+
info = self.class.web_services[web_service_name.to_sym]
|
77
|
+
unless info
|
78
|
+
raise(ContainerError, "no such web service '#{web_service_name}'")
|
79
|
+
end
|
80
|
+
service = info[:block]
|
81
|
+
service ? self.instance_eval(&service) : info[:object]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActionWebService # :nodoc:
|
2
|
+
module Container # :nodoc:
|
3
|
+
module Direct # :nodoc:
|
4
|
+
class ContainerError < ActionWebServiceError # :nodoc:
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.included(base) # :nodoc:
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Attaches ActionWebService API +definition+ to the calling class.
|
13
|
+
#
|
14
|
+
# Action Controllers can have a default associated API, removing the need
|
15
|
+
# to call this method if you follow the Action Web Service naming conventions.
|
16
|
+
#
|
17
|
+
# A controller with a class name of GoogleSearchController will
|
18
|
+
# implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
|
19
|
+
# API definition class to be named <tt>GoogleSearchAPI</tt> or
|
20
|
+
# <tt>GoogleSearchApi</tt>.
|
21
|
+
#
|
22
|
+
# ==== Service class example
|
23
|
+
#
|
24
|
+
# class MyService < ActionWebService::Base
|
25
|
+
# web_service_api MyAPI
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class MyAPI < ActionWebService::API::Base
|
29
|
+
# ...
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# ==== Controller class example
|
33
|
+
#
|
34
|
+
# class MyController < ActionController::Base
|
35
|
+
# web_service_api MyAPI
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class MyAPI < ActionWebService::API::Base
|
39
|
+
# ...
|
40
|
+
# end
|
41
|
+
def web_service_api(definition=nil)
|
42
|
+
if definition.nil?
|
43
|
+
read_inheritable_attribute("web_service_api")
|
44
|
+
else
|
45
|
+
if definition.is_a?(Symbol)
|
46
|
+
raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
|
47
|
+
end
|
48
|
+
unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
|
49
|
+
raise(ContainerError, "#{definition.to_s} is not a valid API definition")
|
50
|
+
end
|
51
|
+
write_inheritable_attribute("web_service_api", definition)
|
52
|
+
call_web_service_api_callbacks(self, definition)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_web_service_api_callback(&block) # :nodoc:
|
57
|
+
write_inheritable_array("web_service_api_callbacks", [block])
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def call_web_service_api_callbacks(container_class, definition)
|
62
|
+
(read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
|
63
|
+
block.call(container_class, definition)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
module ActionWebService # :nodoc:
|
4
|
+
module Dispatcher # :nodoc:
|
5
|
+
class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
set_backtrace(caller)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base) # :nodoc:
|
13
|
+
base.class_inheritable_option(:web_service_dispatching_mode, :direct)
|
14
|
+
base.class_inheritable_option(:web_service_inflect_type, false)
|
15
|
+
base.class_inheritable_option(:web_service_exception_reporting, true)
|
16
|
+
base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods # :nodoc:
|
20
|
+
private
|
21
|
+
def invoke_web_service_request(protocol_request)
|
22
|
+
invocation = web_service_invocation(protocol_request)
|
23
|
+
if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
|
24
|
+
xmlrpc_multicall_invoke(invocation)
|
25
|
+
else
|
26
|
+
web_service_invoke(invocation)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def web_service_direct_invoke(invocation)
|
31
|
+
@method_params = invocation.method_ordered_params
|
32
|
+
arity = method(invocation.api_method.name).arity rescue 0
|
33
|
+
if arity < 0 || arity > 0
|
34
|
+
params = @method_params
|
35
|
+
else
|
36
|
+
params = []
|
37
|
+
end
|
38
|
+
web_service_filtered_invoke(invocation, params)
|
39
|
+
end
|
40
|
+
|
41
|
+
def web_service_delegated_invoke(invocation)
|
42
|
+
web_service_filtered_invoke(invocation, invocation.method_ordered_params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def web_service_filtered_invoke(invocation, params)
|
46
|
+
cancellation_reason = nil
|
47
|
+
return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
|
48
|
+
cancellation_reason = x
|
49
|
+
end
|
50
|
+
if cancellation_reason
|
51
|
+
raise(DispatcherError, "request canceled: #{cancellation_reason}")
|
52
|
+
end
|
53
|
+
return_value
|
54
|
+
end
|
55
|
+
|
56
|
+
def web_service_invoke(invocation)
|
57
|
+
case web_service_dispatching_mode
|
58
|
+
when :direct
|
59
|
+
return_value = web_service_direct_invoke(invocation)
|
60
|
+
when :delegated, :layered
|
61
|
+
return_value = web_service_delegated_invoke(invocation)
|
62
|
+
end
|
63
|
+
web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
|
64
|
+
end
|
65
|
+
|
66
|
+
def xmlrpc_multicall_invoke(invocations)
|
67
|
+
responses = []
|
68
|
+
invocations.each do |invocation|
|
69
|
+
if invocation.is_a?(Hash)
|
70
|
+
responses << [invocation, nil]
|
71
|
+
next
|
72
|
+
end
|
73
|
+
begin
|
74
|
+
case web_service_dispatching_mode
|
75
|
+
when :direct
|
76
|
+
return_value = web_service_direct_invoke(invocation)
|
77
|
+
when :delegated, :layered
|
78
|
+
return_value = web_service_delegated_invoke(invocation)
|
79
|
+
end
|
80
|
+
api_method = invocation.api_method
|
81
|
+
if invocation.api.has_api_method?(api_method.name)
|
82
|
+
response_type = (api_method.returns ? api_method.returns[0] : nil)
|
83
|
+
return_value = api_method.cast_returns(return_value)
|
84
|
+
else
|
85
|
+
response_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
|
86
|
+
end
|
87
|
+
responses << [return_value, response_type]
|
88
|
+
rescue Exception => e
|
89
|
+
responses << [{ 'faultCode' => 3, 'faultString' => e.message }, nil]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
invocation = invocations[0]
|
93
|
+
invocation.protocol.encode_multicall_response(responses, invocation.protocol_options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def web_service_invocation(request, level = 0)
|
97
|
+
public_method_name = request.method_name
|
98
|
+
invocation = Invocation.new
|
99
|
+
invocation.protocol = request.protocol
|
100
|
+
invocation.protocol_options = request.protocol_options
|
101
|
+
invocation.service_name = request.service_name
|
102
|
+
if web_service_dispatching_mode == :layered
|
103
|
+
case invocation.protocol
|
104
|
+
when Protocol::Soap::SoapProtocol
|
105
|
+
soap_action = request.protocol_options[:soap_action]
|
106
|
+
if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
|
107
|
+
invocation.service_name = $1
|
108
|
+
end
|
109
|
+
when Protocol::XmlRpc::XmlRpcProtocol
|
110
|
+
if request.method_name =~ /^([^\.]+)\.(.*)$/
|
111
|
+
public_method_name = $2
|
112
|
+
invocation.service_name = $1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
|
117
|
+
if public_method_name == 'multicall' && invocation.service_name == 'system'
|
118
|
+
if level > 0
|
119
|
+
raise(DispatcherError, "Recursive system.multicall invocations not allowed")
|
120
|
+
end
|
121
|
+
multicall = request.method_params.dup
|
122
|
+
unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
|
123
|
+
raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
|
124
|
+
end
|
125
|
+
multicall = multicall[0]
|
126
|
+
return multicall.map do |item|
|
127
|
+
raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
|
128
|
+
raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
|
129
|
+
method_name = item['methodName']
|
130
|
+
params = item.has_key?('params') ? item['params'] : []
|
131
|
+
multicall_request = request.dup
|
132
|
+
multicall_request.method_name = method_name
|
133
|
+
multicall_request.method_params = params
|
134
|
+
begin
|
135
|
+
web_service_invocation(multicall_request, level + 1)
|
136
|
+
rescue Exception => e
|
137
|
+
{'faultCode' => 4, 'faultMessage' => e.message}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
case web_service_dispatching_mode
|
143
|
+
when :direct
|
144
|
+
invocation.api = self.class.web_service_api
|
145
|
+
invocation.service = self
|
146
|
+
when :delegated, :layered
|
147
|
+
invocation.service = web_service_object(invocation.service_name)
|
148
|
+
invocation.api = invocation.service.class.web_service_api
|
149
|
+
end
|
150
|
+
if invocation.api.nil?
|
151
|
+
raise(DispatcherError, "no API attached to #{invocation.service.class}")
|
152
|
+
end
|
153
|
+
invocation.protocol.register_api(invocation.api)
|
154
|
+
request.api = invocation.api
|
155
|
+
if invocation.api.has_public_api_method?(public_method_name)
|
156
|
+
invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
|
157
|
+
else
|
158
|
+
if invocation.api.default_api_method.nil?
|
159
|
+
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
|
160
|
+
else
|
161
|
+
invocation.api_method = invocation.api.default_api_method_instance
|
162
|
+
end
|
163
|
+
end
|
164
|
+
if invocation.service.nil?
|
165
|
+
raise(DispatcherError, "no service available for service name #{invocation.service_name}")
|
166
|
+
end
|
167
|
+
unless invocation.service.respond_to?(invocation.api_method.name)
|
168
|
+
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
|
169
|
+
end
|
170
|
+
request.api_method = invocation.api_method
|
171
|
+
begin
|
172
|
+
invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
|
173
|
+
rescue
|
174
|
+
logger.warn "Casting of method parameters failed" unless logger.nil?
|
175
|
+
invocation.method_ordered_params = request.method_params
|
176
|
+
end
|
177
|
+
request.method_params = invocation.method_ordered_params
|
178
|
+
invocation.method_named_params = {}
|
179
|
+
invocation.api_method.param_names.inject(0) do |m, n|
|
180
|
+
invocation.method_named_params[n] = invocation.method_ordered_params[m]
|
181
|
+
m + 1
|
182
|
+
end
|
183
|
+
invocation
|
184
|
+
end
|
185
|
+
|
186
|
+
def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
|
187
|
+
if api.has_api_method?(api_method.name)
|
188
|
+
return_type = api_method.returns ? api_method.returns[0] : nil
|
189
|
+
return_value = api_method.cast_returns(return_value)
|
190
|
+
else
|
191
|
+
return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
|
192
|
+
end
|
193
|
+
protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
|
194
|
+
end
|
195
|
+
|
196
|
+
class Invocation # :nodoc:
|
197
|
+
attr_accessor :protocol
|
198
|
+
attr_accessor :protocol_options
|
199
|
+
attr_accessor :service_name
|
200
|
+
attr_accessor :api
|
201
|
+
attr_accessor :api_method
|
202
|
+
attr_accessor :method_ordered_params
|
203
|
+
attr_accessor :method_named_params
|
204
|
+
attr_accessor :service
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|