actionwebservice 0.5.0 → 0.6.0

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.
Files changed (66) hide show
  1. data/{ChangeLog → CHANGELOG} +20 -0
  2. data/README +45 -1
  3. data/Rakefile +12 -10
  4. data/TODO +8 -9
  5. data/lib/action_web_service.rb +10 -6
  6. data/lib/action_web_service/api.rb +1 -2
  7. data/lib/action_web_service/api/{abstract.rb → base.rb} +14 -71
  8. data/lib/action_web_service/base.rb +0 -3
  9. data/lib/action_web_service/client/base.rb +1 -12
  10. data/lib/action_web_service/client/soap_client.rb +49 -17
  11. data/lib/action_web_service/client/xmlrpc_client.rb +20 -15
  12. data/lib/action_web_service/container.rb +3 -85
  13. data/lib/action_web_service/{api/action_controller.rb → container/action_controller_container.rb} +2 -2
  14. data/lib/action_web_service/container/delegated_container.rb +87 -0
  15. data/lib/action_web_service/container/direct_container.rb +70 -0
  16. data/lib/action_web_service/dispatcher/abstract.rb +100 -102
  17. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +199 -137
  18. data/lib/action_web_service/protocol.rb +1 -1
  19. data/lib/action_web_service/protocol/abstract.rb +14 -112
  20. data/lib/action_web_service/protocol/discovery.rb +37 -0
  21. data/lib/action_web_service/protocol/soap_protocol.rb +32 -458
  22. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +29 -149
  23. data/lib/action_web_service/struct.rb +2 -5
  24. data/lib/action_web_service/test_invoke.rb +130 -0
  25. data/lib/action_web_service/vendor/ws.rb +4 -0
  26. data/lib/action_web_service/vendor/ws/common.rb +8 -0
  27. data/lib/action_web_service/vendor/ws/encoding.rb +3 -0
  28. data/lib/action_web_service/vendor/ws/encoding/abstract.rb +26 -0
  29. data/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +90 -0
  30. data/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +53 -0
  31. data/lib/action_web_service/vendor/ws/marshaling.rb +3 -0
  32. data/lib/action_web_service/vendor/ws/marshaling/abstract.rb +17 -0
  33. data/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +277 -0
  34. data/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +116 -0
  35. data/lib/action_web_service/vendor/ws/types.rb +162 -0
  36. data/test/abstract_client.rb +8 -11
  37. data/test/abstract_dispatcher.rb +370 -0
  38. data/test/abstract_unit.rb +1 -0
  39. data/test/api_test.rb +18 -1
  40. data/test/apis/auto_load_api.rb +3 -0
  41. data/test/apis/broken_auto_load_api.rb +2 -0
  42. data/test/client_soap_test.rb +16 -3
  43. data/test/client_xmlrpc_test.rb +16 -4
  44. data/test/container_test.rb +28 -8
  45. data/test/dispatcher_action_controller_soap_test.rb +106 -0
  46. data/test/dispatcher_action_controller_xmlrpc_test.rb +44 -0
  47. data/test/gencov +1 -1
  48. data/test/invocation_test.rb +39 -3
  49. data/test/run +4 -4
  50. data/test/test_invoke_test.rb +77 -0
  51. data/test/ws/abstract_encoding.rb +68 -0
  52. data/test/ws/abstract_unit.rb +13 -0
  53. data/test/ws/gencov +3 -0
  54. data/test/ws/run +5 -0
  55. data/test/ws/soap_marshaling_test.rb +91 -0
  56. data/test/ws/soap_rpc_encoding_test.rb +47 -0
  57. data/test/ws/types_test.rb +41 -0
  58. data/test/ws/xmlrpc_encoding_test.rb +34 -0
  59. metadata +48 -19
  60. data/lib/action_web_service/protocol/registry.rb +0 -55
  61. data/lib/action_web_service/support/signature.rb +0 -100
  62. data/test/abstract_soap.rb +0 -58
  63. data/test/dispatcher_action_controller_test.rb +0 -186
  64. data/test/protocol_registry_test.rb +0 -53
  65. data/test/protocol_soap_test.rb +0 -252
  66. data/test/protocol_xmlrpc_test.rb +0 -147
