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,3 +1,6 @@
1
+ require 'benchmark'
2
+ require 'builder/xmlmarkup'
3
+
1
4
  module ActionWebService # :nodoc:
2
5
  module Dispatcher # :nodoc:
3
6
  module ActionController # :nodoc:
@@ -7,106 +10,123 @@ module ActionWebService # :nodoc:
7
10
  class << self
8
11
  alias_method :inherited_without_action_controller, :inherited
9
12
  end
10
- alias_method :before_direct_invoke_without_action_controller, :before_direct_invoke
11
- alias_method :after_direct_invoke_without_action_controller, :after_direct_invoke
13
+ alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
12
14
  end
13
15
  base.add_web_service_api_callback do |klass, api|
14
16
  if klass.web_service_dispatching_mode == :direct
15
- klass.class_eval <<-EOS
16
- def api
17
- controller_dispatch_web_service_request
18
- end
19
- EOS
17
+ klass.class_eval 'def api; dispatch_web_service_request; end'
20
18
  end
21
19
  end
22
20
  base.add_web_service_definition_callback do |klass, name, info|
23
21
  if klass.web_service_dispatching_mode == :delegated
24
- klass.class_eval <<-EOS
25
- def #{name}
26
- controller_dispatch_web_service_request
27
- end
28
- EOS
22
+ klass.class_eval "def #{name}; dispatch_web_service_request; end"
23
+ elsif klass.web_service_dispatching_mode == :layered
24
+ klass.class_eval 'def api; dispatch_web_service_request; end'
29
25
  end
30
26
  end
31
27
  base.extend(ClassMethods)
32
- base.send(:include, ActionWebService::Dispatcher::ActionController::Invocation)
28
+ base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
33
29
  end
34
30
 
35
31
  module ClassMethods # :nodoc:
36
32
  def inherited(child)
37
33
  inherited_without_action_controller(child)
38
- child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlGeneration)
34
+ child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
39
35
  end
40
36
  end
41
37
 
42
- module Invocation # :nodoc:
38
+ module InstanceMethods # :nodoc:
43
39
  private
44
- def controller_dispatch_web_service_request
45
- request, response, elapsed, exception = dispatch_web_service_request(@request)
46
- if response
47
- begin
48
- log_request(request)
49
- log_error(exception) if exception && logger
50
- log_response(response, elapsed)
51
- response_options = { :type => response.content_type, :disposition => 'inline' }
52
- send_data(response.raw_body, response_options)
53
- rescue Exception => e
54
- log_error(e) unless logger.nil?
55
- render_text("Internal protocol error", "500 Internal Server Error")
40
+ def dispatch_web_service_request
41
+ request = discover_web_service_request(@request)
42
+ if request
43
+ log_request(request, @request.raw_post)
44
+ response = nil
45
+ exception = nil
46
+ bm = Benchmark.measure do
47
+ begin
48
+ response = invoke_web_service_request(request)
49
+ rescue Exception => e
50
+ exception = e
51
+ end
52
+ end
53
+ if exception
54
+ log_error(exception) unless logger.nil?
55
+ send_web_service_error_response(request, exception)
56
+ else
57
+ send_web_service_response(response, bm.real)
56
58
  end
57
59
  else
58
- logger.error("No response available") unless logger.nil?
59
- render_text("Internal protocol error", "500 Internal Server Error")
60
+ exception = DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
61
+ send_web_service_error_response(request, exception)
60
62
  end
63
+ rescue Exception => e
64
+ log_error(e) unless logger.nil?
65
+ send_web_service_error_response(request, e)
61
66
  end
62
67
 
63
- def before_direct_invoke(request)
64
- before_direct_invoke_without_action_controller(request)
65
- @params ||= {}
66
- signature = request.signature
67
- if signature && (expects = request.signature[:expects])
68
- (0..(@method_params.size-1)).each do |i|
69
- if expects[i].is_a?(Hash)
70
- @params[expects[i].keys[0].to_s] = @method_params[i]
71
- else
72
- @params['param%d' % i] = @method_params[i]
73
- end
68
+ def send_web_service_response(response, elapsed=nil)
69
+ log_response(response, elapsed)
70
+ options = { :type => response.content_type, :disposition => 'inline' }
71
+ send_data(response.body, options)
72
+ end
73
+
74
+ def send_web_service_error_response(request, exception)
75
+ if request
76
+ unless self.class.web_service_exception_reporting
77
+ exception = DispatcherError.new("Internal server error (exception raised)")
78
+ end
79
+ response = request.protocol.marshal_response(request.method_name, exception, exception.class)
80
+ send_web_service_response(response)
81
+ else
82
+ if self.class.web_service_exception_reporting
83
+ message = exception.message
84
+ else
85
+ message = "Exception raised"
74
86
  end
