zen of coding

Putting semi-RESTful API development to… rest

With some of the older web API standards subsiding, such as SOAP based interfaces, the new buzz seems to be all about REST (for the last few years at least).
Not that SOAP is “bad” in any way, as a matter of fact self-discovery via WSDL, and the standard itself is quite beneficial in many cases. It’s just that… programmers are notoriously lazy, and bulkiness of deploying a SOAP-like API makes most of us cringe.

So, let’s take a deeper look at REST for a second.

I think one of the most important principles behind the REST, is the fact that:

… [it] depends on HTTP headers for direction.

What that simply means is that a server, which exposes its API and expects a RESTful request, should rely on the HTTP headers (sent by the client) to make certain assumptions on what exactly the client is expecting to do (or acquire from the server).

CakePHP already does and excellent job of handling this issue for you. Let’s take a look at the mapping table from cake’s manual. (The table represents a specific HTTP method being used to send a request, and how it would be mapped, or actually expected to be mapped to a CakePHP action is some controller).
http://book.cakephp.org/view/1239/The-Simple-Setup

HTTP Method URL.method Controller action invoked
GET /recipes.method RecipesController::index()
GET /recipes/123.method RecipesController::view(123)
POST /recipes.method RecipesController::add()
PUT /recipes/123.method RecipesController::edit(123)
DELETE /recipes/123.method RecipesController::delete(123)
POST /recipes/123.method RecipesController::edit(123)

This is just my opinion, but while the REST ideology makes a lot of sense, it doesn’t always work out exactly as intended in the real world. That being said, REST is not meant to be a rule or a standard, rather it is a great set of suggestions on how to make your web app communicate efficiently with third-parties without much effort.

I guess, this is the point where I should explain the “semi-RESTful” part of the article…

According to the REST manifest (and as seen from the above mapping table), a POST action should be sent when any sort of data is supposed to be added (…again, as seen above, CakePHP will handle it perfectly well for you, out of the box).

To cut to the chase, a RESTful API will provide an opportunity for another web app to access yours and perform certain tasks.
As an example, some partner site might wish to send over the user credentials in order to gain certain information about the user attempting log-in.

Surely, issuing a GET request with such sensitive information wouldn’t be acceptable at all. Based on the above requirements we would have to send the data as POST (preferably over HTTPS). And thus our API deviates a bit from the REST approach, but… only slightly so.

… let’s keep this in mind and switch gears for a second.

Setting up a simple RESTful API in CakePHP will only take you a few minutes. (By following the manual and quite a few great tutorials out there).

Let’s see how to set things up when we need to stray away from the basics a little.

To illustrate a pseudo-real-world example, let’s consider the scenario mentioned above.
(A partner site is going to log-in into your application and get some data for the user).

I am going to do things a little backwards and present the following method for your consideration:
(this is going to be a part of our Users Controller):

public function api_info() {

Configure::write('debug', 0);

$result = array('response' => $this->User->find('first',
array(
 'conditions' => array(
   'User.username' => $_POST['username'],
   'User.password' => $_POST['password']
),
 'fields' => array(
   'id', 'first_name', 'last_name', 'email',  'phone', 'dob', 'stats'
),
 'recursive' => -1,
 'callbacks' => FALSE
)));

if($result['response'] === FALSE) {
  $result = array('response' => 'no user found');
}

$this->set(compact('result'));
}

So the idea, is quite simple here. Some remote application will access the given method with the POST’ed credentials and receive back some information about a user (as provided in our fields key)… agreeably so, in an XML format.

Here’s what would happen in real life:

1. Do a form post with username and password, as seen above…
2. To the following URL: https://www.example.com/api/users/info.xml
3. Get back the information about the user (or receive a “no user found” response) if the credentials are wrong.

(There are a few ways to make such access more secure, via IP restriction for example, but I don’t feel that is extremely relevant at this point. Just something to keep in mind).

Technically all we would need at this point is a view and the following code added to the method:
$this->RequestHandler->respondAs(‘xml’);
The RequestHandler component, in theory and according to the manual, would set the proper headers and provide a response, as expected in the XML format.
Well, for the life of me, I could not get this this to work… i.e. RequestHandler would not set the headers properly, thus a well formed XML string would be returned, as expected, but without telling the requesting application that it is an XML response, indeed (the magic is in the HTTP header).
(I hope someone can give me a guiding light…)

Nevertheless, this is not something to cry over as we can still fulfill our requirements, albeit in the “not-so-perfect-cake-way”.

Let’s take a look at the rest of the setup to make things a little more clear…

In order to respond with XML we will need to create the following structure in our views:
app/views/users/xml/api_info.ctp

