actionwebservice 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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