-
Notifications
You must be signed in to change notification settings - Fork 62
RestfulX XML over HTTP Provider API
At the core of the RestfulX framework is an assumption that REST
and by implication RESTful CRUD
is a good way to develop networked applications. As a result XML-over-HTTP providers are assumed to expose 5 standard REST
methods. Let’s use an example to illustrate what this means. If we have a model (a.k.a resource) called project
, the following provider methods must be exposed:
method | HTTP method | URL |
---|---|---|
index | GET | /projects.fxml |
show | GET | /projects/1.fxml |
create | POST | /projects.fxml |
update | PUT | /projects/1.fxml |
destroy | DELETE | /projects/1.fxml |
- Of course we know that most browsers don’t actually support HTTP PUT and DELETE methods. As a result,
update
anddestroy
are actually implemented using HTTP POST with a special_method
parameter sent along to indicate ifPUT
orDELETE
should be used. This is a pretty big hack, but let’s call this an implementation detail since it doesn’t really affect the way we develop applications. - In case you are wondering what’s up with that
.fxml
extension, why not.xml
? Well, there’s a good reason for that. Flex imposes certain conditions on how the content is delivered and what kind of XML formatting is used1. Instead of hijacking the default.xml
content type, we’ve decided to use a special Flex specific content-type (.fxml
stands for Flex XML). This allows us to support standard XML API consumers that respect HTTP response headers and can consume just about any XML using.xml
content type and Adobe Flex clients using the same controller. - The other 2 standard Rails RESTful Controller methods (
new
andedit
) are optional. They are typically used to render a form that allows you to create a new (or edit an existing) resource. Unlike normal HTML/JS websites, applications written for the Adobe Flash platform are stateful so the concept of requesting a form rendered by the server is meaningless. (new
andedit
methods are never used by the RestfulX framework.)
1 Staying with our project
resource example, we can now focus on the details of XML-over-HTTP messaging.
1. If our RESTful projects controller receives index
method invocation XML of the following form must be returned:
<?xml version="1.0" encoding="UTF-8"?>
<projects type="array">
<project>
<completed type="boolean">false</completed>
<created_at type="datetime">2008/07/09 20:08:28</created_at>
<end_date type="date">2008/07/09</end_date>
<id type="integer">490909803</id>
<name>Project4NameString</name>
<notes>Project4NotesText</notes>
<start_date type="date">2008/07/09</start_date>
<updated_at type="datetime">2008/07/09 20:08:28</updated_at>
<user_id type="integer">276171944</user_id>
</project>
<project>
<completed type="boolean">false</completed>
<created_at type="datetime">2008/07/09 20:08:28</created_at>
<end_date type="date">2008/07/09</end_date>
<id type="integer">1043718716</id>
<name>Project2NameString</name>
<notes>Project2NotesText</notes>
<start_date type="date">2008/07/09</start_date>
<updated_at type="datetime">2008/07/09 20:08:28</updated_at>
<user_id type="integer">981972180</user_id>
</project>
<project>
<completed type="boolean">false</completed>
<created_at type="datetime">2008/07/09 20:08:28</created_at>
<end_date type="date">2008/07/09</end_date>
<id type="integer">1060557696</id>
<name>Project1NameString</name>
<notes>Project1NotesText</notes>
<start_date type="date">2008/07/09</start_date>
<updated_at type="datetime">2008/07/09 20:08:28</updated_at>
<user_id type="integer">938764944</user_id>
</project>
<project>
<completed type="boolean">false</completed>
<created_at type="datetime">2008/07/09 20:08:28</created_at>
<end_date type="date">2008/07/09</end_date>
<id type="integer">1063252898</id>
<name>Project3NameString</name>
<notes>Project3NotesText</notes>
<start_date type="date">2008/07/09</start_date>
<updated_at type="datetime">2008/07/09 20:08:28</updated_at>
<user_id type="integer">887745387</user_id>
</project>
</projects>
If you are familiar with Ruby On Rails you’ll notice that this is pretty much standard XML returned by calling .to_xml
on any ActiveRecord model (or in this case calling .to_xml
on an array of ActiveRecord models). There is one caveat however:
- Flex E4X implementation is actively unhappy with XML element names that contain dashes. As a result, all XML returned must use underscores instead of dashes by convention.
Speaking of conventions… There are a few more things you’ll want to pay attention to when working with XML providers for the RestfulX framework.
- If the value of a particular XML element represents a
string
you can omittype
attribute on that particular element, otherwise type attribute is required. The following types are supported:- integer
- boolean
- date
- datetime
- array
- If you are referring to another model/resource by id the following syntax must be used:
<user_id type="integer">887745387</user_id>
It is essentially equal to model_name
followed by _id
.
This should all sounds familiar if you’ve been working with Rails for a while.
restfulx
gem provides a .to_fxml
implementation that follows all of the rules required by the RestfulX framework.
2. The result of invoking show
is pretty similar, except that a single element is expected:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<completed type="boolean">false</completed>
<created_at type="datetime">2008/07/09 20:08:28</created_at>
<end_date type="date">2008/07/09</end_date>
<id type="integer">1060557696</id>
<name>Project1NameString</name>
<notes>Project1NotesText</notes>
<start_date type="date">2008/07/09</start_date>
<updated_at type="datetime">2008/07/09 20:08:28</updated_at>
<user_id type="integer">938764944</user_id>
</project>
3. Unlike standard Rails controller implementation create
, update
and destroy
methods must also return full XML representation of the model/resource that has been created, updated or destoyed. For example, if I’ve invoked update
on the projects controller and changed project name to FooBar
, the following XML should be returned:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<completed type="boolean">false</completed>
<created_at type="datetime">2008/07/09 20:08:28</created_at>
<end_date type="date">2008/07/09</end_date>
<id type="integer">1060557696</id>
<name>FooBar</name>
<notes>Project1NotesText</notes>
<start_date type="date">2008/07/09</start_date>
<updated_at type="datetime">2008/07/09 20:08:28</updated_at>
<user_id type="integer">938764944</user_id>
</project>
Another interesting feature of Flex’s own HTTPService is that using HTTP responses with status code set to anything other than 200 OK is pretty much useless. When the error is handled by the Flash player client programmer working in ActionScript will not be able to inspect the status code or the body of the error message (if any). All we’ll know at runtime is that something is wrong but not what exactly is wrong.
As a result all errors returned by the remote RESTful controllers must be accompanied by HTTP status code 200 OK.
Use can use
AS3XMLHTTPServiceProvider
if you don’t like this.
We use top level <errors>
element to denote that the response is an error and must be handled in a special way. Here’s an example:
<?xml version="1.0" encoding="UTF-8"?>
<errors>
<error field="name" message="Some stuff is wrong"/>
</errors>
And here’s the bit of Ruby that allows us to serialize ActiveRecord errors to XML in that way:
# Add more extensive reporting on errors including field name along with a message
# when errors are serialized to XML
class Errors
def to_fxml(options={})
options[:root] ||= "errors"
options[:indent] ||= 2
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
options[:builder].instruct! unless options.delete(:skip_instruct)
options[:builder].errors do |e|
# The @errors instance variable is a Hash inside the Errors class
@errors.each_key do |attr|
@errors[attr].each do |msg|
next if msg.nil?
if attr == "base"
options[:builder].error("message" => msg)
else
fullmsg = @base.class.human_attribute_name(attr) + ' ' + msg
options[:builder].error("field" => attr, "message" => fullmsg)
end
end
end
end
end
end
Finally here’s an example RESTful Ruby On Rails controller that exposes the API required by the RestfulX Framework. Of course this code will be very different if you are using (or perhaps developing) and XML-over-HTTP provider for the RestfulX Framework in say Merb or Django or Erlyweb or some Java framework. But it should give you an idea of what’s required.
class ProjectsController < ApplicationController
def index
@projects = Project.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @projects }
format.fxml { render :fxml => @projects }
end
end
def show
@project = Project.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @project }
format.fxml { render :fxml => @project }
end
end
def new
@project = Project.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @project }
format.fxml { render :fxml => @project }
end
end
def edit
@project = Project.find(params[:id])
end
def create
@project = Project.new(params[:project])
respond_to do |format|
if @project.save
flash[:notice] = 'Project was successfully created.'
format.html { redirect_to(@project) }
format.xml { render :xml => @project, :status => :created, :location => @project }
format.fxml { render :fxml => @project }
else
format.html { render :action => "new" }
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
format.fxml { render :fxml => @project.errors }
end
end
end
def update
@project = Project.find(params[:id])
respond_to do |format|
if @project.update_attributes(params[:project])
flash[:notice] = 'Project was successfully updated.'
format.html { redirect_to(@project) }
format.xml { head :ok }
format.fxml { render :fxml => @project }
else
format.html { render :action => "edit" }
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
format.fxml { render :fxml => @project.errors }
end
end
end
def destroy
@project = Project.find(params[:id])
@project.destroy
respond_to do |format|
format.html { redirect_to(projects_url) }
format.xml { head :ok }
format.fxml { render :fxml => @project }
end
end
end
This controller exposes .html
, .xml
and .fxml
APIs. .xml
API can be used by normal ActiveResource
-like consumers, while .fxml
API is used by Flex clients and .html
API can be customized to say iPhone.
edit
and new
methods above are only used by HTML clients.
If you follow the above conventions you should be able to port just about anything that can message XML and follows basic RESTful conventions to use Flex UI based on the RestfulX framework.