aboutsummaryrefslogtreecommitdiffstats
path: root/actionwebservice/lib/action_web_service
diff options
context:
space:
mode:
authorLeon Breedt <bitserf@gmail.com>2005-03-29 12:31:39 +0000
committerLeon Breedt <bitserf@gmail.com>2005-03-29 12:31:39 +0000
commitb94bd32f3116b469b48400382dbc964bf17994d1 (patch)
tree91752d7e0f49916e5ed2b9e015815c6dc06b4366 /actionwebservice/lib/action_web_service
parent715715aed443a027fccbac995cd5404eaeabaf53 (diff)
downloadrails-b94bd32f3116b469b48400382dbc964bf17994d1.tar.gz
rails-b94bd32f3116b469b48400382dbc964bf17994d1.tar.bz2
rails-b94bd32f3116b469b48400382dbc964bf17994d1.zip
first pass of web service scaffolding. add ability to quickly generate an
action pack request for a protocol, add missing log_error when we fail to parse protocol messages. add RDoc for scaffolding and functional testing. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1037 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionwebservice/lib/action_web_service')
-rw-r--r--actionwebservice/lib/action_web_service/api/base.rb21
-rw-r--r--actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb1
-rw-r--r--actionwebservice/lib/action_web_service/protocol/abstract.rb54
-rw-r--r--actionwebservice/lib/action_web_service/protocol/soap_protocol.rb8
-rw-r--r--actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb2
-rw-r--r--actionwebservice/lib/action_web_service/scaffolding.rb181
-rw-r--r--actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml65
-rw-r--r--actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml6
-rw-r--r--actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml20
-rw-r--r--actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml26
-rw-r--r--actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb3
11 files changed, 382 insertions, 5 deletions
diff --git a/actionwebservice/lib/action_web_service/api/base.rb b/actionwebservice/lib/action_web_service/api/base.rb
index e440a8b1bd..03e406cfc3 100644
--- a/actionwebservice/lib/action_web_service/api/base.rb
+++ b/actionwebservice/lib/action_web_service/api/base.rb
@@ -284,10 +284,31 @@ module ActionWebService # :nodoc:
marshaler.cast_inbound_recursive(return_value, @returns[0])
end
+ # String representation of this method
+ def to_s
+ fqn = ""
+ fqn << (@returns ? (friendly_param(@returns[0], nil) + " ") : "void ")
+ fqn << "#{@public_name}("
+ if @expects
+ i = 0
+ fqn << @expects.map{ |p| friendly_param(p, i+= 1) }.join(", ")
+ end
+ fqn << ")"
+ fqn
+ end
+
private
def response_name(encoder)
encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name
end
+
+ def friendly_param(spec, i)
+ name = param_name(spec, i)
+ type = param_type(spec)
+ spec = spec.values.first if spec.is_a?(Hash)
+ type = spec.is_a?(Array) ? (type.to_s + "[]") : type.to_s
+ i ? (type + " " + name) : type
+ end
end
end
end
diff --git a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
index 3c9a6e2c78..0140039c49 100644
--- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
+++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
@@ -63,6 +63,7 @@ module ActionWebService # :nodoc:
end
else
exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
+ log_error(exception) unless logger.nil?
send_web_service_error_response(request, exception)
end
rescue Exception => e
diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb
index ed50a6ffde..0ff4feef84 100644
--- a/actionwebservice/lib/action_web_service/protocol/abstract.rb
+++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb
@@ -7,10 +7,64 @@ module ActionWebService # :nodoc:
attr :marshaler
attr :encoder
+ def unmarshal_request(ap_request)
+ end
+
def marshal_response(method, return_value)
body = method.encode_rpc_response(marshaler, encoder, return_value)
Response.new(body, 'text/xml')
end
+
+ def protocol_client(api, protocol_name, endpoint_uri, options)
+ end
+
+ def create_action_pack_request(service_name, public_method_name, raw_body, options={})
+ klass = options[:request_class] || SimpleActionPackRequest
+ request = klass.new
+ request.request_parameters['action'] = service_name.to_s
+ request.env['RAW_POST_DATA'] = raw_body
+ request.env['REQUEST_METHOD'] = 'POST'
+ request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
+ request
+ end
+ end
+
+ class SimpleActionPackRequest < ActionController::AbstractRequest
+ def initialize
+ @env = {}
+ @qparams = {}
+ @rparams = {}
+ @cookies = {}
+ reset_session
+ end
+
+ def query_parameters
+ @qparams
+ end
+
+ def request_parameters
+ @rparams
+ end
+
+ def env
+ @env
+ end
+
+ def host
+ ''
+ end
+
+ def cookies
+ @cookies
+ end
+
+ def session
+ @session
+ end
+
+ def reset_session
+ @session = {}
+ end
end
class Request # :nodoc:
diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
index 253812b5e2..5e56748ae3 100644
--- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
+++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
@@ -20,11 +20,17 @@ module ActionWebService # :nodoc:
Request.new(self, method_name, params, service_name)
end
- def protocol_client(api, protocol_name, endpoint_uri, options)
+ def protocol_client(api, protocol_name, endpoint_uri, options={})
return nil unless protocol_name == :soap
ActionWebService::Client::Soap.new(api, endpoint_uri, options)
end
+ def create_action_pack_request(service_name, public_method_name, raw_body, options={})
+ request = super
+ request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
+ request
+ end
+
private
def has_valid_soap_action?(request)
return nil unless request.method == :post
diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
index 37ef16c12f..f8ff12cfa3 100644
--- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
+++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
@@ -18,7 +18,7 @@ module ActionWebService # :nodoc:
Request.new(self, method_name, params, service_name)
end
- def protocol_client(api, protocol_name, endpoint_uri, options)
+ def protocol_client(api, protocol_name, endpoint_uri, options={})
return nil unless protocol_name == :xmlrpc
ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
end
diff --git a/actionwebservice/lib/action_web_service/scaffolding.rb b/actionwebservice/lib/action_web_service/scaffolding.rb
new file mode 100644
index 0000000000..21790c101b
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/scaffolding.rb
@@ -0,0 +1,181 @@
+require 'ostruct'
+require 'uri'
+
+module ActionWebService
+ module Scaffolding # :nodoc:
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ end
+
+ # Web service invocation scaffolding provides a way to quickly invoke web service methods in a controller. The
+ # generated scaffold actions have default views to let you enter the method parameters and view the
+ # results.
+ #
+ # Example:
+ #
+ # class ApiController < ActionController
+ # web_service_scaffold :invoke
+ # end
+ #
+ # This example generates an +invoke+ action in the +ApiController+ that you can navigate to from
+ # your browser, select the API method, enter its parameters, and perform the invocation.
+ #
+ # If you want to customize the default views, create the following views in "app/views":
+ #
+ # * <tt>action_name/methods.rhtml</tt>
+ # * <tt>action_name/parameters.rhtml</tt>
+ # * <tt>action_name/result.rhtml</tt>
+ # * <tt>action_name/layout.rhtml</tt>
+ #
+ # Where <tt>action_name</tt> is the name of the action you gave to ClassMethods#web_service_scaffold.
+ #
+ # You can use the default views in <tt>RAILS_DIR/lib/action_web_service/templates/scaffolds</tt> as
+ # a guide.
+ module ClassMethods
+ # Generates web service invocation scaffolding for the current controller. The given action name
+ # can then be used as the entry point for invoking API methods from a web browser.
+ def web_service_scaffold(action_name)
+ add_template_helper(Helpers)
+ module_eval <<-END, __FILE__, __LINE__
+ def #{action_name}
+ if @request.method == :get
+ setup_#{action_name}_assigns
+ render_#{action_name}_scaffold 'methods'
+ end
+ end
+
+ def #{action_name}_method_params
+ if @request.method == :get
+ setup_#{action_name}_assigns
+ render_#{action_name}_scaffold 'parameters'
+ end
+ end
+
+ def #{action_name}_submit
+ if @request.method == :post
+ setup_#{action_name}_assigns
+ protocol_name = @params['protocol'] ? @params['protocol'].to_sym : :soap
+ case protocol_name
+ when :soap
+ protocol = Protocol::Soap::SoapProtocol.new
+ when :xmlrpc
+ protocol = Protocol::XmlRpc::XmlRpcProtocol.new
+ end
+ @method_request_xml = @scaffold_method.encode_rpc_call(protocol.marshaler, protocol.encoder, @params['method_params'].dup)
+ cgi = @request.cgi
+ @request = protocol.create_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
+ dispatch_web_service_request
+ @method_response_xml = @response.body
+ @method_return_value = protocol.marshaler.unmarshal(protocol.encoder.decode_rpc_response(@method_response_xml)[1]).value
+ add_instance_variables_to_assigns
+ @response = ::ActionController::CgiResponse.new(cgi)
+ @performed_render = false
+ render_#{action_name}_scaffold 'result'
+ end
+ end
+
+ private
+ def setup_#{action_name}_assigns
+ @scaffold_class = self.class
+ @scaffold_action_name = "#{action_name}"
+ @scaffold_container = WebServiceModel::Container.new(self)
+ if @params['service'] && @params['method']
+ @scaffold_service = @scaffold_container.services.find{ |x| x.name == @params['service'] }
+ @scaffold_method = @scaffold_service.api_methods[@params['method']]
+ end
+ add_instance_variables_to_assigns
+ end
+
+ def render_#{action_name}_scaffold(action)
+ customized_template = "\#{self.class.controller_path}/#{action_name}/\#{action}"
+ default_template = scaffold_path(action)
+ @content_for_layout = template_exists?(customized_template) ? @template.render_file(customized_template) : @template.render_file(default_template, false)
+ self.active_layout ? render_file(self.active_layout, "200 OK", true) : render_file(scaffold_path("layout"))
+ end
+
+ def scaffold_path(template_name)
+ File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
+ end
+ END
+ end
+ end
+
+ module Helpers # :nodoc:
+ def method_parameter_input_fields(method, param_spec, i)
+ klass = method.param_class(param_spec)
+ unless WS::BaseTypes.base_type?(klass)
+ name = method.param_name(param_spec, i)
+ raise "Parameter #{name}: Structured/array types not supported in scaffolding input fields yet"
+ end
+ type_name = method.param_type(param_spec)
+ field_name = "method_params[]"
+ case type_name
+ when :int
+ text_field_tag field_name
+ when :string
+ text_field_tag field_name
+ when :bool
+ radio_button_tag field_name, "True"
+ radio_button_tag field_name, "False"
+ when :float
+ text_field_tag field_name
+ when :time
+ select_datetime Time.now, 'name' => field_name
+ when :date
+ select_date Date.today, 'name' => field_name
+ end
+ end
+
+ def service_method_list(service)
+ action = @scaffold_action_name + '_method_params'
+ methods = service.api_methods_full.map do |desc, name|
+ content_tag("li", link_to(desc, :action => action, :service => service.name, :method => name))
+ end
+ content_tag("ul", methods.join("\n"))
+ end
+ end
+
+ module WebServiceModel # :nodoc:
+ class Container # :nodoc:
+ attr :services
+
+ def initialize(real_container)
+ @real_container = real_container
+ @services = []
+ if @real_container.class.web_service_dispatching_mode == :direct
+ @services << Service.new(@real_container.controller_name, @real_container)
+ else
+ @real_container.class.web_services.each do |name|
+ @services << Service.new(name, @real_container.instance_eval{ web_service_object(name) })
+ end
+ end
+ end
+ end
+
+ class Service # :nodoc:
+ attr :name
+ attr :object
+ attr :api
+ attr :api_methods
+ attr :api_methods_full
+
+ def initialize(name, real_service)
+ @name = name.to_s
+ @object = real_service
+ @api = @object.class.web_service_api
+ @api_methods = {}
+ @api_methods_full = []
+ @api.api_methods.each do |name, method|
+ @api_methods[method.public_name.to_s] = method
+ @api_methods_full << [method.to_s, method.public_name.to_s]
+ end
+ end
+
+ def to_s
+ self.name.camelize
+ end
+ end
+ end
+ end
+end
diff --git a/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml b/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml
new file mode 100644
index 0000000000..1cd2e3042a
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/templates/scaffolds/layout.rhtml
@@ -0,0 +1,65 @@
+<html>
+<head>
+ <title><%= @scaffold_class.wsdl_service_name %> Web Service</title>
+ <style>
+ body { background-color: #fff; color: #333; }
+
+ body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+
+ .fieldWithErrors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+ }
+
+ #ErrorExplanation {
+ width: 400px;
+ border: 2px solid #red;
+ padding: 7px;
+ padding-bottom: 12px;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+ }
+
+ #ErrorExplanation h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px;
+ background-color: #c00;
+ color: #fff;
+ }
+
+ #ErrorExplanation p {
+ color: #333;
+ margin-bottom: 0;
+ padding: 5px;
+ }
+
+ #ErrorExplanation ul li {
+ font-size: 12px;
+ list-style: square;
+ }
+ </style>
+</head>
+<body>
+
+<%= @content_for_layout %>
+
+</body>
+</html>
diff --git a/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml b/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml
new file mode 100644
index 0000000000..60dfe23f07
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml
@@ -0,0 +1,6 @@
+<% @scaffold_container.services.each do |service| %>
+
+ <h4>API Methods for <%= service %></h4>
+ <%= service_method_list(service) %>
+
+<% end %>
diff --git a/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml b/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
new file mode 100644
index 0000000000..0516738da6
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
@@ -0,0 +1,20 @@
+<h4>Method Parameters for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4>
+
+<%= form_tag :action => @scaffold_action_name + '_submit' %>
+<%= hidden_field_tag "service", @scaffold_service.name %>
+<%= hidden_field_tag "method", @scaffold_method.public_name %>
+
+<% i = 0 %>
+<% @scaffold_method.expects.each do |spec| %>
+ <p>
+ <label for="method_params[]"><%= @scaffold_method.param_name(spec, i).camelize %></label><br />
+ <%= method_parameter_input_fields(@scaffold_method, spec, i) %>
+ </p>
+ <% i += 1 %>
+<% end %>
+
+<%= submit_tag "Invoke" %>
+
+<p>
+<%= link_to "Back", :action => @scaffold_action_name %>
+</p>
diff --git a/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml b/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml
new file mode 100644
index 0000000000..4475abff4a
--- /dev/null
+++ b/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml
@@ -0,0 +1,26 @@
+<h4>Method Invocation Result for <em><%= @scaffold_service %>#<%= @scaffold_method.public_name %></em></h4>
+
+<p>
+<strong>Return Value:</strong><br />
+<pre>
+<%= h @method_return_value.inspect %>
+</pre>
+</p>
+
+<p>
+<strong>Request XML:</strong><br />
+<pre>
+<%= h @method_request_xml %>
+</pre>
+</p>
+
+<p>
+<strong>Response XML:</strong><br />
+<pre>
+<%= h @method_response_xml %>
+</pre>
+</p>
+
+<p>
+<%= link_to "Back", :action => @scaffold_action_name + '_method_params', :method => @scaffold_method.public_name, :service => @scaffold_service.name %>
+</p>
diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
index 122f62434f..4876c08705 100644
--- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
+++ b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
@@ -2,9 +2,6 @@ require 'xmlrpc/marshal'
module WS
module Encoding
- class XmlRpcError < WSError
- end
-
class XmlRpcEncoding < AbstractEncoding
def encode_rpc_call(method_name, params)
XMLRPC::Marshal.dump_call(method_name, *params)