actionservice 0.2.99 → 0.2.100
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -2
- data/Rakefile +1 -1
- data/TODO +6 -1
- data/lib/action_service/base.rb +1 -8
- data/lib/action_service/container.rb +56 -15
- data/lib/action_service/invocation.rb +18 -7
- data/lib/action_service/protocol/abstract.rb +37 -4
- data/lib/action_service/protocol/soap.rb +57 -60
- data/lib/action_service/protocol/xmlrpc.rb +97 -0
- data/lib/action_service/protocol.rb +1 -0
- data/lib/action_service/router/action_controller.rb +2 -2
- data/lib/action_service/router/wsdl.rb +7 -5
- data/lib/action_service.rb +1 -0
- data/test/base_test.rb +4 -0
- data/test/container_test.rb +20 -3
- data/test/protocol_soap_test.rb +89 -2
- data/test/protocol_xmlrpc_test.rb +130 -0
- data/test/router_wsdl_test.rb +5 -1
- metadata +20 -18
data/CHANGELOG
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
1
|
+
TBA
|
2
2
|
|
3
|
-
* First release
|
3
|
+
* First public release
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ require 'rake/contrib/rubyforgepublisher'
|
|
8
8
|
|
9
9
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
10
10
|
PKG_NAME = 'actionservice'
|
11
|
-
PKG_VERSION = '0.2.
|
11
|
+
PKG_VERSION = '0.2.100' + PKG_BUILD
|
12
12
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
13
13
|
|
14
14
|
desc "Default Task"
|
data/TODO
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
= Features Remaining
|
2
|
-
- XML-RPC support
|
3
2
|
- Support for ActiveRecord model classes as structure types as well, since
|
4
3
|
we can reflect all the details to do proper mapping from model classes.
|
5
4
|
|
@@ -21,5 +20,11 @@
|
|
21
20
|
* Use the return value of the action to send back to the client
|
22
21
|
|
23
22
|
= Warts
|
23
|
+
- Come up with a cleaner way for supporting XML-RPC introspection that doesn't
|
24
|
+
involve throwing a SystemExportNotHandledError exception.
|
25
|
+
- Come up with a cleaner approach for 'default_export' implementations.
|
26
|
+
But NOT method_missing!
|
27
|
+
- Create abstract SOAP test that provides helpers for doing SOAP method
|
28
|
+
calls, get rid of the copy & paste SOAP crap in the SOAP protocol tests
|
24
29
|
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
|
25
30
|
string
|
data/lib/action_service/base.rb
CHANGED
@@ -8,6 +8,7 @@ module ActionService
|
|
8
8
|
class_inheritable_option :export_name_mangling, true
|
9
9
|
class_inheritable_option :report_exceptions, true
|
10
10
|
class_inheritable_option :logger
|
11
|
+
class_inheritable_option :default_export
|
11
12
|
|
12
13
|
class << self
|
13
14
|
def export(name, options={})
|
@@ -19,14 +20,6 @@ module ActionService
|
|
19
20
|
expects = options[:expects]
|
20
21
|
returns = options[:returns]
|
21
22
|
end
|
22
|
-
if expects.nil?
|
23
|
-
arity = instance_method(name).arity
|
24
|
-
expects = [Object] * arity if arity > 0
|
25
|
-
end
|
26
|
-
if returns.nil?
|
27
|
-
returns = [NilClass]
|
28
|
-
end
|
29
|
-
|
30
23
|
name = name.to_sym
|
31
24
|
public_name = public_export_name(name)
|
32
25
|
info = { :expects => expects, :returns => returns }
|
@@ -36,15 +36,6 @@ module ActionService
|
|
36
36
|
read_inheritable_attribute("action_services") || {}
|
37
37
|
end
|
38
38
|
|
39
|
-
def service_object(service_name)
|
40
|
-
info = services[service_name.to_sym]
|
41
|
-
unless info
|
42
|
-
raise(ContainerError, "no such service '#{service_name}'")
|
43
|
-
end
|
44
|
-
service = info[:block]
|
45
|
-
service ? instance_eval(&service) : info[:object]
|
46
|
-
end
|
47
|
-
|
48
39
|
private
|
49
40
|
def call_service_definition_callbacks(container_klass, service_name, service_info)
|
50
41
|
(read_inheritable_attribute("action_service_definition_callbacks") || []).each do |block|
|
@@ -54,14 +45,64 @@ module ActionService
|
|
54
45
|
end
|
55
46
|
|
56
47
|
module InstanceMethods
|
48
|
+
def service_object(service_name)
|
49
|
+
info = self.class.services[service_name.to_sym]
|
50
|
+
unless info
|
51
|
+
raise(ContainerError, "no such service '#{service_name}'")
|
52
|
+
end
|
53
|
+
service = info[:block]
|
54
|
+
service ? instance_eval(&service) : info[:object]
|
55
|
+
end
|
56
|
+
|
57
57
|
private
|
58
58
|
def dispatch_service_invocation_request(protocol, info)
|
59
|
-
service =
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
59
|
+
service = service_object(info.service_name)
|
60
|
+
service_klass = service.class
|
61
|
+
method_name = service_klass.internal_export_name(info.public_method_name)
|
62
|
+
opts = {}
|
63
|
+
params = []
|
64
|
+
if method_name
|
65
|
+
export_info = service_klass.exports[method_name]
|
66
|
+
strict = true
|
67
|
+
else
|
68
|
+
system_exports = self.class.read_inheritable_attribute('default_system_exports') || {}
|
69
|
+
method_name = system_exports[protocol.class]
|
70
|
+
if method_name
|
71
|
+
opts[:system_call] = [info.public_method_name, system_exports[protocol.class]]
|
72
|
+
params.unshift service_klass
|
73
|
+
else
|
74
|
+
method_name = service_klass.default_export
|
75
|
+
method_name = method_name.to_sym if method_name
|
76
|
+
unless method_name
|
77
|
+
raise(ContainerError, "no such method /#{info.service_name}##{info.public_method_name}()")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
export_info = {}
|
81
|
+
strict = false
|
82
|
+
end
|
83
|
+
opts[:require_exported] = false unless strict
|
84
|
+
params = params + protocol.unmarshal_request(info, export_info, strict)
|
85
|
+
canceled_reason = nil
|
86
|
+
canceled_block = lambda{|r| canceled_reason = r}
|
87
|
+
perform_invoke = lambda do
|
88
|
+
service.perform_invocation(method_name, params, opts, &canceled_block)
|
89
|
+
end
|
90
|
+
begin
|
91
|
+
result = perform_invoke.call
|
92
|
+
rescue ActionService::Protocol::SystemExportNotHandledError => e
|
93
|
+
params.shift
|
94
|
+
method_name = service_klass.default_export
|
95
|
+
opts.delete(:system_call)
|
96
|
+
if method_name
|
97
|
+
method_name = method_name.to_sym
|
98
|
+
export_info = {}
|
99
|
+
strict = false
|
100
|
+
else
|
101
|
+
raise(ContainerError, "no such method /#{info.service_name}##{info.public_method_name}()")
|
102
|
+
end
|
103
|
+
result = perform_invoke.call
|
104
|
+
end
|
105
|
+
protocol.marshal_response(info, export_info, result, strict)
|
65
106
|
end
|
66
107
|
|
67
108
|
end
|
@@ -86,7 +86,7 @@ module ActionService
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
module InstanceMethods
|
89
|
+
module InstanceMethods
|
90
90
|
def self.append_features(base)
|
91
91
|
super
|
92
92
|
base.class_eval do
|
@@ -95,19 +95,30 @@ module ActionService
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
-
def perform_invocation_with_interception(name, args=nil, &block)
|
98
|
+
def perform_invocation_with_interception(name, args=nil, options={}, &block)
|
99
99
|
return if before_invocation(name, args, &block) == false
|
100
|
-
result = perform_invocation_without_interception(name, args)
|
100
|
+
result = perform_invocation_without_interception(name, args, options)
|
101
101
|
after_invocation(name, args, result)
|
102
102
|
result
|
103
103
|
end
|
104
104
|
|
105
|
-
def perform_invocation(name, args=nil)
|
106
|
-
unless
|
107
|
-
|
105
|
+
def perform_invocation(name, args=nil, options={})
|
106
|
+
unless options[:require_exported] == false
|
107
|
+
unless self.respond_to?(name) && self.class.has_export?(name)
|
108
|
+
raise InvocationError, "no such exported method '#{name}'"
|
109
|
+
end
|
108
110
|
end
|
109
111
|
args ||= []
|
110
|
-
|
112
|
+
if options[:system_call]
|
113
|
+
name, block = options[:system_call]
|
114
|
+
if block.respond_to?('call')
|
115
|
+
block.call(name, *args)
|
116
|
+
else
|
117
|
+
self.send(block, *args)
|
118
|
+
end
|
119
|
+
else
|
120
|
+
self.send(name, *args)
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
def before_invocation(name, args, &block)
|
@@ -2,6 +2,8 @@ module ActionService
|
|
2
2
|
module Protocol
|
3
3
|
class ProtocolError < ActionService::ActionServiceError
|
4
4
|
end
|
5
|
+
class SystemExportNotHandledError < ProtocolError
|
6
|
+
end
|
5
7
|
|
6
8
|
class AbstractProtocol
|
7
9
|
attr :container_klass
|
@@ -10,11 +12,11 @@ module ActionService
|
|
10
12
|
@container_klass = container_klass
|
11
13
|
end
|
12
14
|
|
13
|
-
def unmarshal_request(request_info, export_info)
|
15
|
+
def unmarshal_request(request_info, export_info, strict=true)
|
14
16
|
raise NotImplementedError
|
15
17
|
end
|
16
18
|
|
17
|
-
def marshal_response(request_info, export_info, return_value)
|
19
|
+
def marshal_response(request_info, export_info, return_value, strict=true)
|
18
20
|
raise NotImplementedError
|
19
21
|
end
|
20
22
|
|
@@ -22,13 +24,44 @@ module ActionService
|
|
22
24
|
raise NotImplementedError
|
23
25
|
end
|
24
26
|
|
25
|
-
def request_info
|
27
|
+
def request_info
|
26
28
|
raise NotImplementedError
|
27
29
|
end
|
28
30
|
|
29
31
|
def request_supported?(request)
|
30
32
|
raise NotImplementedError
|
31
33
|
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def validate_types(method_name, signature, params, kind)
|
37
|
+
unless signature.length == params.length
|
38
|
+
raise(ProtocolError, "signature/parameter mismatch")
|
39
|
+
end
|
40
|
+
case kind
|
41
|
+
when :in
|
42
|
+
extra_msg = ' (Input parameter %d of method "%s")'
|
43
|
+
when :out
|
44
|
+
extra_msg = ' (Return value %d of method "%s")'
|
45
|
+
end
|
46
|
+
i = 0
|
47
|
+
signature.each do |klass|
|
48
|
+
check_compatibility(params[i].class, klass, extra_msg % [i+1, method_name])
|
49
|
+
i += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_compatibility(given_klass, signature_klass, extra_msg=nil)
|
54
|
+
if (given_klass == TrueClass or given_klass == FalseClass) and \
|
55
|
+
(signature_klass == TrueClass or signature_klass == FalseClass)
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
unless given_klass.ancestors.include?(signature_klass) || \
|
59
|
+
signature_klass.ancestors.include?(given_klass)
|
60
|
+
raise(ProtocolError, "value of type #{given_klass.name} is not compatible " +
|
61
|
+
"with expected type #{signature_klass.name}" +
|
62
|
+
(extra_msg ? extra_msg : ''))
|
63
|
+
end
|
64
|
+
end
|
32
65
|
end
|
33
66
|
|
34
67
|
class ServiceRequestInfo
|
@@ -38,7 +71,7 @@ module ActionService
|
|
38
71
|
attr :content_type
|
39
72
|
attr :protocol_info
|
40
73
|
|
41
|
-
def initialize(service_name, public_method_name, raw_body, content_type, protocol_info)
|
74
|
+
def initialize(service_name, public_method_name, raw_body, content_type, protocol_info=nil)
|
42
75
|
@service_name = service_name
|
43
76
|
@public_method_name = public_method_name
|
44
77
|
@raw_body = raw_body
|
@@ -28,30 +28,52 @@ module ActionService
|
|
28
28
|
end
|
29
29
|
|
30
30
|
class SoapProtocol < AbstractProtocol
|
31
|
-
def unmarshal_request(request_info, export_info)
|
31
|
+
def unmarshal_request(request_info, export_info, strict=true)
|
32
32
|
expects = export_info[:expects]
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
unmarshal_soap_message = lambda do
|
34
|
+
envelope = SOAP::Processor.unmarshal(request_info.raw_body)
|
35
|
+
request = envelope.body.request
|
36
|
+
params = request.collect{|k, v| request[k]}
|
37
|
+
soap_to_ruby_array(params)
|
38
|
+
end if expects || !strict
|
39
|
+
if expects
|
40
|
+
map_types(expects)
|
41
|
+
params = unmarshal_soap_message.call
|
42
|
+
validate_types(request_info.public_method_name, expects, params, :in) if expects
|
43
|
+
params
|
44
|
+
else
|
45
|
+
if strict
|
46
|
+
[]
|
47
|
+
else
|
48
|
+
unmarshal_soap_message.call
|
49
|
+
end
|
50
|
+
end
|
40
51
|
end
|
41
52
|
|
42
|
-
def marshal_response(request_info, export_info, return_value)
|
53
|
+
def marshal_response(request_info, export_info, return_value, strict=true)
|
43
54
|
returns = export_info[:returns]
|
55
|
+
create_param_def = lambda do |ret|
|
56
|
+
map_types(ret)
|
57
|
+
mapping = mapper.lookup(ret[0])
|
58
|
+
ret = [mapping.ruby_klass]
|
59
|
+
retval = fixup_array_types(mapping, return_value)
|
60
|
+
validate_types(request_info.public_method_name, ret, [retval], :out)
|
61
|
+
retval = ruby_to_soap(retval)
|
62
|
+
param_def = [['retval', 'return', mapping.registry_mapping]]
|
63
|
+
[param_def, ret, retval]
|
64
|
+
end if returns || !strict
|
44
65
|
if returns
|
45
|
-
|
46
|
-
|
47
|
-
|
66
|
+
param_def, returns, return_value = create_param_def.call(returns)
|
67
|
+
else
|
68
|
+
if strict
|
69
|
+
param_def = []
|
70
|
+
else
|
71
|
+
param_def, returns, return_value = create_param_def.call([return_value.class])
|
72
|
+
end
|
48
73
|
end
|
49
|
-
validate_types(request_info.public_method_name, returns, [return_value], :out)
|
50
|
-
return_value = ruby_to_soap(return_value)
|
51
|
-
param_def = returns ? [['retval', 'return', mapping.registry_mapping]] : []
|
52
74
|
qname = XSD::QName.new(mapper.custom_namespace, request_info.public_method_name)
|
53
75
|
response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
|
54
|
-
response.retval = return_value
|
76
|
+
response.retval = return_value if returns
|
55
77
|
ServiceResponseInfo.new(create_response(response), 'text/xml')
|
56
78
|
end
|
57
79
|
|
@@ -59,19 +81,20 @@ module ActionService
|
|
59
81
|
ServiceResponseInfo.new(create_exception_response(exception), 'text/xml')
|
60
82
|
end
|
61
83
|
|
62
|
-
def request_info
|
84
|
+
def request_info
|
85
|
+
@request_info
|
86
|
+
end
|
87
|
+
|
88
|
+
def request_supported?(request)
|
63
89
|
soap_action = extract_soap_action(request)
|
90
|
+
return false unless soap_action
|
64
91
|
service_name = request.parameters['action']
|
65
92
|
public_method_name = soap_action.gsub(/^[\/]+/, '').split(/[\/]+/)[-1]
|
66
|
-
ServiceRequestInfo.new(service_name,
|
93
|
+
@request_info = ServiceRequestInfo.new(service_name,
|
67
94
|
public_method_name,
|
68
95
|
request.raw_post,
|
69
|
-
request.env['HTTP_CONTENT_TYPE'] || 'text/xml'
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
def request_supported?(request)
|
74
|
-
extract_soap_action(request) ? true : false
|
96
|
+
request.env['HTTP_CONTENT_TYPE'] || 'text/xml')
|
97
|
+
true
|
75
98
|
end
|
76
99
|
|
77
100
|
private
|
@@ -89,9 +112,10 @@ module ActionService
|
|
89
112
|
end
|
90
113
|
if mapping.is_a?(SoapArrayMapping)
|
91
114
|
obj = mapping.ruby_klass.new(obj)
|
92
|
-
|
93
|
-
obj
|
115
|
+
# man, this is going to be slow for big arrays :(
|
116
|
+
obj.each{|el| fixup_array_types(mapping.element_mapping, el)}
|
94
117
|
end
|
118
|
+
obj
|
95
119
|
end
|
96
120
|
|
97
121
|
def extract_soap_action(request)
|
@@ -111,36 +135,6 @@ module ActionService
|
|
111
135
|
types.collect{|type| mapper.map(type)}
|
112
136
|
end
|
113
137
|
|
114
|
-
def validate_types(method_name, signature, params, kind)
|
115
|
-
unless signature.length == params.length
|
116
|
-
raise(ProtocolError, "signature/parameter mismatch")
|
117
|
-
end
|
118
|
-
case kind
|
119
|
-
when :in
|
120
|
-
extra_msg = ' (Input parameter %d of method "%s")'
|
121
|
-
when :out
|
122
|
-
extra_msg = ' (Return value %d of method "%s")'
|
123
|
-
end
|
124
|
-
i = 0
|
125
|
-
signature.each do |klass|
|
126
|
-
check_compatibility(params[i].class, klass, extra_msg % [i, method_name])
|
127
|
-
i += 1
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def check_compatibility(klass1, klass2, extra_msg=nil)
|
132
|
-
if (klass1 == TrueClass or klass1 == FalseClass) and \
|
133
|
-
(klass2 == TrueClass or klass2 == FalseClass)
|
134
|
-
return true
|
135
|
-
end
|
136
|
-
unless klass1.ancestors.include?(klass2) || \
|
137
|
-
klass2.ancestors.include?(klass1)
|
138
|
-
raise(ProtocolError, "value of type #{klass1.name} is not compatible " +
|
139
|
-
"with expected type #{klass2.name}" +
|
140
|
-
(extra_msg ? extra_msg : ''))
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
138
|
def create_response(body, is_error=false)
|
145
139
|
header = SOAP::SOAPHeader.new
|
146
140
|
body = SOAP::SOAPBody.new(body)
|
@@ -261,9 +255,9 @@ module ActionService
|
|
261
255
|
end
|
262
256
|
alias :map :lookup
|
263
257
|
|
264
|
-
def map_container_services(
|
265
|
-
|
266
|
-
object =
|
258
|
+
def map_container_services(container, &block)
|
259
|
+
container.class.services.each do |service_name, service_info|
|
260
|
+
object = container.service_object(service_name)
|
267
261
|
service_klass = object.class
|
268
262
|
service_exports = {}
|
269
263
|
object.class.exports.each do |export_name, export_info|
|
@@ -286,7 +280,8 @@ module ActionService
|
|
286
280
|
mapping
|
287
281
|
end
|
288
282
|
expects_signature = expects ? expects.map{|klass| lookup_proc.call(klass)} : nil
|
289
|
-
|
283
|
+
returns = export_info[:returns]
|
284
|
+
returns_signature = returns ? returns.map{|klass| lookup_proc.call(klass)} : nil
|
290
285
|
service_exports[export_name] = {
|
291
286
|
:expects => expects_signature,
|
292
287
|
:returns => returns_signature
|
@@ -380,6 +375,8 @@ module ActionService
|
|
380
375
|
def type_name
|
381
376
|
super + "Array"
|
382
377
|
end
|
378
|
+
|
379
|
+
def each_attribute(&block); end
|
383
380
|
end
|
384
381
|
end
|
385
382
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'xmlrpc/parser'
|
2
|
+
require 'xmlrpc/utils'
|
3
|
+
|
4
|
+
module ActionService
|
5
|
+
module Protocol
|
6
|
+
module XmlRpc
|
7
|
+
def self.append_features(base)
|
8
|
+
super
|
9
|
+
base.register_protocol(BodyOnly, XmlRpcProtocol)
|
10
|
+
end
|
11
|
+
|
12
|
+
class XmlRpcProtocol < AbstractProtocol
|
13
|
+
include XMLRPC::ParserWriterChooseMixin
|
14
|
+
|
15
|
+
def initialize(container_klass)
|
16
|
+
@container_klass = container_klass
|
17
|
+
@container_klass.write_inheritable_hash('default_system_exports', XmlRpcProtocol => method(:xmlrpc_default_system_handler))
|
18
|
+
end
|
19
|
+
|
20
|
+
def unmarshal_request(request_info, export_info, strict=true)
|
21
|
+
expects = export_info[:expects]
|
22
|
+
params = request_info.protocol_info[:xmlrpc][:params]
|
23
|
+
if expects
|
24
|
+
expects = array_types(expects)
|
25
|
+
validate_types(request_info.public_method_name, expects, params, :in)
|
26
|
+
params
|
27
|
+
else
|
28
|
+
strict ? [] : params
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def marshal_response(request_info, export_info, return_value, strict=true)
|
33
|
+
returns = export_info[:returns]
|
34
|
+
if returns
|
35
|
+
returns = array_types(returns)
|
36
|
+
validate_types(request_info.public_method_name, returns, [return_value], :out)
|
37
|
+
raw_response = create().methodResponse(true, return_value)
|
38
|
+
else
|
39
|
+
# XML-RPC doesn't have the concept of a void method, nor does it
|
40
|
+
# support a nil return value, so return true if we would have returned
|
41
|
+
# nil
|
42
|
+
if strict
|
43
|
+
raw_response = create().methodResponse(true, true)
|
44
|
+
else
|
45
|
+
return_value = true if return_value.nil?
|
46
|
+
raw_response = create().methodResponse(true, return_value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
ServiceResponseInfo.new(raw_response, 'text/xml')
|
50
|
+
end
|
51
|
+
|
52
|
+
def marshal_exception(exception)
|
53
|
+
raw_response = create().methodResponse(false, exception)
|
54
|
+
ServiceResponseInfo.new(raw_response, 'text/xml')
|
55
|
+
end
|
56
|
+
|
57
|
+
def request_info
|
58
|
+
@request_info
|
59
|
+
end
|
60
|
+
|
61
|
+
def request_supported?(request)
|
62
|
+
begin
|
63
|
+
service_name = request.parameters['action']
|
64
|
+
methodname, params = parser().parseMethodCall(request.raw_post)
|
65
|
+
@request_info = ServiceRequestInfo.new(service_name,
|
66
|
+
methodname,
|
67
|
+
request.raw_post,
|
68
|
+
request.env['HTTP_CONTENT_TYPE'] || 'text/xml',
|
69
|
+
:xmlrpc => { :params => params })
|
70
|
+
true
|
71
|
+
rescue
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def xmlrpc_default_system_handler(name, service_klass, *args)
|
78
|
+
case name
|
79
|
+
when 'system.listMethods'
|
80
|
+
methods = []
|
81
|
+
service_klass.exports.each do |name, info|
|
82
|
+
methods << service_klass.public_export_name(name)
|
83
|
+
end
|
84
|
+
methods.sort
|
85
|
+
else
|
86
|
+
raise SystemExportNotHandledError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def array_types(signature)
|
91
|
+
signature.map{|x| x.is_a?(Array) ? Array : x}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -19,7 +19,7 @@ module ActionService
|
|
19
19
|
request_info = nil
|
20
20
|
begin
|
21
21
|
protocol = probe_request_protocol(self.request)
|
22
|
-
request_info = protocol.request_info
|
22
|
+
request_info = protocol.request_info
|
23
23
|
if request_info
|
24
24
|
log_request request_info
|
25
25
|
response_info = dispatch_service_invocation_request(protocol, request_info)
|
@@ -35,7 +35,7 @@ module ActionService
|
|
35
35
|
end
|
36
36
|
rescue Exception => e
|
37
37
|
log_error e unless logger.nil?
|
38
|
-
service_object =
|
38
|
+
service_object = service_object(request_info.service_name)
|
39
39
|
exc_response = nil
|
40
40
|
if service_object.class.report_exceptions
|
41
41
|
exc_response_info = protocol.marshal_exception(e) rescue nil
|
@@ -13,15 +13,15 @@ module ActionService
|
|
13
13
|
SoapEncodingNs = 'http://schemas.xmlsoap.org/soap/encoding/'
|
14
14
|
SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'
|
15
15
|
|
16
|
-
def to_wsdl(
|
16
|
+
def to_wsdl(container, uri, soap_action_base)
|
17
17
|
wsdl = ""
|
18
18
|
|
19
|
-
namespace =
|
19
|
+
namespace = container.class.soap_mapper.custom_namespace
|
20
20
|
wsdl_service_name = namespace.split(/:/)[1]
|
21
21
|
|
22
22
|
mapper = ActionService::Protocol::Soap::SoapMapper.new(namespace)
|
23
23
|
services = {}
|
24
|
-
mapper.map_container_services(
|
24
|
+
mapper.map_container_services(container) do |name, klass, exports|
|
25
25
|
services[name] = [klass, exports]
|
26
26
|
end
|
27
27
|
custom_types = mapper.custom_types
|
@@ -78,7 +78,9 @@ module ActionService
|
|
78
78
|
xm.message('name' => msg_name) do
|
79
79
|
sym = nil
|
80
80
|
if direction == :out
|
81
|
-
|
81
|
+
if export_signature[:returns]
|
82
|
+
xm.part('name' => 'return', 'type' => export_signature[:returns][0].qualified_type_name)
|
83
|
+
end
|
82
84
|
else
|
83
85
|
mapping_list = export_signature[:expects]
|
84
86
|
i = 1
|
@@ -165,7 +167,7 @@ module ActionService
|
|
165
167
|
begin
|
166
168
|
uri = "http://#{@request.env['HTTP_HOST']||@request.env['SERVER_NAME']}/#{controller_name}/"
|
167
169
|
soap_action_base = "/#{controller_name}"
|
168
|
-
xml = self.class.to_wsdl(self
|
170
|
+
xml = self.class.to_wsdl(self, uri, soap_action_base)
|
169
171
|
send_data(xml, :type => 'text/xml', :disposition => 'inline')
|
170
172
|
rescue Exception => e
|
171
173
|
render_text('', "500 #{e.message}")
|
data/lib/action_service.rb
CHANGED
@@ -45,6 +45,7 @@ ActionController::Base.class_eval do
|
|
45
45
|
include ActionService::Container
|
46
46
|
include ActionService::Protocol::Registry
|
47
47
|
include ActionService::Protocol::Soap
|
48
|
+
include ActionService::Protocol::XmlRpc
|
48
49
|
include ActionService::Router::ActionController
|
49
50
|
include ActionService::Router::Wsdl
|
50
51
|
end
|
data/test/base_test.rb
CHANGED
@@ -44,4 +44,8 @@ class BaseTest < Test::Unit::TestCase
|
|
44
44
|
assert(BaseTestService.internal_export_name('Add') == :add)
|
45
45
|
assert(UnmangledOrReportedService.public_export_name(:custom_name_casing) == 'custom_name_casing')
|
46
46
|
end
|
47
|
+
|
48
|
+
def test_missing_method
|
49
|
+
assert(BaseTestService.has_public_export?('NoSuchMethod') == false)
|
50
|
+
end
|
47
51
|
end
|
data/test/container_test.rb
CHANGED
@@ -11,11 +11,23 @@ $immediate_service = Object.new
|
|
11
11
|
$deferred_service = Object.new
|
12
12
|
|
13
13
|
class TestContainer < AbstractTestContainer
|
14
|
+
attr :flag
|
15
|
+
attr :previous_flag
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@previous_flag = nil
|
19
|
+
@flag = true
|
20
|
+
end
|
21
|
+
|
14
22
|
service :immediate_service, $immediate_service
|
15
|
-
service(:deferred_service) { $deferred_service }
|
23
|
+
service(:deferred_service) { @previous_flag = @flag; @flag = false; $deferred_service }
|
16
24
|
end
|
17
25
|
|
18
26
|
class ContainerTest < Test::Unit::TestCase
|
27
|
+
def setup
|
28
|
+
@container = TestContainer.new
|
29
|
+
end
|
30
|
+
|
19
31
|
def test_registration
|
20
32
|
assert(TestContainer.has_service?(:immediate_service))
|
21
33
|
assert(TestContainer.has_service?(:deferred_service))
|
@@ -23,7 +35,12 @@ class ContainerTest < Test::Unit::TestCase
|
|
23
35
|
end
|
24
36
|
|
25
37
|
def test_service_object
|
26
|
-
assert(
|
27
|
-
assert(
|
38
|
+
assert(@container.flag == true)
|
39
|
+
assert(@container.service_object(:immediate_service) == $immediate_service)
|
40
|
+
assert(@container.previous_flag.nil?)
|
41
|
+
assert(@container.flag == true)
|
42
|
+
assert(@container.service_object(:deferred_service) == $deferred_service)
|
43
|
+
assert(@container.previous_flag == true)
|
44
|
+
assert(@container.flag == false)
|
28
45
|
end
|
29
46
|
end
|
data/test/protocol_soap_test.rb
CHANGED
@@ -4,10 +4,13 @@ require 'soap/rpc/element'
|
|
4
4
|
class SoapService < ActionService::Base
|
5
5
|
attr :int
|
6
6
|
attr :string
|
7
|
+
attr :values
|
8
|
+
attr :default_args
|
7
9
|
|
8
10
|
def initialize
|
9
11
|
@int = 20
|
10
12
|
@string = "wrong string value"
|
13
|
+
@default_args = nil
|
11
14
|
end
|
12
15
|
|
13
16
|
def some_method(int, string)
|
@@ -16,7 +19,19 @@ class SoapService < ActionService::Base
|
|
16
19
|
true
|
17
20
|
end
|
18
21
|
|
22
|
+
def array_returner
|
23
|
+
@values = [1, 2, 3]
|
24
|
+
end
|
25
|
+
|
26
|
+
def default(*args)
|
27
|
+
@default_args = args
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
19
31
|
export :some_method, :expects => [Integer, String], :returns => [TrueClass]
|
32
|
+
export :array_returner, :returns => [[Integer]]
|
33
|
+
|
34
|
+
default_export :default
|
20
35
|
end
|
21
36
|
|
22
37
|
class SoapContainer
|
@@ -68,7 +83,7 @@ class ProtocolSoapTest < Test::Unit::TestCase
|
|
68
83
|
test_request.env['RAW_POST_DATA'] = raw_request
|
69
84
|
protocol = @container.request_protocol(test_request)
|
70
85
|
assert(protocol.is_a?(ActionService::Protocol::Soap::SoapProtocol))
|
71
|
-
request_info = protocol.request_info
|
86
|
+
request_info = protocol.request_info
|
72
87
|
assert(request_info.service_name == service_name)
|
73
88
|
assert(request_info.public_method_name == public_method_name)
|
74
89
|
method_name = SoapService.internal_export_name(public_method_name)
|
@@ -77,7 +92,7 @@ class ProtocolSoapTest < Test::Unit::TestCase
|
|
77
92
|
assert(params.length == 2)
|
78
93
|
assert(params == [int_value, string_value])
|
79
94
|
response = @container.dispatch_request(protocol, request_info)
|
80
|
-
service =
|
95
|
+
service = @container.service_object(:soap_service)
|
81
96
|
assert(service.int == int_value)
|
82
97
|
assert(service.string == string_value)
|
83
98
|
raw_response = response.raw_body
|
@@ -91,4 +106,76 @@ class ProtocolSoapTest < Test::Unit::TestCase
|
|
91
106
|
def test_service_name
|
92
107
|
assert(SoapContainer.soap_mapper.custom_namespace == 'urn:Test')
|
93
108
|
end
|
109
|
+
|
110
|
+
def test_array_returning
|
111
|
+
service_name = 'soap_service'
|
112
|
+
public_method_name = 'ArrayReturner'
|
113
|
+
qname = XSD::QName.new('urn:ActionService', public_method_name)
|
114
|
+
request = SOAP::RPC::SOAPMethodRequest.new(qname)
|
115
|
+
header = SOAP::SOAPHeader.new
|
116
|
+
body = SOAP::SOAPBody.new(request)
|
117
|
+
envelope = SOAP::SOAPEnvelope.new(header, body)
|
118
|
+
raw_request = SOAP::Processor.marshal(envelope)
|
119
|
+
test_request = ActionController::TestRequest.new
|
120
|
+
test_request.request_parameters['action'] = service_name
|
121
|
+
test_request.env['REQUEST_METHOD'] = "POST"
|
122
|
+
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
|
123
|
+
test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{public_method_name}"
|
124
|
+
test_request.env['RAW_POST_DATA'] = raw_request
|
125
|
+
protocol = @container.request_protocol(test_request)
|
126
|
+
assert(protocol.is_a?(ActionService::Protocol::Soap::SoapProtocol))
|
127
|
+
request_info = protocol.request_info
|
128
|
+
method_name = SoapService.internal_export_name(public_method_name)
|
129
|
+
export_info = SoapService.exports[method_name]
|
130
|
+
response = @container.dispatch_request(protocol, request_info)
|
131
|
+
service = @container.service_object(:soap_service)
|
132
|
+
assert(service.values == [1, 2, 3])
|
133
|
+
raw_response = response.raw_body
|
134
|
+
envelope = SOAP::Processor.unmarshal(raw_response)
|
135
|
+
assert(envelope.is_a?(SOAP::SOAPEnvelope))
|
136
|
+
resp = envelope.body.response
|
137
|
+
assert(resp.is_a?(SOAP::SOAPArray))
|
138
|
+
assert(SOAP::Mapping.soap2obj(resp) == [1, 2, 3])
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_default_export
|
142
|
+
service_name = 'soap_service'
|
143
|
+
public_method_name = 'NonExistentMethodName'
|
144
|
+
qname = XSD::QName.new('urn:ActionService', public_method_name)
|
145
|
+
int_value = 50
|
146
|
+
bool_value = false
|
147
|
+
param_def = [
|
148
|
+
['in', 'param1', [SOAP::SOAPInt]],
|
149
|
+
['in', 'param2', [SOAP::SOAPBoolean]],
|
150
|
+
]
|
151
|
+
request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
|
152
|
+
request.set_param([
|
153
|
+
['param1', SOAP::Mapping.obj2soap(int_value)],
|
154
|
+
['param2', SOAP::Mapping.obj2soap(bool_value)]
|
155
|
+
])
|
156
|
+
header = SOAP::SOAPHeader.new
|
157
|
+
body = SOAP::SOAPBody.new(request)
|
158
|
+
envelope = SOAP::SOAPEnvelope.new(header, body)
|
159
|
+
raw_request = SOAP::Processor.marshal(envelope)
|
160
|
+
test_request = ActionController::TestRequest.new
|
161
|
+
test_request.request_parameters['action'] = service_name
|
162
|
+
test_request.env['REQUEST_METHOD'] = "POST"
|
163
|
+
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
|
164
|
+
test_request.env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{public_method_name}"
|
165
|
+
test_request.env['RAW_POST_DATA'] = raw_request
|
166
|
+
protocol = @container.request_protocol(test_request)
|
167
|
+
assert(protocol.is_a?(ActionService::Protocol::Soap::SoapProtocol))
|
168
|
+
request_info = protocol.request_info
|
169
|
+
method_name = SoapService.internal_export_name(public_method_name)
|
170
|
+
export_info = SoapService.exports[method_name]
|
171
|
+
service = @container.service_object(:soap_service)
|
172
|
+
assert(service.default_args.nil?)
|
173
|
+
response = @container.dispatch_request(protocol, request_info)
|
174
|
+
raw_response = response.raw_body
|
175
|
+
envelope = SOAP::Processor.unmarshal(raw_response)
|
176
|
+
assert(envelope.is_a?(SOAP::SOAPEnvelope))
|
177
|
+
resp = envelope.body.response
|
178
|
+
assert(resp.is_a?(SOAP::SOAPNil))
|
179
|
+
assert(service.default_args == [50, false])
|
180
|
+
end
|
94
181
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/abstract_unit'
|
2
|
+
require 'xmlrpc/parser'
|
3
|
+
require 'xmlrpc/create'
|
4
|
+
require 'xmlrpc/config'
|
5
|
+
|
6
|
+
module XMLRPC
|
7
|
+
class XmlRpcHelper
|
8
|
+
include ParserWriterChooseMixin
|
9
|
+
|
10
|
+
def create_request(methodName, *args)
|
11
|
+
create().methodCall(methodName, *args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_response(response)
|
15
|
+
parser().parseMethodResponse(response)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class XmlRpcService < ActionService::Base
|
21
|
+
attr :result
|
22
|
+
attr :hashvalue
|
23
|
+
attr :default_args
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@result = nil
|
27
|
+
@hashvalue = nil
|
28
|
+
@default_args = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(a, b)
|
32
|
+
@result = a + b
|
33
|
+
end
|
34
|
+
|
35
|
+
def something_hash(hash)
|
36
|
+
@hashvalue = hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def array_returner
|
40
|
+
[1, 2, 3]
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash_returner
|
44
|
+
{'name' => 1, 'value' => 2}
|
45
|
+
end
|
46
|
+
|
47
|
+
def default(*args)
|
48
|
+
@default_args = args
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
export :add, :expects => [Integer, Integer], :returns => [Integer]
|
53
|
+
export :hash_returner, :returns => [Hash]
|
54
|
+
export :array_returner, :returns => [[Integer]]
|
55
|
+
export :something_hash, :expects => [Hash]
|
56
|
+
|
57
|
+
default_export :default
|
58
|
+
end
|
59
|
+
|
60
|
+
$service = XmlRpcService.new
|
61
|
+
|
62
|
+
class XmlRpcContainer
|
63
|
+
include ActionService::Container
|
64
|
+
include ActionService::Protocol::Registry
|
65
|
+
include ActionService::Protocol::Soap
|
66
|
+
include ActionService::Protocol::XmlRpc
|
67
|
+
|
68
|
+
def request_protocol(request)
|
69
|
+
probe_request_protocol(request)
|
70
|
+
end
|
71
|
+
|
72
|
+
def dispatch_request(protocol, info)
|
73
|
+
dispatch_service_invocation_request(protocol, info)
|
74
|
+
end
|
75
|
+
|
76
|
+
service :xmlrpc, $service
|
77
|
+
end
|
78
|
+
|
79
|
+
class ProtocolXmlRpcTest < Test::Unit::TestCase
|
80
|
+
def setup
|
81
|
+
@helper = XMLRPC::XmlRpcHelper.new
|
82
|
+
@container = XmlRpcContainer.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_xmlrpc_request_dispatching
|
86
|
+
retval = do_xmlrpc_call('Add', 50, 30)
|
87
|
+
assert(retval == [true, 80])
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_array_returning
|
91
|
+
retval = do_xmlrpc_call('ArrayReturner')
|
92
|
+
assert(retval == [true, [1, 2, 3]])
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_hash_returning
|
96
|
+
retval = do_xmlrpc_call('HashReturner')
|
97
|
+
assert(retval == [true, {'name' => 1, 'value' => 2}])
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_hash_parameter
|
101
|
+
retval = do_xmlrpc_call('SomethingHash', {'name' => 1, 'value' => 2})
|
102
|
+
assert(retval == [true, true])
|
103
|
+
assert($service.hashvalue == {'name' => 1, 'value' => 2})
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_default_export
|
107
|
+
retval = do_xmlrpc_call('SomeNonexistentMethod', 'test', [1, 2], {'name'=>'value'})
|
108
|
+
assert(retval == [true, true])
|
109
|
+
assert($service.default_args == ['test', [1, 2], {'name'=>'value'}])
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_xmlrpc_introspection
|
113
|
+
retval = do_xmlrpc_call('system.listMethods', 'test', [1, 2], {'name'=>'value'})
|
114
|
+
assert(retval == [true, ["Add", "ArrayReturner", "HashReturner", "SomethingHash"]])
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def do_xmlrpc_call(public_method_name, *args)
|
119
|
+
service_name = 'xmlrpc'
|
120
|
+
raw_request = @helper.create_request(public_method_name, *args)
|
121
|
+
test_request = ActionController::TestRequest.new
|
122
|
+
test_request.request_parameters['action'] = service_name
|
123
|
+
test_request.env['REQUEST_METHOD'] = "POST"
|
124
|
+
test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
|
125
|
+
test_request.env['RAW_POST_DATA'] = raw_request
|
126
|
+
protocol = @container.request_protocol(test_request)
|
127
|
+
response = @container.dispatch_request(protocol, protocol.request_info)
|
128
|
+
@helper.parse_response(response.raw_body)
|
129
|
+
end
|
130
|
+
end
|
data/test/router_wsdl_test.rb
CHANGED
@@ -17,8 +17,12 @@ class WsdlTestService < ActionService::Base
|
|
17
17
|
[]
|
18
18
|
end
|
19
19
|
|
20
|
+
def nil_returner
|
21
|
+
end
|
22
|
+
|
20
23
|
export :add, :expects => [Integer, Integer], :returns => [Integer]
|
21
24
|
export :find_people, :returns => [[TestPerson]]
|
25
|
+
export :nil_returner
|
22
26
|
end
|
23
27
|
|
24
28
|
class WsdlController < ActionController::Base
|
@@ -27,7 +31,7 @@ end
|
|
27
31
|
|
28
32
|
class RouterWsdlTest < Test::Unit::TestCase
|
29
33
|
def test_wsdl_generation
|
30
|
-
wsdl = WsdlController.to_wsdl(WsdlController, 'http://localhost:3000/test/', '/test')
|
34
|
+
wsdl = WsdlController.to_wsdl(WsdlController.new, 'http://localhost:3000/test/', '/test')
|
31
35
|
assert(WSDL::Parser.new.parse(wsdl).is_a?(WSDL::Definitions))
|
32
36
|
end
|
33
37
|
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: actionservice
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2.
|
7
|
-
date: 2005-02-
|
6
|
+
version: 0.2.100
|
7
|
+
date: 2005-02-08
|
8
8
|
summary: Web service support for Action Pack.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -35,38 +35,40 @@ files:
|
|
35
35
|
- CHANGELOG
|
36
36
|
- MIT-LICENSE
|
37
37
|
- examples/soap
|
38
|
-
- examples/soap/app
|
39
38
|
- examples/soap/lib
|
40
39
|
- examples/soap/README
|
40
|
+
- examples/soap/app
|
41
|
+
- examples/soap/lib/google_search_service.rb
|
41
42
|
- examples/soap/app/controllers
|
42
43
|
- examples/soap/app/controllers/search_controller.rb
|
43
|
-
- examples/soap/lib/google_search_service.rb
|
44
44
|
- lib/action_service
|
45
45
|
- lib/action_service.rb
|
46
|
-
- lib/action_service/router
|
47
46
|
- lib/action_service/protocol
|
48
|
-
- lib/action_service/
|
49
|
-
- lib/action_service/router.rb
|
50
|
-
- lib/action_service/protocol.rb
|
51
|
-
- lib/action_service/container.rb
|
52
|
-
- lib/action_service/struct.rb
|
47
|
+
- lib/action_service/router
|
53
48
|
- lib/action_service/base.rb
|
49
|
+
- lib/action_service/container.rb
|
54
50
|
- lib/action_service/support
|
51
|
+
- lib/action_service/protocol.rb
|
52
|
+
- lib/action_service/router.rb
|
53
|
+
- lib/action_service/struct.rb
|
54
|
+
- lib/action_service/invocation.rb
|
55
|
+
- lib/action_service/protocol/registry.rb
|
56
|
+
- lib/action_service/protocol/soap.rb
|
57
|
+
- lib/action_service/protocol/xmlrpc.rb
|
58
|
+
- lib/action_service/protocol/abstract.rb
|
55
59
|
- lib/action_service/router/action_controller.rb
|
56
60
|
- lib/action_service/router/wsdl.rb
|
57
|
-
- lib/action_service/protocol/abstract.rb
|
58
|
-
- lib/action_service/protocol/soap.rb
|
59
|
-
- lib/action_service/protocol/registry.rb
|
60
61
|
- lib/action_service/support/class_inheritable_options.rb
|
62
|
+
- test/struct_test.rb
|
63
|
+
- test/abstract_unit.rb
|
61
64
|
- test/base_test.rb
|
62
65
|
- test/container_test.rb
|
63
|
-
- test/
|
64
|
-
- test/router_action_controller_test.rb
|
65
|
-
- test/abstract_unit.rb
|
66
|
+
- test/protocol_registry_test.rb
|
66
67
|
- test/protocol_soap_test.rb
|
67
|
-
- test/
|
68
|
+
- test/invocation_test.rb
|
68
69
|
- test/router_wsdl_test.rb
|
69
|
-
- test/
|
70
|
+
- test/protocol_xmlrpc_test.rb
|
71
|
+
- test/router_action_controller_test.rb
|
70
72
|
test_files: []
|
71
73
|
rdoc_options: []
|
72
74
|
extra_rdoc_files: []
|