@@ -1,167 +1,47 @@
1
- require 'xmlrpc/parser'
2
- require 'xmlrpc/create'
3
- require 'xmlrpc/config'
4
- require 'xmlrpc/utils'
5
- require 'singleton'
6
-
7
- module XMLRPC # :nodoc:
8
- class XmlRpcHelper # :nodoc:
9
- include Singleton
10
- include ParserWriterChooseMixin
11
-
12
- def parse_method_call(message)
13
- parser().parseMethodCall(message)
14
- end
15
-
16
- def create_method_response(successful, return_value)
17
- create().methodResponse(successful, return_value)
18
- end
19
- end
20
- end
21
-
22
1
  module ActionWebService # :nodoc:
23
2
  module Protocol # :nodoc:
24
3
  module XmlRpc # :nodoc:
25
- def self.append_features(base) # :nodoc:
26
- super
27
- base.register_protocol(BodyOnly, XmlRpcProtocol)
4
+ def self.included(base)
5
+ base.register_protocol(XmlRpcProtocol)
28
6
  end
7
+
8
+ class XmlRpcProtocol # :nodoc:
9
+ attr :marshaler
29
10
 
30
- class XmlRpcProtocol < AbstractProtocol # :nodoc:
31
- def self.create_protocol_request(container_class, action_pack_request)
32
- helper = XMLRPC::XmlRpcHelper.instance
33
- service_name = action_pack_request.parameters['action']
34
- methodname, params = helper.parse_method_call(action_pack_request.raw_post)
35
- methodname.gsub!(/^[^\.]+\./, '') unless methodname =~ /^system\./ # XXX
36
- protocol = XmlRpcProtocol.new(container_class)
37
- content_type = action_pack_request.env['HTTP_CONTENT_TYPE']
38
- content_type ||= 'text/xml'
39
- request = ProtocolRequest.new(protocol,
40
- action_pack_request.raw_post,
41
- service_name.to_sym,
42
- methodname,
43
- content_type,
44
- :xmlrpc_values => params)
45
- request
46
- rescue
47
- nil
48
- end
49
-
50
- def self.create_protocol_client(api, protocol_name, endpoint_uri, options)
51
- return nil unless protocol_name.to_s.downcase.to_sym == :xmlrpc
52
- ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
11
+ def initialize
12
+ @encoder = WS::Encoding::XmlRpcEncoding.new
13
+ @marshaler = WS::Marshaling::XmlRpcMarshaler.new
53
14
  end
54
15
 
55
- def initialize(container_class)
56
- super(container_class)
16
+ def unmarshal_request(ap_request)
17
+ method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
18
+ params = params.map{|x| @marshaler.unmarshal(x)}
19
+ service_name = ap_request.parameters['action']
20
+ Request.new(self, method_name, params, service_name)
21
+ rescue
22
+ nil
57
23
  end
58
24
 
59
- def unmarshal_request(protocol_request)
60
- values = protocol_request.options[:xmlrpc_values]
61
- signature = protocol_request.signature
62
- if signature
63
- values = self.class.transform_incoming_method_params(self.class.transform_array_types(signature), values)
64
- protocol_request.check_parameter_types(values, check_array_types(signature))
65
- values
66
- else
67
- protocol_request.checked? ? [] : values
68
- end
69
- end
70
-
71
- def marshal_response(protocol_request, return_value)
72
- helper = XMLRPC::XmlRpcHelper.instance
73
- signature = protocol_request.return_signature
74
- if signature
75
- protocol_request.check_parameter_types([return_value], check_array_types(signature))
76
- return_value = self.class.transform_return_value(self.class.transform_array_types(signature), return_value)
77
- raw_response = helper.create_method_response(true, return_value)
25
+ def marshal_response(method_name, return_value, signature_type)
26
+ if !return_value.nil? && signature_type
27
+ type_binding = @marshaler.register_type(signature_type)
28
+ info = WS::ParamInfo.create(signature_type, type_binding, 0)
29
+ return_value = @marshaler.marshal(WS::Param.new(return_value, info))
78
30
  else
79
- # XML-RPC doesn't have the concept of a void method, nor does it
80
- # support a nil return value, so return true if we would have returned
81
- # nil
82
- if protocol_request.checked?
83
- raw_response = helper.create_method_response(true, true)
84
- else
85
- return_value = true if return_value.nil?
86
- raw_response = helper.create_method_response(true, return_value)
87
- end
31
+ return_value = nil
88
32
  end
89
- ProtocolResponse.new(self, raw_response, 'text/xml')
33
+ body = @encoder.encode_rpc_response(method_name, return_value)
34
+ Response.new(body, 'text/xml')
90
35
  end
