actionwebservice 0.7.1 → 0.8.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 (31) hide show
  1. data/CHANGELOG +33 -0
  2. data/README +25 -6
  3. data/Rakefile +7 -7
  4. data/TODO +27 -6
  5. data/lib/action_web_service.rb +1 -0
  6. data/lib/action_web_service/api.rb +8 -11
  7. data/lib/action_web_service/casting.rb +8 -1
  8. data/lib/action_web_service/client/soap_client.rb +7 -8
  9. data/lib/action_web_service/container/action_controller_container.rb +8 -8
  10. data/lib/action_web_service/container/delegated_container.rb +1 -1
  11. data/lib/action_web_service/dispatcher/abstract.rb +13 -7
  12. data/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +41 -41
  13. data/lib/action_web_service/protocol/abstract.rb +5 -2
  14. data/lib/action_web_service/protocol/discovery.rb +2 -2
  15. data/lib/action_web_service/protocol/soap_protocol.rb +35 -9
  16. data/lib/action_web_service/protocol/soap_protocol/marshaler.rb +54 -14
  17. data/lib/action_web_service/protocol/xmlrpc_protocol.rb +11 -3
  18. data/lib/action_web_service/scaffolding.rb +68 -40
  19. data/lib/action_web_service/support/signature_types.rb +39 -11
  20. data/lib/action_web_service/templates/scaffolds/layout.rhtml +1 -1
  21. data/lib/action_web_service/templates/scaffolds/parameters.rhtml +4 -2
  22. data/lib/action_web_service/test_invoke.rb +4 -7
  23. data/test/abstract_dispatcher.rb +52 -13
  24. data/test/api_test.rb +2 -2
  25. data/test/casting_test.rb +6 -2
  26. data/test/dispatcher_action_controller_soap_test.rb +24 -9
  27. data/test/dispatcher_action_controller_xmlrpc_test.rb +1 -1
  28. data/test/invocation_test.rb +1 -1
  29. data/test/scaffolded_controller_test.rb +1 -1
  30. data/test/test_invoke_test.rb +11 -9
  31. metadata +6 -6
@@ -4,6 +4,9 @@ module ActionWebService # :nodoc:
4
4
  end
5
5
 
6
6
  class AbstractProtocol # :nodoc:
7
+ def setup(controller)
8
+ end
9
+
7
10
  def decode_action_pack_request(action_pack_request)
8
11
  end
9
12
 
@@ -17,7 +20,7 @@ module ActionWebService # :nodoc:
17
20
  request
18
21
  end
19
22
 
20
- def decode_request(raw_request, service_name, protocol_options=nil)
23
+ def decode_request(raw_request, service_name, protocol_options={})
21
24
  end
22
25
 
23
26
  def encode_request(method_name, params, param_types)
@@ -26,7 +29,7 @@ module ActionWebService # :nodoc:
26
29
  def decode_response(raw_response)
27
30
  end
28
31
 
29
- def encode_response(method_name, return_value, return_type)
32
+ def encode_response(method_name, return_value, return_type, protocol_options={})
30
33
  end
31
34
 
32
35
  def protocol_client(api, protocol_name, endpoint_uri, options)
@@ -16,7 +16,7 @@ module ActionWebService # :nodoc:
16
16
  private
17
17
  def discover_web_service_request(action_pack_request)