87
+ render_text("Internal protocol error: #{message}", "500 #{message}")
75
88
  end
76
- @params['action'] = request.method_name.to_s
77
- @session ||= {}
78
- @assigns ||= {}
79
- return nil if before_action == false
80
- true
81
89
  end
82
90
 
83
- def after_direct_invoke(request)
84
- after_direct_invoke_without_action_controller(request)
91
+ def web_service_direct_invoke(invocation)
92
+ @params ||= {}
93
+ invocation.method_named_params.each do |name, value|
94
+ @params[name] = value
95
+ end
96
+ @session ||= {}
97
+ @assigns ||= {}
98
+ @params['action'] = invocation.api_method_name.to_s
99
+ if before_action == false
100
+ raise(DispatcherError, "Method filtered")
101
+ end
102
+ return_value = web_service_direct_invoke_without_controller(invocation)
85
103
  after_action
104
+ return_value
86
105
  end
87
106
 
88
- def log_request(request)
89
- unless logger.nil? || request.nil?
90
- logger.debug("\nWeb Service Request:")
91
- indented = request.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
92
- logger.debug(indented)
107
+ def log_request(request, body)
108
+ unless logger.nil?
109
+ name = request.method_name
110
+ params = request.method_params.map{|x| "#{x.info.name}=>#{x.value.inspect}"}
111
+ service = request.service_name
112
+ logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
113
+ logger.debug(indent(body))
93
114
  end
94
115
  end
95
116
 
96
- def log_response(response, elapsed)
97
- unless logger.nil? || response.nil?
98
- logger.debug("\nWeb Service Response (%f):" % elapsed)
99
- indented = response.raw_body.split(/\n/).map{|x| " #{x}"}.join("\n")
100
- logger.debug(indented)
117
+ def log_response(response, elapsed=nil)
118
+ unless logger.nil?
119
+ logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":"))
120
+ logger.debug(indent(response.body))
101
121
  end
102
122
  end
103
123
 
104
- unless method_defined?(:logger)
105
- def logger; @logger; end
124
+ def indent(body)
125
+ body.split(/\n/).map{|x| " #{x}"}.join("\n")
106
126
  end
107
127
  end
108
128
 
109
- module WsdlGeneration # :nodoc:
129
+ module WsdlAction # :nodoc:
110
130
  XsdNs = 'http://www.w3.org/2001/XMLSchema'
111
131
  WsdlNs = 'http://schemas.xmlsoap.org/wsdl/'
112
132
  SoapNs = 'http://schemas.xmlsoap.org/wsdl/soap/'
@@ -117,40 +137,53 @@ module ActionWebService # :nodoc:
117
137
  case @request.method
118
138
  when :get
119
139
  begin
120
- host_name = @request.env['HTTP_HOST'] || @request.env['SERVER_NAME']
121
- uri = "http://#{host_name}/#{controller_name}/"
122
- soap_action_base = "/#{controller_name}"
123
- xml = to_wsdl(self, uri, soap_action_base)
124
- send_data(xml, :type => 'text/xml', :disposition => 'inline')
140
+ options = { :type => 'text/xml', :disposition => 'inline' }
141
+ send_data(to_wsdl, options)
125
142
  rescue Exception => e
126
- log_error e unless logger.nil?
127
- render_text('', "500 #{e.message}")
143
+ log_error(e) unless logger.nil?
128
144
  end
129
145
  when :post
130
- render_text('', "500 POST not supported")
146
+ render_text('POST not supported', '500 POST not supported')
131
147
  end
132
148
  end
133
149
 
134
150
  private
135
- def to_wsdl(container, uri, soap_action_base)
136
- wsdl = ""
137
-
138
- web_service_dispatching_mode = container.web_service_dispatching_mode
139
- mapper = container.class.soap_mapper
140
- namespace = mapper.custom_namespace
141
- wsdl_service_name = namespace.split(/:/)[1]
142
-
143
- services = {}
144
- mapper.map_container_services(container) do |name, api, api_methods|
145
- services[name] = [api, api_methods]
151
+ def base_uri
152
+ host = @request ? (@request.env['HTTP_HOST'] || @request.env['SERVER_NAME']) : 'localhost'
153
+ 'http://%s/%s/' % [host, controller_name]
154
+ end
155
+
156
+ def to_wsdl
157
+ xml = ''
158
+ dispatching_mode = web_service_dispatching_mode
159
+ global_service_name = wsdl_service_name
160
+ namespace = 'urn:ActionWebService'
161
+ soap_action_base = "/#{controller_name}"
162
+
163
+ marshaler = WS::Marshaling::SoapMarshaler.new(namespace)
164
+ apis = {}
165
+ case dispatching_mode
166
+ when :direct
167
+ api = self.class.web_service_api
168
+ web_service_name = controller_class_name.sub(/Controller$/, '').underscore
169
+ apis[web_service_name] = [api, register_api(api, marshaler)]
170
+ when :delegated
171
+ self.class.web_services.each do |web_service_name, info|
172
+ service = web_service_object(web_service_name)
173
+ api = service.class.web_service_api
174
+ apis[web_service_name] = [api, register_api(api, marshaler)]
175
+ end
176
+ end
177
+ custom_types = []
178
+ apis.values.each do |api, bindings|
179
+ bindings.each do |b|
180
+ custom_types << b
181
+ end
146
182
  end