See, the xml directory under our standard app/views/users will hold the view, which will be used to send the XML response back to the requestor. And, as you can see, the actual view api_info.ctp will match the action name, as usual.
If we had to send a JSON response the following structure would be needed:
app/views/users/json/api_info.ctp…. now, that everything is coming together let’s take a look at the view (api_info.ctp).

<?php
if(!empty($result)) {
  echo '<?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?>';
  echo $this->Xml->serialize($result, array('format' => 'tags'));
}
?>

I am not going to dwell on the details here, but basically we’ve got our $result from the controller and now present one as XML in the view. (I surely hope that the code above is quite easy to grasp).

Now, remember my complaint about the XML headers above?

Here is the necessary remedy… in the layouts we do have to create the following:
app/views/layouts/xml/api.ctp
… and here it is in all its glory:

<?php
  header (&quot;content-type: text/xml&quot;);
  echo $content_for_layout;
?>

As mentioned, I’ve abandoned my failed attempts with RequestHandler and set the header manually in the layout. (Not much harm done).

The next two steps, to complete our semi-RESTful API are as follows:

1. Enable parsing of extenstions in the routes.php file: Router::parseExtensions();
2. And… to use the proper prefix routing, i.e. transform the api_info() method into a cake-accessible URL such as outlined above: https://www.example.com/api/users/info.xml… we’ll need yet another addition to the routes.php file:
Router::connect(‘/:controller/:id’, array(‘prefix’ => ‘api’, ‘action’ => ‘info’, ‘[method]’ => ‘POST’), array(‘id’ => ‘[0-9]+’));. (I believe this neat little trick was “inspired” by cakebaker, so please award the required props to him).

Hooray! We are done now. To further extend your application and open up certain functionality to some third-party apps,
all that needs to be done now is to create a few, api_some_methods() and appropriate views.

  • Pingback: Tweets that mention Putting semi-RESTful API development to… rest « nuts and bolts of cakephp -- Topsy.com()

  • great article!

    however, i would like to know your thoughts on api authentication.

    what if the api user needs to be identified by a api_key and secret ?

    is there an easy way to just make sure all api functions are protected by it ?

    how can we take care of rate limiting if we are going to get hammered by hits ?

    i know some of my questions are beyond the scope of your article but since i was working on an api, i was trying to answers these myself when i read your article :)

    -mandy.

  • @Mandy

    These are very good questions, and would supplement the article very well… but I wanted to stay on point.
    That being said, there a few common solutions for the authentication.

    1. IP restriction on the server side, basically if you have the IP(s) of the requesting server (such as a partner site), this is probably the “safest” way to control access… however it should be done in your firewall or server config.
    2. Issue a token…
    a. The user must first request an authorization.
    b. You issue a token (some random string) which must be supplied with any following request… again combining this with something like a hashed IP + username would make the access even more secure.
    c. This token would persist for a limited amount of time. Such scheme is used by many API’s including popular credit card gateways and your favorite sites like PayPal :)
    3. You could agree with the third party on a specific public/private key combo and only allow access based on the fact that the both requirements are met.

    The one point that’s a bit harder to control is the amount of hits to the API, again, IMO, this should not be handled on the application level, but rather at the server/firewall settings.

  • kvz

    Hey teknoid,

    FYI, I’ve developed a plugin for Cake that does this & some more:
    http://github.com/kvz/cakephp-rest-plugin

  • @kvz

    Thanks, that’s great. I’ll give it a shot once I come back around to this part of the project.

  • red

    Regarding the not-cake-way header(“content-type: text/xml”) I’ve found that putting in controller:
    $this->RequestHandler->respondAs(‘xml’);
    $this->RequestHandler->setContent(‘xml’, ‘text/xml’);

    Works perfect, no need to set up header in view anymore.

    Oh, and instead of:
    echo ”;

    I think better is:
    Xml->header(); ?>

    • red

      Ops, looks like comments are not escaped :) I mean insted of:

      Better is:
      echo $this->Xml->header();

  • teknoid

    @red

    Thanks, I’ll give it a shot to see if it works for me now as well.

  • This works fine for me except that my api_info() action will not accept post vars, I’ve tried all sorts! var_dump(file_get_contents(‘php://input’)) will receive the Request Body but nothing will transfer the Request Headers into $_POST variables. Know any reason why this may happen? Thanks in advance

  • teknoid

    @Stephen

    It might have something to do with HTTP headers and content type.
    I think if you send your request as “text/plain”, it won’t be POST’ed properly.

    There’s a simple firefox add-on called “poster”, which can help set the correct headers/content type and further troubleshoot the problem

%d bloggers like this: