Architecting Forward ::>

Intro

It is pretty common nowadays for web applications and sites to expose a developer API to allow consumers to integrate their data with other application. Examples: Twitter, LinkedIn, Bit.ly, etc.

Although there are various options for implementing web services, as of 2010, the REST protocol seems to have achieved a comfortable lead over other approaches for Internet-based resource-oriented services.

Web Services Implementation Options

Before jumping to RESTstop (if you insist click here), let’s do a brief review of the few options,
developers have historically leveraged to build web services:

SOAP services: the API is formally defined using the WSDL format. API messages use a specific XML representation.
Additional services can be composed on top of the basic protocol to provide authentication, authorization, etc.
Although powerful, SOAP has proven too complex and too heavy for web developer APIs .
Also they require a full SOAP-compatible client stack for making them more difficult to call especially in the case of AJAX applications.
As a consequence, SOAP services tend to have been relegated to intra-enterprise cross-application services.

XML services: the API is not formally defined but end-points and their inputs and outputs are usually documented on a developer site.
Each service has a given url and uses XML to represent messages. As such an XML parser is required to provide robust processing
especially if the data structures are complex and rely on namespaces.

JSON services: similarly to the XML services the API is also not formally defined.
Messages are represented as JSON structures.JSON services lend themselves very well to AJAX calls from browser applications sinceJSON structures can be easily processed using basic Javascript.

Camping for Web Services

Since Camping is a generic Ruby web application framework, you can define service endpoints as url routes,
each mapped to a given controller.
An HTML-based view is not necessary obviously, so instead one option is to render the model
using the desired format: XML or JSON.
Example:

Such as an approach is fine for function-oriented APIs but if we want to provide CRUD access to resources,
then a REST approach provides more structure. You can of course implement a REST-style API on your own
by overriding the route dispatch mechanism and mapping the HTTP verbs such as PUT and DELETE
to the appropriate route controller methods.

RESTstop, a REST plugin for Camping

Matt Zukowski wrote the RESTstop library to easily provide access to REST resources using the underlying Camping route engine and controllers.
The idea was powerful yet simple, as a developer you would create a controller per resource
and specify the REST nature of the route like so:

Then once you had a controller, you would implement a special type of view appropriate for the format of the data you wanted to expose.
For example you could create views to render the data as XML or JSON or any other format you liked.

Structure the Views module according to the various formats to specify: HTML vs. XML vs. JSON

Render the controller output using the specified format

I would definitely recommend that your read the plugin's source. At 449 lines with many comments
it is pretty easy to see how it actually works. This will also make it easier for you to troubleshoot your code when needed.

4. Create the REST Sessions controller

The Camping Blog example has a simple login system built on top of the Camping session.
For our REST blog, we'll want to support both an interactive web app login (like in the original blog)
as well as a service-based login so that only authenticated applications can call our services.

So we'll expose a new REST resource called Session. Applications will be able to create and delete sessions -
to mirror the login and logout behavior. So let's create our first REST controller and name it Sessions:

class Sessions < REST 'sessions'
end

Now we just need to declare only a subset of the standard CRUD REST methods: create, and delete.
For now, let's just create the shell:

# POST /sessions
def create
end
# DELETE /sessions
def delete
end

For the create method, we will lookup the user based on the user id and password.
If not found we'll return a 401 (unauthorized) error with a message.
If found, we'll create a Camping session and render a view we'll create in a bit called user.
Here is the code:

8. Add JSON Support

Our Session resource can currently only be accessed via an XML REST interface.
So let's add JSON support by adding a user view to the JSON submodule (within our Views module):

module JSON
def user
@user.to_json
end
end

Let's go back to your IRB session and evaluate the following statements:

# Set the url for the JSON Resource format
u0b = "http://localhost:8080/sessions.json"
# Post to the Sessions resource
p0=Restr.post(u0b,o)

In TcpTrace you should see the request and the response containing the serialized user in JSON format:

In IRB you should also see the returned user instance, deserialized from JSON:

At this stage, our service can provide authentication via the Sessions resource using either an XML or JSON protocol.
I will leave the HTML implementation for the reader. The approach would include re-using the existing Login controller,
but the login view would be moved to the HTML submodule and could be modified to POST to the Sessions resource.

So we now have the basic building authentication block for the rest of the functionality for our service.
Although this would work fine, this approach is not very robust for industrial-strength services.
I would recommend using OAuth as it provides more features such as:

9. Create the REST Posts controller

We will model the Posts REST controller after the Sessions controller, with a few new twists such as theread, update and list methods.

Currently, in the Camping Blog example, there are several controllers responsible for managing posts: Index, PostNew, PostN, and Edit.
We will combine them into a brand new REST controller called Posts. So let's define a Posts controller with a REST route of 'posts':

class Posts < R 'posts'
end

Now we just need to declare the standard CRUD REST methods: create, read, update, delete, and list.
For now, let's just create the shell:

class Posts < R 'posts'
# POST /posts
def create
end
# GET /posts/1 or
# GET /posts/1.xml or
# GET /posts/1.json
def read(post_id)
end
# PUT /posts/1
def update(post_id)
end
# DELETE /posts/1
def delete(post_id)
end
# GET /posts or
# GET /posts.xml or
# GET /posts.json
def list
end
end

Let's start with the simplest method: list.
We will just take the code from the old Index controller's get method
(and later we'll move the index view method into the HTML submodule).

Now for the create method, we will start with the code from the old PostNew controller's post method.
But we will tweak the Post.create parameters list to accept either input.post_title (when coming from an HTML form)
or input.title (when coming from a REST service call). We'll do the same for the body input parameter.
Then we will adjust the redirect call to use the RESTstop-enhanced R route creation method as opposed
to redirecting to the old PostN controller.
So all in all the code will now look like this:

For the update method, we'll use the same technique as for the create method:
we'll start with the code from the old Edit controller's post method and allow for the
variations in the input parameters (depending on whether we come from an HTML post or a REST post).
We'll also adapt the redirect to use the new R method. So the code should look like this:

Our first edit will be in the index method where we'll change the code to
create a route for the "add one" link to route to the '"new" action of our new Posts REST resource
as opposed to routing to the old PostNew controller.

The next view method to change is login. In the original Camping version the form will post to the Login controller.
We now need it to post to the new Sessions REST resource. So let's adapt the code as follows:

Once the user has logged in we need to render the user HTML view
(if you remember we created an XML and JSON version but not an HTML one).
So let's add a quick view to greet the user and provide a link to the Posts page:

For the add view, we'll only adjust the posting route
from the old PostNew controller to the new Posts REST controller:

def add
_form(@post, :action => R(Posts))
end

For the edit view, we'll also adjust the posting route
from the old Edit controller to an item-specific action (using the post id) on the
new Posts REST controller using the PUT HTTP verb:

def edit
_form(@post, :action => R(@post), :method => :put)
end

We will copy the view view as-is:

def view
_post(@post)
end

For the _post partial view, we'll adjust the h2 title link
from the old PostN controller to an item-specific action (using the post id) on the
new Posts REST controller. And we'll add a statement to display the post's body:

The last partial view to update is _post_menu, where we'll adjust the edit link
from the old Edit controller to an item-specific edit action (using the post id) on the
new Posts REST controller. And we'll also add a link to delete the blog post using
an item-specific delete action.

So if you have been hesitant to provide a REST api in your Camping web application due to the anticipated complexity,
or if you want to create REST API separate from your web app, RESTstop is the right solution for you!

The only additional suggestion I would have is to consider using OAuth authorization framework
in conjunction with your REST API. This will increase the robustness and security of your service.