147
- custom_types = mapper.custom_types
148
-
149
-
150
- xm = Builder::XmlMarkup.new(:target => wsdl, :indent => 2)
183
+
184
+ xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
151
185
  xm.instruct!
152
-
153
- xm.definitions('name' => wsdl_service_name,
186
+ xm.definitions('name' => wsdl_service_name,
154
187
  'targetNamespace' => namespace,
155
188
  'xmlns:typens' => namespace,
156
189
  'xmlns:xsd' => XsdNs,
@@ -158,95 +191,96 @@ module ActionWebService # :nodoc:
158
191
  'xmlns:soapenc' => SoapEncodingNs,
159
192
  'xmlns:wsdl' => WsdlNs,
160
193
  'xmlns' => WsdlNs) do
161
-
162
- # Custom type XSD generation
194
+ # Generate XSD
163
195
  if custom_types.size > 0
164
196
  xm.types do
165
197
  xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
166
- custom_types.each do |klass, mapping|
198
+ custom_types.each do |binding|
167
199
  case
168
- when mapping.is_a?(ActionWebService::Protocol::Soap::SoapArrayMapping)
169
- xm.xsd(:complexType, 'name' => mapping.type_name) do
200
+ when binding.is_typed_array?
201
+ xm.xsd(:complexType, 'name' => binding.type_name) do
170
202
  xm.xsd(:complexContent) do
171
203
  xm.xsd(:restriction, 'base' => 'soapenc:Array') do
172
204
  xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
173
- 'wsdl:arrayType' => mapping.element_mapping.qualified_type_name + '[]')
205
+ 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
174
206
  end
175
207
  end
176
208
  end
177
- when mapping.is_a?(ActionWebService::Protocol::Soap::SoapMapping)
178
- xm.xsd(:complexType, 'name' => mapping.type_name) do
209
+ when binding.is_typed_struct?
210
+ xm.xsd(:complexType, 'name' => binding.type_name) do
179
211
  xm.xsd(:all) do
180
- mapping.each_attribute do |name, type_name|
181
- xm.xsd(:element, 'name' => name, 'type' => type_name)
212
+ binding.each_member do |name, spec|
213
+ b = marshaler.register_type(spec)
214
+ xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
182
215
  end
183
216
  end
184
217
  end
185
- else
186
- raise(WsdlError, "unsupported mapping type #{mapping.class.name}")
187
218
  end
188
219
  end
189
220
  end
190
221
  end
191
222
  end
192
-
193
- services.each do |service_name, service_values|
194
- service_api, api_methods = service_values
195
- # Parameter list message definitions
196
- api_methods.each do |method_name, method_signature|
223
+
224
+ # APIs
225
+ apis.each do |api_name, values|
226
+ api = values[0]
227
+ api.api_methods.each do |name, info|
197
228
  gen = lambda do |msg_name, direction|
198
229
  xm.message('name' => msg_name) do
199
230
  sym = nil
200
231
  if direction == :out
201
- if method_signature[:returns]
202
- xm.part('name' => 'return', 'type' => method_signature[:returns][0].qualified_type_name)
232
+ returns = info[:returns]
233
+ if returns
234
+ binding = marshaler.register_type(returns[0])
235
+ xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
203
236
  end
204
237
  else
205
- mapping_list = method_signature[:expects]
238
+ expects = info[:expects]
206
239
  i = 1
207
- mapping_list.each do |mapping|
208
- if mapping.is_a?(Hash)
209
- param_name = mapping.keys.shift
210
- mapping = mapping.values.shift
240
+ expects.each do |type|
241
+ if type.is_a?(Hash)
242
+ param_name = type.keys.shift
243
+ type = type.values.shift
211
244
  else
212
245
  param_name = "param#{i}"
213
246
  end
214
- xm.part('name' => param_name, 'type' => mapping.qualified_type_name)
247
+ binding = marshaler.register_type(type)
248
+ xm.part('name' => param_name, 'type' => binding.qualified_type_name('typens'))
215
249
  i += 1
216
- end if mapping_list
250
+ end if expects
217
251
  end
218
252
  end
219
253
  end
