axtro-actionwebservice 2.3.5.1.20101118142125
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 +184 -0
- data/TODO +32 -0
- data/examples/googlesearch/README +143 -0
- data/examples/googlesearch/autoloading/google_search_api.rb +51 -0
- data/examples/googlesearch/autoloading/google_search_controller.rb +58 -0
- data/examples/googlesearch/delegated/google_search_service.rb +109 -0
- data/examples/googlesearch/delegated/search_controller.rb +8 -0
- data/examples/googlesearch/direct/google_search_api.rb +51 -0
- data/examples/googlesearch/direct/search_controller.rb +59 -0
- data/examples/metaWeblog/README +17 -0
- data/examples/metaWeblog/apis/blogger_api.rb +61 -0
- data/examples/metaWeblog/apis/blogger_service.rb +35 -0
- data/examples/metaWeblog/apis/meta_weblog_api.rb +68 -0
- data/examples/metaWeblog/apis/meta_weblog_service.rb +49 -0
- data/examples/metaWeblog/controllers/xmlrpc_controller.rb +17 -0
- data/generators/web_service/USAGE +28 -0
- data/generators/web_service/templates/api_definition.rb +6 -0
- data/generators/web_service/templates/controller.rb +9 -0
- data/generators/web_service/templates/functional_test.rb +20 -0
- data/generators/web_service/web_service_generator.rb +30 -0
- data/lib/action_web_service.rb +61 -0
- data/lib/action_web_service/acts_as_web_service.rb +26 -0
- data/lib/action_web_service/api.rb +298 -0
- data/lib/action_web_service/base.rb +39 -0
- data/lib/action_web_service/casting.rb +160 -0
- data/lib/action_web_service/client.rb +4 -0
- data/lib/action_web_service/client/base.rb +29 -0
- data/lib/action_web_service/client/soap_client.rb +114 -0
- data/lib/action_web_service/client/xmlrpc_client.rb +59 -0
- data/lib/action_web_service/container.rb +4 -0
- data/lib/action_web_service/container/action_controller_container.rb +94 -0
- data/lib/action_web_service/container/delegated_container.rb +87 -0
- data/lib/action_web_service/container/direct_container.rb +70 -0
- data/lib/action_web_service/dispatcher.rb +3 -0
- data/lib/action_web_service/dispatcher/abstract.rb +209 -0
- data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +397 -0
- data/lib/action_web_service/invocation.rb +203 -0
- data/lib/action_web_service/protocol.rb +5 -0
- data/lib/action_web_service/protocol/abstract.rb +113 -0
- data/lib/action_web_service/protocol/discovery.rb +38 -0
- data/lib/action_web_service/protocol/soap_protocol.rb +177 -0
- data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +243 -0
- data/lib/action_web_service/protocol/soap_protocol/marshaler.rb~ +243 -0
- data/lib/action_web_service/protocol/xmlrpc_protocol.rb +124 -0
- data/lib/action_web_service/scaffolding.rb +282 -0
- data/lib/action_web_service/simple.rb +54 -0
- data/lib/action_web_service/string_to_datetime_for_soap.rb +17 -0
- data/lib/action_web_service/struct.rb +69 -0
- data/lib/action_web_service/support/class_inheritable_options.rb +27 -0
- data/lib/action_web_service/support/signature_types.rb +262 -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 +111 -0
- data/lib/action_web_service/version.rb +11 -0
- data/lib/action_web_service/version.rb~ +10 -0
- data/lib/actionwebservice.rb +2 -0
- data/setup.rb +1380 -0
- data/test/abstract_client.rb +185 -0
- data/test/abstract_dispatcher.rb +550 -0
- data/test/abstract_unit.rb +44 -0
- data/test/api_test.rb +103 -0
- data/test/apis/auto_load_api.rb +4 -0
- data/test/apis/broken_auto_load_api.rb +3 -0
- data/test/base_test.rb +43 -0
- data/test/casting_test.rb +96 -0
- data/test/client_soap_test.rb +157 -0
- data/test/client_xmlrpc_test.rb +155 -0
- data/test/container_test.rb +76 -0
- data/test/dispatcher_action_controller_soap_test.rb +147 -0
- data/test/dispatcher_action_controller_xmlrpc_test.rb +60 -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 +187 -0
- data/test/run +6 -0
- data/test/scaffolded_controller_test.rb +148 -0
- data/test/struct_test.rb +85 -0
- data/test/test_invoke_test.rb +114 -0
- metadata +167 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module ActionWebService # :nodoc:
|
3
|
+
module Container # :nodoc:
|
4
|
+
module Delegated # :nodoc:
|
5
|
+
class ContainerError < ActionWebServiceError # :nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base) # :nodoc:
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Declares a web service that will provide access to the API of the given
|
15
|
+
# +object+. +object+ must be an ActionWebService::Base derivative.
|
16
|
+
#
|
17
|
+
# Web service object creation can either be _immediate_, where the object
|
18
|
+
# instance is given at class definition time, or _deferred_, where
|
19
|
+
# object instantiation is delayed until request time.
|
20
|
+
#
|
21
|
+
# ==== Immediate web service object example
|
22
|
+
#
|
23
|
+
# class ApiController < ApplicationController
|
24
|
+
# web_service_dispatching_mode :delegated
|
25
|
+
#
|
26
|
+
# web_service :person, PersonService.new
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# For deferred instantiation, a block should be given instead of an
|
30
|
+
# object instance. This block will be executed in controller instance
|
31
|
+
# context, so it can rely on controller instance variables being present.
|
32
|
+
#
|
33
|
+
# ==== Deferred web service object example
|
34
|
+
#
|
35
|
+
# class ApiController < ApplicationController
|
36
|
+
# web_service_dispatching_mode :delegated
|
37
|
+
#
|
38
|
+
# web_service(:person) { PersonService.new(request.env) }
|
39
|
+
# end
|
40
|
+
def web_service(name, object=nil, &block)
|
41
|
+
if (object && block_given?) || (object.nil? && block.nil?)
|
42
|
+
raise(ContainerError, "either service, or a block must be given")
|
43
|
+
end
|
44
|
+
name = name.to_sym
|
45
|
+
if block_given?
|
46
|
+
info = { name => { :block => block } }
|
47
|
+
else
|
48
|
+
info = { name => { :object => object } }
|
49
|
+
end
|
50
|
+
write_inheritable_hash("web_services", info)
|
51
|
+
call_web_service_definition_callbacks(self, name, info)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Whether this service contains a service with the given +name+
|
55
|
+
def has_web_service?(name)
|
56
|
+
web_services.has_key?(name.to_sym)
|
57
|
+
end
|
58
|
+
|
59
|
+
def web_services # :nodoc:
|
60
|
+
read_inheritable_attribute("web_services") || {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_web_service_definition_callback(&block) # :nodoc:
|
64
|
+
write_inheritable_array("web_service_definition_callbacks", [block])
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
|
69
|
+
(read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
|
70
|
+
block.call(container_class, web_service_name, service_info)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module InstanceMethods # :nodoc:
|
76
|
+
def web_service_object(web_service_name)
|
77
|
+
info = self.class.web_services[web_service_name.to_sym]
|
78
|
+
unless info
|
79
|
+
raise(ContainerError, "no such web service '#{web_service_name}'")
|
80
|
+
end
|
81
|
+
service = info[:block]
|
82
|
+
service ? self.instance_eval(&service) : info[:object]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module ActionWebService # :nodoc:
|
3
|
+
module Container # :nodoc:
|
4
|
+
module Direct # :nodoc:
|
5
|
+
class ContainerError < ActionWebServiceError # :nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base) # :nodoc:
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Attaches ActionWebService API +definition+ to the calling class.
|
14
|
+
#
|
15
|
+
# Action Controllers can have a default associated API, removing the need
|
16
|
+
# to call this method if you follow the Action Web Service naming conventions.
|
17
|
+
#
|
18
|
+
# A controller with a class name of GoogleSearchController will
|
19
|
+
# implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
|
20
|
+
# API definition class to be named <tt>GoogleSearchAPI</tt> or
|
21
|
+
# <tt>GoogleSearchApi</tt>.
|
22
|
+
#
|
23
|
+
# ==== Service class example
|
24
|
+
#
|
25
|
+
# class MyService < ActionWebService::Base
|
26
|
+
# web_service_api MyAPI
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# class MyAPI < ActionWebService::API::Base
|
30
|
+
# ...
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# ==== Controller class example
|
34
|
+
#
|
35
|
+
# class MyController < ActionController::Base
|
36
|
+
# web_service_api MyAPI
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# class MyAPI < ActionWebService::API::Base
|
40
|
+
# ...
|
41
|
+
# end
|
42
|
+
def web_service_api(definition=nil)
|
43
|
+
if definition.nil?
|
44
|
+
read_inheritable_attribute("web_service_api")
|
45
|
+
else
|
46
|
+
if definition.is_a?(Symbol)
|
47
|
+
raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
|
48
|
+
end
|
49
|
+
unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
|
50
|
+
raise(ContainerError, "#{definition.to_s} is not a valid API definition")
|
51
|
+
end
|
52
|
+
write_inheritable_attribute("web_service_api", definition)
|
53
|
+
call_web_service_api_callbacks(self, definition)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_web_service_api_callback(&block) # :nodoc:
|
58
|
+
write_inheritable_array("web_service_api_callbacks", [block])
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def call_web_service_api_callbacks(container_class, definition)
|
63
|
+
(read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
|
64
|
+
block.call(container_class, definition)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
module ActionWebService # :nodoc:
|
5
|
+
module Dispatcher # :nodoc:
|
6
|
+
class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
set_backtrace(caller)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base) # :nodoc:
|
14
|
+
base.class_inheritable_option(:web_service_dispatching_mode, :direct)
|
15
|
+
base.class_inheritable_option(:web_service_inflect_type, false)
|
16
|
+
base.class_inheritable_option(:web_service_exception_reporting, true)
|
17
|
+
base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods # :nodoc:
|
21
|
+
private
|
22
|
+
def invoke_web_service_request(protocol_request)
|
23
|
+
invocation = web_service_invocation(protocol_request)
|
24
|
+
if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
|
25
|
+
xmlrpc_multicall_invoke(invocation)
|
26
|
+
else
|
27
|
+
web_service_invoke(invocation)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def web_service_direct_invoke(invocation)
|
32
|
+
@method_params = invocation.method_ordered_params
|
33
|
+
arity = method(invocation.api_method.name).arity rescue 0
|
34
|
+
if arity < 0 || arity > 0
|
35
|
+
params = @method_params
|
36
|
+
else
|
37
|
+
params = []
|
38
|
+
end
|
39
|
+
web_service_filtered_invoke(invocation, params)
|
40
|
+
end
|
41
|
+
|
42
|
+
def web_service_delegated_invoke(invocation)
|
43
|
+
web_service_filtered_invoke(invocation, invocation.method_ordered_params)
|
44
|
+
end
|
45
|
+
|
46
|
+
def web_service_filtered_invoke(invocation, params)
|
47
|
+
cancellation_reason = nil
|
48
|
+
return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
|
49
|
+
cancellation_reason = x
|
50
|
+
end
|
51
|
+
if cancellation_reason
|
52
|
+
raise(DispatcherError, "request canceled: #{cancellation_reason}")
|
53
|
+
end
|
54
|
+
return_value
|
55
|
+
end
|
56
|
+
|
57
|
+
def web_service_invoke(invocation)
|
58
|
+
case web_service_dispatching_mode
|
59
|
+
when :direct
|
60
|
+
return_value = web_service_direct_invoke(invocation)
|
61
|
+
when :delegated, :layered
|
62
|
+
return_value = web_service_delegated_invoke(invocation)
|
63
|
+
end
|
64
|
+
web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
|
65
|
+
end
|
66
|
+
|
67
|
+
def xmlrpc_multicall_invoke(invocations)
|
68
|
+
responses = []
|
69
|
+
invocations.each do |invocation|
|
70
|
+
if invocation.is_a?(Hash)
|
71
|
+
responses << [invocation, nil]
|
72
|
+
next
|
73
|
+
end
|
74
|
+
begin
|
75
|
+
case web_service_dispatching_mode
|
76
|
+
when :direct
|
77
|
+
return_value = web_service_direct_invoke(invocation)
|
78
|
+
when :delegated, :layered
|
79
|
+
return_value = web_service_delegated_invoke(invocation)
|
80
|
+
end
|
81
|
+
api_method = invocation.api_method
|
82
|
+
if invocation.api.has_api_method?(api_method.name)
|
83
|
+
response_type = (api_method.returns ? api_method.returns[0] : nil)
|
84
|
+
return_value = api_method.cast_returns(return_value)
|
85
|
+
else
|
86
|
+
response_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
|
87
|
+
end
|
88
|
+
responses << [return_value, response_type]
|
89
|
+
rescue Exception => e
|
90
|
+
responses << [{ 'faultCode' => 3, 'faultString' => e.message }, nil]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
invocation = invocations[0]
|
94
|
+
invocation.protocol.encode_multicall_response(responses, invocation.protocol_options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def web_service_invocation(request, level = 0)
|
98
|
+
public_method_name = request.method_name
|
99
|
+
invocation = Invocation.new
|
100
|
+
invocation.protocol = request.protocol
|
101
|
+
invocation.protocol_options = request.protocol_options
|
102
|
+
invocation.service_name = request.service_name
|
103
|
+
if web_service_dispatching_mode == :layered
|
104
|
+
case invocation.protocol
|
105
|
+
when Protocol::Soap::SoapProtocol
|
106
|
+
soap_action = request.protocol_options[:soap_action]
|
107
|
+
if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
|
108
|
+
invocation.service_name = $1
|
109
|
+
end
|
110
|
+
when Protocol::XmlRpc::XmlRpcProtocol
|
111
|
+
if request.method_name =~ /^([^\.]+)\.(.*)$/
|
112
|
+
public_method_name = $2
|
113
|
+
invocation.service_name = $1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
|
118
|
+
if public_method_name == 'multicall' && invocation.service_name == 'system'
|
119
|
+
if level > 0
|
120
|
+
raise(DispatcherError, "Recursive system.multicall invocations not allowed")
|
121
|
+
end
|
122
|
+
multicall = request.method_params.dup
|
123
|
+
unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
|
124
|
+
raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
|
125
|
+
end
|
126
|
+
multicall = multicall[0]
|
127
|
+
return multicall.map do |item|
|
128
|
+
raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
|
129
|
+
raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
|
130
|
+
method_name = item['methodName']
|
131
|
+
params = item.has_key?('params') ? item['params'] : []
|
132
|
+
multicall_request = request.dup
|
133
|
+
multicall_request.method_name = method_name
|
134
|
+
multicall_request.method_params = params
|
135
|
+
begin
|
136
|
+
web_service_invocation(multicall_request, level + 1)
|
137
|
+
rescue Exception => e
|
138
|
+
{'faultCode' => 4, 'faultMessage' => e.message}
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
case web_service_dispatching_mode
|
144
|
+
when :direct
|
145
|
+
invocation.api = self.class.web_service_api
|
146
|
+
invocation.service = self
|
147
|
+
when :delegated, :layered
|
148
|
+
invocation.service = web_service_object(invocation.service_name)
|
149
|
+
invocation.api = invocation.service.class.web_service_api
|
150
|
+
end
|
151
|
+
if invocation.api.nil?
|
152
|
+
raise(DispatcherError, "no API attached to #{invocation.service.class}")
|
153
|
+
end
|
154
|
+
invocation.protocol.register_api(invocation.api)
|
155
|
+
request.api = invocation.api
|
156
|
+
if invocation.api.has_public_api_method?(public_method_name)
|
157
|
+
invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
|
158
|
+
else
|
159
|
+
if invocation.api.default_api_method.nil?
|
160
|
+
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
|
161
|
+
else
|
162
|
+
invocation.api_method = invocation.api.default_api_method_instance
|
163
|
+
end
|
164
|
+
end
|
165
|
+
if invocation.service.nil?
|
166
|
+
raise(DispatcherError, "no service available for service name #{invocation.service_name}")
|
167
|
+
end
|
168
|
+
unless invocation.service.respond_to?(invocation.api_method.name)
|
169
|
+
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
|
170
|
+
end
|
171
|
+
request.api_method = invocation.api_method
|
172
|
+
begin
|
173
|
+
invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
|
174
|
+
rescue
|
175
|
+
logger.warn "Casting of method parameters failed" unless logger.nil?
|
176
|
+
invocation.method_ordered_params = request.method_params
|
177
|
+
end
|
178
|
+
request.method_params = invocation.method_ordered_params
|
179
|
+
invocation.method_named_params = {}
|
180
|
+
invocation.api_method.param_names.inject(0) do |m, n|
|
181
|
+
invocation.method_named_params[n] = invocation.method_ordered_params[m]
|
182
|
+
m + 1
|
183
|
+
end
|
184
|
+
invocation
|
185
|
+
end
|
186
|
+
|
187
|
+
def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
|
188
|
+
if api.has_api_method?(api_method.name)
|
189
|
+
return_type = api_method.returns ? api_method.returns[0] : nil
|
190
|
+
return_value = api_method.cast_returns(return_value)
|
191
|
+
else
|
192
|
+
return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
|
193
|
+
end
|
194
|
+
protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
|
195
|
+
end
|
196
|
+
|
197
|
+
class Invocation # :nodoc:
|
198
|
+
attr_accessor :protocol
|
199
|
+
attr_accessor :protocol_options
|
200
|
+
attr_accessor :service_name
|
201
|
+
attr_accessor :api
|
202
|
+
attr_accessor :api_method
|
203
|
+
attr_accessor :method_ordered_params
|
204
|
+
attr_accessor :method_named_params
|
205
|
+
attr_accessor :service
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,397 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'benchmark'
|
3
|
+
require 'builder/xmlmarkup'
|
4
|
+
|
5
|
+
module ActionWebService # :nodoc:
|
6
|
+
module Dispatcher # :nodoc:
|
7
|
+
module ActionController # :nodoc:
|
8
|
+
def self.included(base) # :nodoc:
|
9
|
+
class << base
|
10
|
+
include ClassMethods
|
11
|
+
alias_method_chain :inherited, :action_controller
|
12
|
+
end
|
13
|
+
base.class_eval do
|
14
|
+
alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
|
15
|
+
end
|
16
|
+
base.add_web_service_api_callback do |klass, api|
|
17
|
+
if klass.web_service_dispatching_mode == :direct
|
18
|
+
klass.class_eval 'def api; dispatch_web_service_request; end'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
base.add_web_service_definition_callback do |klass, name, info|
|
22
|
+
if klass.web_service_dispatching_mode == :delegated
|
23
|
+
klass.class_eval "def #{name}; dispatch_web_service_request; end"
|
24
|
+
elsif klass.web_service_dispatching_mode == :layered
|
25
|
+
klass.class_eval 'def api; dispatch_web_service_request; end'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods # :nodoc:
|
32
|
+
def inherited_with_action_controller(child)
|
33
|
+
inherited_without_action_controller(child)
|
34
|
+
# child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module InstanceMethods # :nodoc:
|
39
|
+
private
|
40
|
+
def dispatch_web_service_request
|
41
|
+
method = request.method.to_s.upcase
|
42
|
+
allowed_methods = self.class.web_service_api ? (self.class.web_service_api.allowed_http_methods || []) : [ :post ]
|
43
|
+
allowed_methods = allowed_methods.map{|m| m.to_s.upcase }
|
44
|
+
if !allowed_methods.include?(method)
|
45
|
+
render :text => "#{method} not supported", :status=>500
|
46
|
+
return
|
47
|
+
end
|
48
|
+
exception = nil
|
49
|
+
begin
|
50
|
+
ws_request = discover_web_service_request(request)
|
51
|
+
rescue Exception => e
|
52
|
+
exception = e
|
53
|
+
end
|
54
|
+
if ws_request
|
55
|
+
ws_response = nil
|
56
|
+
exception = nil
|
57
|
+
bm = Benchmark.measure do
|
58
|
+
begin
|
59
|
+
ws_response = invoke_web_service_request(ws_request)
|
60
|
+
rescue Exception => e
|
61
|
+
exception = e
|
62
|
+
end
|
63
|
+
end
|
64
|
+
log_request(ws_request, request.raw_post)
|
65
|
+
if exception
|
66
|
+
log_error(exception) unless logger.nil?
|
67
|
+
send_web_service_error_response(ws_request, exception)
|
68
|
+
else
|
69
|
+
send_web_service_response(ws_response, bm.real)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
|
73
|
+
log_error(exception) unless logger.nil?
|
74
|
+
send_web_service_error_response(ws_request, exception)
|
75
|
+
end
|
76
|
+
rescue Exception => e
|
77
|
+
log_error(e) unless logger.nil?
|
78
|
+
send_web_service_error_response(ws_request, e)
|
79
|
+
end
|
80
|
+
|
81
|
+
def send_web_service_response(ws_response, elapsed=nil)
|
82
|
+
log_response(ws_response, elapsed)
|
83
|
+
options = { :type => ws_response.content_type, :disposition => 'inline' }
|
84
|
+
send_data(ws_response.body, options)
|
85
|
+
end
|
86
|
+
|
87
|
+
def send_web_service_error_response(ws_request, exception)
|
88
|
+
if ws_request
|
89
|
+
unless self.class.web_service_exception_reporting
|
90
|
+
exception = DispatcherError.new("Internal server error (exception raised)")
|
91
|
+
end
|
92
|
+
api_method = ws_request.api_method
|
93
|
+
public_method_name = api_method ? api_method.public_name : ws_request.method_name
|
94
|
+
return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
|
95
|
+
ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
|
96
|
+
send_web_service_response(ws_response)
|
97
|
+
else
|
98
|
+
if self.class.web_service_exception_reporting
|
99
|
+
message = exception.message
|
100
|
+
backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
|
101
|
+
else
|
102
|
+
message = "Exception raised"
|
103
|
+
backtrace = ""
|
104
|
+
end
|
105
|
+
render :status => 500, :text => "Internal protocol error: #{message}#{backtrace}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def web_service_direct_invoke(invocation)
|
110
|
+
invocation.method_named_params.each do |name, value|
|
111
|
+
params[name] = value
|
112
|
+
end
|
113
|
+
web_service_direct_invoke_without_controller(invocation)
|
114
|
+
end
|
115
|
+
|
116
|
+
def log_request(ws_request, body)
|
117
|
+
unless logger.nil?
|
118
|
+
name = ws_request.method_name
|
119
|
+
api_method = ws_request.api_method
|
120
|
+
params = ws_request.method_params
|
121
|
+
if api_method && api_method.expects
|
122
|
+
params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
|
123
|
+
else
|
124
|
+
params = params.map{ |param| param.inspect }
|
125
|
+
end
|
126
|
+
service = ws_request.service_name
|
127
|
+
logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
|
128
|
+
logger.debug(indent(body))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def log_response(ws_response, elapsed=nil)
|
133
|
+
unless logger.nil?
|
134
|
+
elapsed = (elapsed ? " (%f):" % elapsed : ":")
|
135
|
+
logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
|
136
|
+
logger.debug(indent(ws_response.body))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def indent(body)
|
141
|
+
body.split(/\n/).map{|x| " #{x}"}.join("\n")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
module WsdlAction # :nodoc:
|
146
|
+
XsdNs = 'http://www.w3.org/2001/XMLSchema'
|
147
|
+
WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
|
148
|
+
SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
|
149
|
+
SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
|
150
|
+
SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
|
151
|
+
|
152
|
+
def wsdl
|
153
|
+
case request.method
|
154
|
+
when :get
|
155
|
+
begin
|
156
|
+
options = { :type => 'text/xml', :disposition => 'inline' }
|
157
|
+
send_data(to_wsdl, options)
|
158
|
+
rescue Exception => e
|
159
|
+
log_error(e) unless logger.nil?
|
160
|
+
end
|
161
|
+
when :post
|
162
|
+
render :status => 500, :text => 'POST not supported'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
def base_uri
|
168
|
+
host = request.host_with_port
|
169
|
+
relative_url_root = ::ActionController::Base.relative_url_root
|
170
|
+
scheme = request.ssl? ? 'https' : 'http'
|
171
|
+
'%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_wsdl
|
175
|
+
xml = ''
|
176
|
+
inflect = web_service_inflect_type
|
177
|
+
dispatching_mode = web_service_dispatching_mode
|
178
|
+
global_service_name = wsdl_service_name
|
179
|
+
namespace = wsdl_namespace || 'urn:ActionWebService'
|
180
|
+
soap_action_base = "/#{controller_name}"
|
181
|
+
|
182
|
+
marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
|
183
|
+
apis = {}
|
184
|
+
case dispatching_mode
|
185
|
+
when :direct
|
186
|
+
api = self.class.web_service_api
|
187
|
+
web_service_name = controller_class_name.sub(/Controller$/, '').underscore
|
188
|
+
apis[web_service_name] = [api, register_api(api, marshaler)]
|
189
|
+
when :delegated, :layered
|
190
|
+
self.class.web_services.each do |web_service_name, info|
|
191
|
+
service = web_service_object(web_service_name)
|
192
|
+
api = service.class.web_service_api
|
193
|
+
apis[web_service_name] = [api, register_api(api, marshaler)]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
custom_types = []
|
197
|
+
apis.values.each do |api, bindings|
|
198
|
+
bindings.each do |b|
|
199
|
+
custom_types << b unless custom_types.include?(b)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
|
204
|
+
xm.instruct!
|
205
|
+
xm.definitions('name' => wsdl_service_name,
|
206
|
+
'targetNamespace' => namespace,
|
207
|
+
'xmlns:typens' => namespace,
|
208
|
+
'xmlns:xsd' => XsdNs,
|
209
|
+
'xmlns:soap' => SoapNs,
|
210
|
+
'xmlns:soapenc' => SoapEncodingNs,
|
211
|
+
'xmlns:wsdl' => WsdlNs,
|
212
|
+
'xmlns' => WsdlNs) do
|
213
|
+
# Generate XSD
|
214
|
+
if custom_types.size > 0
|
215
|
+
xm.types do
|
216
|
+
xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
|
217
|
+
simple_types, array_types, complex_types = [], [], []
|
218
|
+
custom_types.each do |binding|
|
219
|
+
case
|
220
|
+
when binding.type.simple? then simple_types.push binding
|
221
|
+
when binding.type.array? then array_types.push binding
|
222
|
+
when binding.type.structured? then complex_types.push binding
|
223
|
+
end
|
224
|
+
end
|
225
|
+
simple_types.each do |binding|
|
226
|
+
xm.xsd(:simpleType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
|
227
|
+
xm.xsd(:restriction, 'base' => "xsd:#{binding.type.base}") do
|
228
|
+
binding.type.restrictions do |name, value|
|
229
|
+
xm.xsd(name.to_sym, 'value' => value)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
array_types.each do |binding|
|
235
|
+
xm.xsd(:complexType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
|
236
|
+
xm.xsd(:complexContent) do
|
237
|
+
xm.xsd(:restriction, 'base' => 'soapenc:Array') do
|
238
|
+
xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
|
239
|
+
'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
complex_types.each do |binding|
|
245
|
+
xm.xsd(:complexType, 'name' => inflect ? binding.type_name.camelize(:lower) : binding.type_name) do
|
246
|
+
xm.xsd(:all) do
|
247
|
+
binding.type.each_member do |name, type, options|
|
248
|
+
options ||= {}
|
249
|
+
b = marshaler.register_type(type)
|
250
|
+
xm.xsd(:element, {'name' => inflect ? name.to_s.camelize : name, 'type' => b.qualified_type_name('typens')}.merge(options))
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# APIs
|
260
|
+
apis.each do |api_name, values|
|
261
|
+
api = values[0]
|
262
|
+
api.api_methods.each do |name, method|
|
263
|
+
gen = lambda do |msg_name, direction|
|
264
|
+
xm.message('name' => message_name_for(api_name, msg_name)) do
|
265
|
+
sym = nil
|
266
|
+
if direction == :out
|
267
|
+
returns = method.returns
|
268
|
+
if returns
|
269
|
+
binding = marshaler.register_type(returns[0])
|
270
|
+
xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
|
271
|
+
end
|
272
|
+
else
|
273
|
+
expects = method.expects
|
274
|
+
expects.each do |type|
|
275
|
+
binding = marshaler.register_type(type)
|
276
|
+
xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
|
277
|
+
end if expects
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
public_name = method.public_name
|
282
|
+
gen.call(public_name, :in)
|
283
|
+
gen.call("#{public_name}Response", :out)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Port
|
287
|
+
port_name = port_name_for(global_service_name, api_name)
|
288
|
+
xm.portType('name' => port_name) do
|
289
|
+
api.api_methods.each do |name, method|
|
290
|
+
xm.operation('name' => method.public_name) do
|
291
|
+
xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
|
292
|
+
xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Bind it
|
298
|
+
binding_name = binding_name_for(global_service_name, api_name)
|
299
|
+
xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
|
300
|
+
xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
|
301
|
+
api.api_methods.each do |name, method|
|
302
|
+
xm.operation('name' => method.public_name) do
|
303
|
+
case web_service_dispatching_mode
|
304
|
+
when :direct
|
305
|
+
soap_action = soap_action_base + "/api/" + method.public_name
|
306
|
+
when :delegated, :layered
|
307
|
+
soap_action = soap_action_base \
|
308
|
+
+ "/" + api_name.to_s \
|
309
|
+
+ "/" + method.public_name
|
310
|
+
end
|
311
|
+
xm.soap(:operation, 'soapAction' => soap_action)
|
312
|
+
xm.input do
|
313
|
+
xm.soap(:body,
|
314
|
+
'use' => 'encoded',
|
315
|
+
'namespace' => namespace,
|
316
|
+
'encodingStyle' => SoapEncodingNs)
|
317
|
+
end
|
318
|
+
xm.output do
|
319
|
+
xm.soap(:body,
|
320
|
+
'use' => 'encoded',
|
321
|
+
'namespace' => namespace,
|
322
|
+
'encodingStyle' => SoapEncodingNs)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
# Define it
|
330
|
+
xm.service('name' => "#{global_service_name}Service") do
|
331
|
+
apis.each do |api_name, values|
|
332
|
+
port_name = port_name_for(global_service_name, api_name)
|
333
|
+
binding_name = binding_name_for(global_service_name, api_name)
|
334
|
+
case web_service_dispatching_mode
|
335
|
+
when :direct, :layered
|
336
|
+
binding_target = 'api'
|
337
|
+
when :delegated
|
338
|
+
binding_target = api_name.to_s
|
339
|
+
end
|
340
|
+
xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
|
341
|
+
xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def port_name_for(global_service, service)
|
349
|
+
"#{global_service}#{service.to_s.camelize}Port"
|
350
|
+
end
|
351
|
+
|
352
|
+
def binding_name_for(global_service, service)
|
353
|
+
"#{global_service}#{service.to_s.camelize}Binding"
|
354
|
+
end
|
355
|
+
|
356
|
+
def message_name_for(api_name, message_name)
|
357
|
+
mode = web_service_dispatching_mode
|
358
|
+
if mode == :layered || mode == :delegated
|
359
|
+
api_name.to_s + '-' + message_name
|
360
|
+
else
|
361
|
+
message_name
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def register_api(api, marshaler)
|
366
|
+
bindings = {}
|
367
|
+
traverse_custom_types(api, marshaler, bindings) do |binding|
|
368
|
+
bindings[binding] = nil unless bindings.has_key?(binding)
|
369
|
+
element_binding = binding.element_binding
|
370
|
+
bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
|
371
|
+
end
|
372
|
+
bindings.keys
|
373
|
+
end
|
374
|
+
|
375
|
+
def traverse_custom_types(api, marshaler, bindings, &block)
|
376
|
+
api.api_methods.each do |name, method|
|
377
|
+
expects, returns = method.expects, method.returns
|
378
|
+
expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
|
379
|
+
returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def traverse_type(marshaler, type, bindings, &block)
|
384
|
+
binding = marshaler.register_type(type)
|
385
|
+
return if bindings.has_key?(binding)
|
386
|
+
bindings[binding] = nil
|
387
|
+
yield binding
|
388
|
+
if type.array?
|
389
|
+
yield marshaler.register_type(type.element_type)
|
390
|
+
type = type.element_type
|
391
|
+
end
|
392
|
+
type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|