-
Notifications
You must be signed in to change notification settings - Fork 59
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,
updateanddestroyare actually implemented using HTTP POST with a special_methodparameter sent along to indicate ifPUTorDELETEshould 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
.fxmlextension, 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.xmlcontent type, we’ve decided to use a special Flex specific content-type (.fxmlstands 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.xmlcontent type and Adobe Flex clients using the same controller. - The other 2 standard Rails RESTful Controller methods (
newandedit) 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. (newandeditmethods 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
stringyou can omittypeattribute 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
AS3XMLHTTPServiceProviderif 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.