91
-
92
- def marshal_exception(exception)
93
- helper = XMLRPC::XmlRpcHelper.instance
94
- exception = XMLRPC::FaultException.new(1, exception.message)
95
- raw_response = helper.create_method_response(false, exception)
96
- ProtocolResponse.new(self, raw_response, 'text/xml')
97
- end
98
-
99
- class << self
100
- def transform_incoming_method_params(signature, params)
101
- (1..signature.size).each do |i|
102
- i -= 1
103
- params[i] = xmlrpc_to_ruby(params[i], signature[i])
104
- end
105
- params
106
- end
107
-
108
- def transform_return_value(signature, return_value)
109
- ruby_to_xmlrpc(return_value, signature[0])
110
- end
111
-
112
- def ruby_to_xmlrpc(param, param_class)
113
- if param_class.is_a?(XmlRpcArray)
114
- param.map{|p| ruby_to_xmlrpc(p, param_class.klass)}
115
- elsif param_class.ancestors.include?(ActiveRecord::Base)
116
- param.instance_variable_get('@attributes')
117
- elsif param_class.ancestors.include?(ActionWebService::Struct)
118
- struct = {}
119
- param_class.members.each do |name, klass|
120
- value = param.send(name)
121
- next if value.nil?
122
- struct[name.to_s] = value
123
- end
124
- struct
125
- else
126
- param
127
- end
128
- end
129
36
 
130
- def xmlrpc_to_ruby(param, param_class)
131
- if param_class.is_a?(XmlRpcArray)
132
- param.map{|p| xmlrpc_to_ruby(p, param_class.klass)}
133
- elsif param_class.ancestors.include?(ActiveRecord::Base)
134
- raise(ProtocolError, "incoming ActiveRecord::Base types are not allowed")
135
- elsif param_class.ancestors.include?(ActionWebService::Struct)
136
- unless param.is_a?(Hash)
137
- raise(ProtocolError, "expected parameter to be a Hash")
138
- end
139
- new_param = param_class.new
140
- param_class.members.each do |name, klass|
141
- new_param.send('%s=' % name.to_s, param[name.to_s])
142
- end
143
- new_param
144
- else
145
- param
146
- end
147
- end
37
+ def register_signature_type(spec)
38
+ nil
39
+ end
148
40
 
149
- def transform_array_types(signature)
150
- signature.map{|x| x.is_a?(Array) ? XmlRpcArray.new(x[0]) : x}
151
- end
41
+ def protocol_client(api, protocol_name, endpoint_uri, options)
42
+ return nil unless protocol_name == :xmlrpc
43
+ ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
152
44
  end
153
-
154
- private
155
- def check_array_types(signature)
156
- signature.map{|x| x.is_a?(Array) ? Array : x}
157
- end
158
-
159
- class XmlRpcArray
160
- attr :klass
161
- def initialize(klass)
162
- @klass = klass
163
- end
164
- end
165
45
  end
166
46
  end
167
47
  end
@@ -17,8 +17,7 @@ module ActionWebService
17
17
  # person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe')
18
18
  #
19
19
  # Active Record model classes are already implicitly supported for method
20
- # return signatures. A structure containing its columns as members will be
21
- # automatically generated if its present in a signature.
20
+ # return signatures.
22
21
  class Struct
23
22
 
24
23
  # If a Hash is given as argument to an ActionWebService::Struct constructor,
@@ -35,12 +34,10 @@ module ActionWebService
35
34
  end
36
35
 
37
36
  class << self
38
- include ActionWebService::Signature
39
-
40
37
  # Creates a structure member with the specified +name+ and +type+. Generates
41
38
  # accessor methods for reading and writing the member value.
42
39
  def member(name, type)
43
- write_inheritable_hash("struct_members", name => signature_parameter_class(type))
40
+ write_inheritable_hash("struct_members", name => WS::BaseTypes.canonical_param_type_class(type))
44
41
  class_eval <<-END
45
42
  def #{name}; @#{name}; end
46
43
  def #{name}=(value); @#{name} = value; end