18
18
  (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
19
- protocol = protocol.new
19
+ protocol = protocol.create(self)
20
20
  request = protocol.decode_action_pack_request(action_pack_request)
21
21
  return request unless request.nil?
22
22
  end
@@ -25,7 +25,7 @@ module ActionWebService # :nodoc:
25
25
 
26
26
  def create_web_service_client(api, protocol_name, endpoint_uri, options)
27
27
  (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
28
- protocol = protocol.new
28
+ protocol = protocol.create(self)
29
29
  client = protocol.protocol_client(api, protocol_name, endpoint_uri, options)
30
30
  return client unless client.nil?
31
31
  end
@@ -1,4 +1,5 @@
1
1
  require 'action_web_service/protocol/soap_protocol/marshaler'
2
+ require 'soap/streamHandler'
2
3
 
3
4
  module ActionWebService # :nodoc:
4
5
  module Protocol # :nodoc:
@@ -6,17 +7,31 @@ module ActionWebService # :nodoc:
6
7
  def self.included(base)
7
8
  base.register_protocol(SoapProtocol)
8
9
  base.class_inheritable_option(:wsdl_service_name)
10
+ base.class_inheritable_option(:wsdl_namespace)
9
11
  end
10
12
 
11
13
  class SoapProtocol < AbstractProtocol # :nodoc:
12
- def marshaler
13
- @marshaler ||= SoapMarshaler.new
14
+ DefaultEncoding = 'utf-8'
15
+
16
+ attr :marshaler
17
+
18
+ def initialize(namespace=nil)
19
+ namespace ||= 'urn:ActionWebService'
20
+ @marshaler = SoapMarshaler.new namespace
21
+ end
22
+
23
+ def self.create(controller)
24
+ SoapProtocol.new(controller.wsdl_namespace)
14
25
  end
15
26
 
16
27
  def decode_action_pack_request(action_pack_request)
17
28
  return nil unless soap_action = has_valid_soap_action?(action_pack_request)
18
29
  service_name = action_pack_request.parameters['action']
19
- protocol_options = { :soap_action => soap_action }
30
+ charset = parse_charset(action_pack_request.env['HTTP_CONTENT_TYPE'])
31
+ protocol_options = {
32
+ :soap_action => soap_action,
33
+ :charset => charset
34
+ }
20
35
  decode_request(action_pack_request.raw_post, service_name, protocol_options)
21
36
  end
22
37
 
@@ -26,8 +41,9 @@ module ActionWebService # :nodoc:
26
41
  request
27
42
  end
28
43
 
29
- def decode_request(raw_request, service_name, protocol_options=nil)
30
- envelope = SOAP::Processor.unmarshal(raw_request)
44
+ def decode_request(raw_request, service_name, protocol_options={})
45
+ charset = protocol_options[:charset] || DefaultEncoding
46
+ envelope = SOAP::Processor.unmarshal(raw_request, :charset => charset)
31
47
  unless envelope
32
48
  raise ProtocolError, "Failed to parse SOAP request message"
33
49
  end
@@ -39,7 +55,7 @@ module ActionWebService # :nodoc:
39
55
 
40
56
  def encode_request(method_name, params, param_types)
41
57
  param_types.each{ |type| marshaler.register_type(type) } if param_types
42
- qname = XSD::QName.new(marshaler.type_namespace, method_name)
58
+ qname = XSD::QName.new(marshaler.namespace, method_name)
43
59
  param_def = []
44
60
  if param_types
45
61
  params = param_types.zip(params).map do |type, param|
@@ -66,12 +82,12 @@ module ActionWebService # :nodoc:
66
82
  [method_name, return_value]
67
83
  end
68
84
 
69
- def encode_response(method_name, return_value, return_type)
85
+ def encode_response(method_name, return_value, return_type, protocol_options={})
70
86
  if return_type
71
87
  return_binding = marshaler.register_type(return_type)
72
88
  marshaler.annotate_arrays(return_binding, return_value)
73
89
  end
74
- qname = XSD::QName.new(marshaler.type_namespace, method_name)
90
+ qname = XSD::QName.new(marshaler.namespace, method_name)
75
91
  if return_value.nil?
76
92
  response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
77
93
  else
@@ -93,7 +109,8 @@ module ActionWebService # :nodoc:
93
109
  end
94
110
  end
95
111
  envelope = create_soap_envelope(response)
96
- Response.new(SOAP::Processor.marshal(envelope), 'text/xml', return_value)
112
+ charset = protocol_options[:charset] || DefaultEncoding
113
+ Response.new(SOAP::Processor.marshal(envelope, :charset => charset), "text/xml; charset=#{charset}", return_value)
97
114
  end
98
115
 
99
116
  def protocol_client(api, protocol_name, endpoint_uri, options={})
@@ -121,6 +138,15 @@ module ActionWebService # :nodoc:
121
138
  soap_action
122
139
  end
123
140
 
141
+ def parse_charset(content_type)
142
+ return DefaultEncoding if content_type.nil?
143
+ if /^text\/xml(?:\s*;\s*charset=([^"]+|"[^"]+"))$/i =~ content_type
144
+ $1
145
+ else
146
+ DefaultEncoding
147
+ end
148
+ end
149
+
124
150
  def create_soap_envelope(body)
125
151
  header = SOAP::SOAPHeader.new
126
152
  body = SOAP::SOAPBody.new(body)
@@ -3,14 +3,28 @@ require 'soap/mapping'
3
3
  module ActionWebService
4
4
  module Protocol
5
5
  module Soap
6
+ # Workaround for SOAP4R return values changing
7
+ class Registry < SOAP::Mapping::Registry
8
+ if SOAP::Version >= "1.5.4"
9
+ def find_mapped_soap_class(obj_class)
10
+ return @map.instance_eval { @obj2soap[obj_class][0] }
11
+ end
12
+
13
+ def find_mapped_obj_class(soap_class)
14
+ return @map.instance_eval { @soap2obj[soap_class][0] }
15
+ end
16
+ end
17
+ end
18
+
6
19
  class SoapMarshaler
7
- attr :type_namespace
20
+ attr :namespace
8
21
  attr :registry
9
22
 
10
- def initialize(type_namespace=nil)
11
- @type_namespace = type_namespace || 'urn:ActionWebService'
12
- @registry = SOAP::Mapping::Registry.new
23
+ def initialize(namespace=nil)
24
+ @namespace = namespace || 'urn:ActionWebService'
25
+ @registry = Registry.new
13
26
  @type2binding = {}
27
+ register_static_factories
14
28
  end
15
29
 
16
30
  def soap_to_ruby(obj)
@@ -32,7 +46,7 @@ module ActionWebService
32
46
  qname ||= soap_base_type_name(mapping[0])
33
47
  type_binding = SoapBinding.new(self, qname, type_type, mapping)
34
48
  else
35
- qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
49
+ qname = XSD::QName.new(@namespace, soap_type_name(type_class.name))
36
50
  @registry.add(type_class,
37
51
  SOAP::SOAPStruct,
38
52
  typed_struct_factory(type_class),
@@ -43,14 +57,8 @@ module ActionWebService
43
57
 
44
58
  array_binding = nil
45
59
  if type.array?
46
- array_mapping = @registry.find_mapped_soap_class(Array) rescue nil
47
- if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil?
48
- @registry.set(Array,
49
- SOAP::SOAPArray,
50
- SoapTypedArrayFactory.new)
51
- array_mapping = @registry.find_mapped_soap_class(Array)
52
- end
53
- qname = XSD::QName.new(@type_namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
60
+ array_mapping = @registry.find_mapped_soap_class(Array)
61
+ qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
54
62
  array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding)
55
63
  end
56
64
 
@@ -80,7 +88,7 @@ module ActionWebService
80
88
  def typed_struct_factory(type_class)
81
89
  if Object.const_defined?('ActiveRecord')
82
90
  if type_class.ancestors.include?(ActiveRecord::Base)
83
- qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
91
+ qname = XSD::QName.new(@namespace, soap_type_name(type_class.name))
84
92
  type_class.instance_variable_set('@qname', qname)
85
93
  return SoapActiveRecordStructFactory.new
86
94
  end
@@ -104,6 +112,21 @@ module ActionWebService
104
112
  def soap_type_name(type_name)
105
113
  type_name.gsub(/::/, '..')
106
114
  end
115
+
116
+ def register_static_factories
117
+ @registry.add(ActionWebService::Base64,
118
+ SOAP::SOAPBase64,
119
+ SoapBase64Factory.new,
120
+ nil)
121
+ mapping = @registry.find_mapped_soap_class(ActionWebService::Base64)
122
+ @type2binding[ActionWebService::Base64] =
123
+ SoapBinding.new(self, SOAP::SOAPBase64::Type,
124
+ ActionWebService::Base64, mapping)
125
+ @registry.add(Array,
126
+ SOAP::SOAPArray,
127
+ SoapTypedArrayFactory.new,
128
+ nil)
129
+ end
107
130
  end
108
131
 
109
132
  class SoapBinding
@@ -130,6 +153,7 @@ module ActionWebService
130
153
  else
131
154
  ns = XSD::NS.new
132
155
  ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
156
+ ns.assign(SOAP::EncodingNamespace, "soapenc")
133
157
  xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
134
158
  return ns.name(XSD::AnyTypeName) unless xsd_klass
135
159
  ns.name(xsd_klass.const_get('Type'))
@@ -192,6 +216,22 @@ module ActionWebService
192
216
  end
193
217
  end
194
218
 
219
+ class SoapBase64Factory < SOAP::Mapping::Factory
220
+ def obj2soap(soap_class, obj, info, map)
221
+ unless obj.is_a?(ActionWebService::Base64)
222
+ return nil
223
+ end
224
+ return soap_class.new(obj)
225
+ end
226
+
227
+ def soap2obj(obj_class, node, info, map)
228
+ unless node.type == SOAP::SOAPBase64::Type
229
+ return false
230
+ end
231
+ return true, obj_class.new(node.string)
232
+ end
233
+ end
234
+
195
235
  end
196
236
  end
197
237
  end
@@ -1,7 +1,9 @@
1
1
  require 'xmlrpc/marshal'
2
2
 
3
- class XMLRPC::FaultException
4
- alias :message :faultString
3
+ module XMLRPC # :nodoc:
4
+ class FaultException # :nodoc:
5
+ alias :message :faultString
6
+ end
5
7
  end
6
8
 
7
9
  module ActionWebService # :nodoc:
@@ -12,6 +14,10 @@ module ActionWebService # :nodoc:
12
14
  end
13
15
 
14
16
  class XmlRpcProtocol < AbstractProtocol # :nodoc:
17
+ def self.create(controller)
18
+ XmlRpcProtocol.new
19
+ end
20
+
15
21
  def decode_action_pack_request(action_pack_request)
16
22
  service_name = action_pack_request.parameters['action']
17
23
  decode_request(action_pack_request.raw_post, service_name)
@@ -34,7 +40,7 @@ module ActionWebService # :nodoc:
34
40
  [nil, XMLRPC::Marshal.load_response(raw_response)]
35
41
  end
36
42
 
37
- def encode_response(method_name, return_value, return_type)
43
+ def encode_response(method_name, return_value, return_type, protocol_options={})
38
44
  return_value = true if return_value.nil?
39
45
  if return_type
40
46
  return_value = value_to_xmlrpc_wire_format(return_value, return_type)
@@ -67,6 +73,8 @@ module ActionWebService # :nodoc:
67
73
  struct[key.to_s] = member_value
68
74
  end
69
75
  struct
76
+ elsif value.is_a?(ActionWebService::Base64)
77
+ XMLRPC::Base64.new(value)
70
78
  elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
71
79
  XMLRPC::FaultException.new(2, value.message)
72
80
  else
@@ -1,5 +1,3 @@
1
- require 'ostruct'
2
- require 'uri'
3
1
  require 'benchmark'
4
2
  require 'pathname'
5
3
 
@@ -44,39 +42,48 @@ module ActionWebService
44
42
  add_template_helper(Helpers)
45
43
  module_eval <<-END, __FILE__, __LINE__
46
44
  def #{action_name}
47
- if @request.method == :get
45
+ if request.method == :get
48
46
  setup_invocation_assigns
49
47
  render_invocation_scaffold 'methods'
50
48
  end
51
49
  end
52
50
 
53
51
  def #{action_name}_method_params
54
- if @request.method == :get
52
+ if request.method == :get
55
53
  setup_invocation_assigns
56
54
  render_invocation_scaffold 'parameters'
57
55
  end
58
56
  end
59
57
 
60
58
  def #{action_name}_submit
61
- if @request.method == :post
59
+ if request.method == :post
62
60
  setup_invocation_assigns
63
- protocol_name = @params['protocol'] ? @params['protocol'].to_sym : :soap
61
+ protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap
64
62
  case protocol_name
65
63
  when :soap
66
- @protocol = Protocol::Soap::SoapProtocol.new
64
+ @protocol = Protocol::Soap::SoapProtocol.create(self)
67
65
  when :xmlrpc
68
- @protocol = Protocol::XmlRpc::XmlRpcProtocol.new
66
+ @protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self)
69
67
  end
70
- @invocation_cgi = @request.respond_to?(:cgi) ? @request.cgi : nil
68
+ @invocation_cgi = request.respond_to?(:cgi) ? request.cgi : nil
71
69
  bm = Benchmark.measure do
72
70
  @protocol.register_api(@scaffold_service.api)
73
- params = @params['method_params'] ? @params['method_params'].dup : nil
71
+ post_params = params['method_params'] ? params['method_params'].dup : nil
72
+ params = []
73
+ if @scaffold_method.expects
74
+ @scaffold_method.expects.length.times do |i|
75
+ params << post_params[i.to_s]
76
+ end
77
+ end
74
78
  params = @scaffold_method.cast_expects(params)
75
79
  method_name = public_method_name(@scaffold_service.name, @scaffold_method.public_name)
76
80
  @method_request_xml = @protocol.encode_request(method_name, params, @scaffold_method.expects)
77
81
  new_request = @protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
78
82
  prepare_request(new_request, @scaffold_service.name, @scaffold_method.public_name)
79
83
  @request = new_request
84
+ if @scaffold_container.dispatching_mode != :direct
85
+ request.parameters['action'] = @scaffold_service.name
86
+ end
80
87
  dispatch_web_service_request
81
88
  @method_response_xml = @response.body
82
89
  method_name, obj = @protocol.decode_response(@method_response_xml)
@@ -95,9 +102,9 @@ module ActionWebService
95
102
  @scaffold_class = self.class
96
103
  @scaffold_action_name = "#{action_name}"
97
104
  @scaffold_container = WebServiceModel::Container.new(self)
98
- if @params['service'] && @params['method']
99
- @scaffold_service = @scaffold_container.services.find{ |x| x.name == @params['service'] }
100
- @scaffold_method = @scaffold_service.api_methods[@params['method']]
105
+ if params['service'] && params['method']
106
+ @scaffold_service = @scaffold_container.services.find{ |x| x.name == params['service'] }
107
+ @scaffold_method = @scaffold_service.api_methods[params['method']]
101
108
  end
102
109
  add_instance_variables_to_assigns
103
110
  end
@@ -110,17 +117,17 @@ module ActionWebService
110
117
  end
111
118
 
112
119
  def scaffold_path(template_name)
113
- Pathname.new(File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml").realpath.to_s
120
+ File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
114
121
  end
115
122
 
116
123
  def reset_invocation_response
117
- template = @response.template
124
+ template = response.template
118
125
  if @invocation_cgi
119
126
  @response = ::ActionController::CgiResponse.new(@invocation_cgi)
120
127
  else
121
128
  @response = ::ActionController::TestResponse.new
122
129
  end
123
- @response.template = template
130
+ response.template = template
124
131
  @performed_render = false
125
132
  end
126
133
 
@@ -132,10 +139,10 @@ module ActionWebService
132
139
  end
133
140
  end
134
141
 
135
- def prepare_request(request, service_name, method_name)
136
- request.parameters.update(@request.parameters)
142
+ def prepare_request(new_request, service_name, method_name)
143
+ new_request.parameters.update(request.parameters)
137
144
  if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
138
- request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}"
145
+ new_request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}"
139
146
  end
140
147
  end
141
148
 
@@ -156,30 +163,49 @@ module ActionWebService
156
163
  end
157
164
 
158
165
  module Helpers # :nodoc:
159
- def method_parameter_input_fields(method, type)
160
- name = type.name.to_s
161
- type_name = type.type
162
- unless type_name.is_a?(Symbol)
163
- raise "Parameter #{name}: Structured/array types not supported in scaffolding input fields yet"
166
+ def method_parameter_input_fields(method, type, field_name_base)
167
+ if type.array?
168
+ return content_tag('em', "Typed array input fields not supported yet (#{type.name})")
164
169
  end
165
- field_name = "method_params[]"
166
- case type_name
167
- when :int
168
- text_field_tag field_name
169
- when :string
170
- text_field_tag field_name
171
- when :bool
172
- radio_button_tag field_name, "True"
173
- radio_button_tag field_name, "False"
174
- when :float
175
- text_field_tag field_name
176
- when :time
177
- select_datetime Time.now, 'name' => field_name
178
- when :date
179
- select_date Date.today, 'name' => field_name
170
+ if type.structured?
171
+ parameters = ""
172
+ type.each_member do |member_name, member_type|
173
+ label = method_parameter_label(member_name, member_type)
174
+ nested_content = method_parameter_input_fields(
175
+ method,
176
+ member_type,
177
+ field_name_base + '[' + member_name.to_s + ']')
178
+ if member_type.custom?
179
+ parameters << content_tag('li', label)
180
+ parameters << content_tag('ul', nested_content)
181
+ else
182
+ parameters << content_tag('li', label + ' ' + nested_content)
183
+ end
184
+ end
185
+ content_tag('ul', parameters)
186
+ else
187
+ case type.type
188
+ when :int
189
+ text_field_tag field_name_base
190
+ when :string
191
+ text_field_tag field_name_base
192
+ when :bool
193
+ radio_button_tag(field_name_base, "true") + " True" +
194
+ radio_button_tag(field_name_base, "false") + "False"
195
+ when :float
196
+ text_field_tag field_name_base
197
+ when :time
198
+ select_datetime Time.now, 'name' => field_name_base
199
+ when :date
200
+ select_date Date.today, 'name' => field_name_base
201
+ end
180
202
  end
181
203
  end
182
204
 
205
+ def method_parameter_label(name, type)
206
+ name.to_s.capitalize + ' (' + type.human_name(false) + ')'
207
+ end
208
+
183
209
  def service_method_list(service)
184
210
  action = @scaffold_action_name + '_method_params'
185
211
  methods = service.api_methods_full.map do |desc, name|
@@ -192,11 +218,13 @@ module ActionWebService
192
218
  module WebServiceModel # :nodoc:
193
219
  class Container # :nodoc:
194
220
  attr :services
221
+ attr :dispatching_mode
195
222
 
196
223
  def initialize(real_container)
197
224
  @real_container = real_container
225
+ @dispatching_mode = @real_container.class.web_service_dispatching_mode
198
226
  @services = []
199
- if @real_container.class.web_service_dispatching_mode == :direct
227
+ if @dispatching_mode == :direct
200
228
  @services << Service.new(@real_container.controller_name, @real_container)
201
229
  else
202
230
  @real_container.class.web_services.each do |name, obj|