220
- public_name = service_api.public_api_method_name(method_name)
254
+ public_name = api.public_api_method_name(name)
221
255
  gen.call(public_name, :in)
222
256
  gen.call("#{public_name}Response", :out)
223
257
  end
224
-
225
- # Declare the port
226
- port_name = port_name_for(wsdl_service_name, service_name)
258
+
259
+ # Port
260
+ port_name = port_name_for(global_service_name, api_name)
227
261
  xm.portType('name' => port_name) do
228
- api_methods.each do |method_name, method_signature|
229
- public_name = service_api.public_api_method_name(method_name)
262
+ api.api_methods.each do |name, info|
263
+ public_name = api.public_api_method_name(name)
230
264
  xm.operation('name' => public_name) do
231
265
  xm.input('message' => "typens:#{public_name}")
232
266
  xm.output('message' => "typens:#{public_name}Response")
233
267
  end
234
268
  end
235
269
  end
236
-
237
- # Bind the port to SOAP
238
- binding_name = binding_name_for(wsdl_service_name, service_name)
270
+
271
+ # Bind it
272
+ binding_name = binding_name_for(global_service_name, api_name)
239
273
  xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
240
274
  xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
241
- api_methods.each do |method_name, method_signature|
242
- public_name = service_api.public_api_method_name(method_name)
275
+ api.api_methods.each do |name, info|
276
+ public_name = api.public_api_method_name(name)
243
277
  xm.operation('name' => public_name) do
244
278
  case web_service_dispatching_mode
245
- when :direct
279
+ when :direct, :layered
246
280
  soap_action = soap_action_base + "/api/" + public_name
247
281
  when :delegated
248
282
  soap_action = soap_action_base \
249
- + "/" + service_name.to_s \
283
+ + "/" + api_name.to_s \
250
284
  + "/" + public_name
251
285
  end
252
286
  xm.soap(:operation, 'soapAction' => soap_action)
@@ -266,34 +300,62 @@ module ActionWebService # :nodoc:
266
300
  end
267
301
  end
268
302
  end
269
-
270
- # Define the service
271
- xm.service('name' => "#{wsdl_service_name}Service") do
272
- services.each do |service_name, service_values|
273
- port_name = port_name_for(wsdl_service_name, service_name)
274
- binding_name = binding_name_for(wsdl_service_name, service_name)
303
+
304
+ # Define it
305
+ xm.service('name' => "#{global_service_name}Service") do
306
+ apis.each do |api_name, values|
307
+ port_name = port_name_for(global_service_name, api_name)
308
+ binding_name = binding_name_for(global_service_name, api_name)
275
309
  case web_service_dispatching_mode
276
310
  when :direct
277
311
  binding_target = 'api'
278
312
  when :delegated
279
- binding_target = service_name.to_s
313
+ binding_target = api_name.to_s
280
314
  end
281
315
  xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
282
- xm.soap(:address, 'location' => "#{uri}#{binding_target}")
316
+ xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
283
317
  end
284
318
  end
285
319
  end
286
320
  end
287
321
  end
288
322
 
289
- def port_name_for(wsdl_service_name, service_name)
290
- "#{wsdl_service_name}#{service_name.to_s.camelize}Port"
323
+ def port_name_for(global_service, service)
324
+ "#{global_service}#{service.to_s.camelize}Port"
291
325
  end
292
326
 
293
- def binding_name_for(wsdl_service_name, service_name)
294
- "#{wsdl_service_name}#{service_name.to_s.camelize}Binding"
327
+ def binding_name_for(global_service, service)
328
+ "#{global_service}#{service.to_s.camelize}Binding"
295
329
  end
296
- end
330
+
331
+ def register_api(api, marshaler)
332
+ bindings = {}
333
+ traverse_custom_types(api, marshaler) do |binding|
334
+ bindings[binding] = nil unless bindings.has_key?(binding.type_class)
335
+ end
336
+ bindings.keys
337
+ end
338
+
339
+ def traverse_custom_types(api, marshaler, &block)
340
+ api.api_methods.each do |name, info|
341
+ expects, returns = info[:expects], info[:returns]
342
+ expects.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if expects
343
+ returns.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if returns
344
+ end
345
+ end
346
+
347
+ def traverse_custom_type_spec(marshaler, spec, &block)
348
+ binding = marshaler.register_type(spec)
349
+ if binding.is_typed_struct?
350
+ binding.each_member do |name, member_spec|
351
+ traverse_custom_type_spec(marshaler, member_spec, &block)
352
+ end
353
+ elsif binding.is_typed_array?
354
+ traverse_custom_type_spec(marshaler, binding.element_binding.type_class, &block)
355
+ end
356
+ yield binding
357
+ end
358
+ end
297
359
  end
298
360
  end
299
361
  end