@@ -0,0 +1,130 @@
1
+ require 'test/unit'
2
+
3
+ module Test
4
+ module Unit
5
+ class TestCase # :nodoc:
6
+ private
7
+ # invoke the specified API method
8
+ def invoke_direct(method_name, *args)
9
+ prepare_request('api', 'api', method_name, *args)
10
+ @controller.process(@request, @response)
11
+ decode_rpc_response
12
+ end
13
+ alias_method :invoke, :invoke_direct
14
+
15
+ # invoke the specified API method on the specified service
16
+ def invoke_delegated(service_name, method_name, *args)
17
+ prepare_request(service_name.to_s, service_name, method_name, *args)
18
+ @controller.process(@request, @response)
19
+ decode_rpc_response
20
+ end
21
+
22
+ # invoke the specified layered API method on the correct service
23
+ def invoke_layered(service_name, method_name, *args)
24
+ if protocol == :soap
25
+ raise "SOAP protocol support for :layered dispatching mode is not available"
26
+ end
27
+ prepare_request('api', service_name, method_name, *args)
28
+ @controller.process(@request, @response)
29
+ decode_rpc_response
30
+ end
31
+
32
+ # ---------------------- internal ---------------------------
33
+
34
+ def prepare_request(action, service_name, api_method_name, *args)
35
+ @request.request_parameters['action'] = action
36
+ @request.env['REQUEST_METHOD'] = 'POST'
37
+ @request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
38
+ @request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args)
39
+ case protocol
40
+ when :soap
41
+ soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}"
42
+ @request.env['HTTP_SOAPACTION'] = soap_action
43
+ when :xmlrpc
44
+ @request.env.delete('HTTP_SOAPACTION')
45
+ end
46
+ end
47
+
48
+ def encode_rpc_call(service_name, api_method_name, *args)
49
+ case @controller.web_service_dispatching_mode
50
+ when :direct
51
+ api = @controller.class.web_service_api
52
+ when :delegated, :layered
53
+ api = @controller.web_service_object(service_name.to_sym).class.web_service_api
54
+ end
55
+ info = api.api_methods[api_method_name.to_sym]
56
+ ((info[:expects] || []) + (info[:returns] || [])).each do |spec|
57
+ marshaler.register_type spec
58
+ end
59
+ expects = info[:expects]
60
+ args = args.dup
61
+ (0..(args.length-1)).each do |i|
62
+ type_binding = marshaler.register_type(expects ? expects[i] : args[i].class)
63
+ info = WS::ParamInfo.create(expects ? expects[i] : args[i].class, type_binding, i)
64
+ args[i] = marshaler.marshal(WS::Param.new(args[i], info))
65
+ end
66
+ encoder.encode_rpc_call(public_method_name(service_name, api_method_name), args)
67
+ end
68
+
69
+ def decode_rpc_response
70
+ public_method_name, return_value = encoder.decode_rpc_response(@response.body)
71
+ result = marshaler.unmarshal(return_value).value
72
+ unless @return_exceptions
73
+ exception = is_exception?(result)
74
+ raise exception if exception
75
+ end
76
+ result
77
+ end
78
+
79
+ def public_method_name(service_name, api_method_name)
80
+ public_name = service_api(service_name).public_api_method_name(api_method_name)
81
+ if @controller.web_service_dispatching_mode == :layered
82
+ '%s.%s' % [service_name.to_s, public_name]
83
+ else
84
+ public_name
85
+ end
86
+ end
87
+
88
+ def service_api(service_name)
89
+ case @controller.web_service_dispatching_mode
90
+ when :direct
91
+ @controller.class.web_service_api
92
+ when :delegated, :layered
93
+ @controller.web_service_object(service_name.to_sym).class.web_service_api
94
+ end
95
+ end
96
+
97
+ def protocol
98
+ @protocol ||= :soap
99
+ end
100
+
101
+ def marshaler
102
+ case protocol
103
+ when :soap
104
+ @soap_marshaler ||= WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
105
+ when :xmlrpc
106
+ @xmlrpc_marshaler ||= WS::Marshaling::XmlRpcMarshaler.new
107
+ end
108
+ end
109
+
110
+ def encoder
111
+ case protocol
112
+ when :soap
113
+ @soap_encoder ||= WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
114
+ when :xmlrpc
115
+ @xmlrpc_encoder ||= WS::Encoding::XmlRpcEncoding.new
116
+ end
117
+ end
118
+
119
+ def is_exception?(obj)
120
+ case protocol
121
+ when :soap
122
+ (obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
123
+ obj.detail.cause.is_a?(Exception)) ? obj.detail.cause : nil
124
+ when :xmlrpc
125
+ obj.is_a?(XMLRPC::FaultException) ? obj : nil
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,4 @@
1
+ require 'ws/common'
2
+ require 'ws/types'
3
+ require 'ws/marshaling'
4
+ require 'ws/encoding'
@@ -0,0 +1,8 @@
1
+ module WS
2
+ class WSError < StandardError
3
+ end
4
+
5
+ def self.derived_from?(ancestor, child)
6
+ child.ancestors.include?(ancestor)
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ require 'ws/encoding/abstract'
2
+ require 'ws/encoding/soap_rpc_encoding'
3
+ require 'ws/encoding/xmlrpc_encoding'
@@ -0,0 +1,26 @@
1
+ module WS
2
+ module Encoding
3
+ # Encoders operate on _foreign_ objects. That is, Ruby object
4
+ # instances that are the _marshaling format specific_ representation
5
+ # of objects. In other words, objects that have not yet been marshaled, but
6
+ # are in protocol-specific form (such as an AST or DOM element), and not
7
+ # native Ruby form.
8
+ class AbstractEncoding
9
+ def encode_rpc_call(method_name, params)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def decode_rpc_call(obj)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def encode_rpc_response(method_name, return_value)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def decode_rpc_response(obj)
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,90 @@
1
+ require 'soap/processor'
2
+ require 'soap/mapping'
3
+ require 'soap/rpc/element'
4
+
5
+ module WS
6
+ module Encoding
7
+ class SoapRpcError < WSError
8
+ end
9
+
10
+ class SoapRpcEncoding < AbstractEncoding
11
+ attr_accessor :method_namespace
12
+
13
+ def initialize(method_namespace='')
14
+ @method_namespace = method_namespace
15
+ end
16
+
17
+ def encode_rpc_call(method_name, foreign_params)
18
+ qname = create_method_qname(method_name)
19
+ param_def = []
20
+ params = foreign_params.map do |p|
21
+ param_def << ['in', p.param.info.name, p.param.info.data.mapping]
22
+ [p.param.info.name, p.soap_object]
23
+ end
24
+ request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
25
+ request.set_param(params)
26
+ envelope = create_soap_envelope(request)
27
+ SOAP::Processor.marshal(envelope)
28
+ end
29
+
30
+ def decode_rpc_call(obj)
31
+ envelope = SOAP::Processor.unmarshal(obj)
32
+ unless envelope
33
+ raise(SoapRpcError, "Malformed SOAP request")
34
+ end
35
+ request = envelope.body.request
36
+ method_name = request.elename.name
37
+ params = request.collect do |key, value|
38
+ info = ParamInfo.new(key, nil, nil)
39
+ param = Param.new(nil, info)
40
+ Marshaling::SoapForeignObject.new(param, request[key])
41
+ end
42
+ [method_name, params]
43
+ end
44
+
45
+ def encode_rpc_response(method_name, return_value)
46
+ response = nil
47
+ qname = create_method_qname(method_name)
48
+ if return_value.nil?
49
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
50
+ else
51
+ param = return_value.param
52
+ soap_object = return_value.soap_object
53
+ param_def = [['retval', 'return', param.info.data.mapping]]
54
+ if soap_object.is_a?(SOAP::SOAPFault)
55
+ response = soap_object
56
+ else
57
+ response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
58
+ response.retval = soap_object
59
+ end
60
+ end
61
+ envelope = create_soap_envelope(response)
62
+ SOAP::Processor.marshal(envelope)
63
+ end
64
+
65
+ def decode_rpc_response(obj)
66
+ envelope = SOAP::Processor.unmarshal(obj)
67
+ unless envelope
68
+ raise(SoapRpcError, "Malformed SOAP response")
69
+ end
70
+ method_name = envelope.body.request.elename.name
71
+ return_value = envelope.body.response
72
+ info = ParamInfo.new('return', nil, nil)
73
+ param = Param.new(nil, info)
74
+ return_value = Marshaling::SoapForeignObject.new(param, return_value)
75
+ [method_name, return_value]
76
+ end
77
+
78
+ private
79
+ def create_soap_envelope(body)
80
+ header = SOAP::SOAPHeader.new
81
+ body = SOAP::SOAPBody.new(body)
82
+ SOAP::SOAPEnvelope.new(header, body)
83
+ end
84
+
85
+ def create_method_qname(method_name)
86
+ XSD::QName.new(@method_namespace, method_name)
87
+ end
88
+ end
89
+ end
90
+ end