About RESTful features of modern Web frameworks

This post has been published more than 13 years ago, it may be obsolete by now.

Frameworks like Symfony or rails (and probably many others) provide a very convenient feature named RESTful routing, aka HTTP-aware urls and controllers, generally associated with an object/url mapping mechanism to expose Model entities and several available actions on them over HTTP.

This is really useful, especially when you deal with WebServices on a daily basis, or if you want to reuse your controllers in both standard html or service oriented architecture (SOA) contexts.

For example, with Symfony, you can declare an HTTP routes collection this way (all examples are taken from the Sftunes Symfony application I recently released on github):

# routing.yml
fortune:
  class: sfDoctrineRouteCollection
  options:
    model:      Fortune
    action:     [list, new, create, edit, show]
    object_actions:
      comment:  post
      down:     put
      up:       put
    collection_actions:
      top:      get
      worst:    get

Here the fortune route collection will expose the Fortune Doctrine ORM model over HTTP, using available HTTP verbs like GET, PUT, POST or DELETE for example, hence providing basic CRUD operations (see documentation).

You will then be able to obtain several Symfony routes, to list them just run the app:routes task from the command line:

~ $ ./symfony app:routes main
>> app       Current routes for application "main"
Name            Method Pattern
fortune_top     GET    /fortune/top.:sf_format
fortune_worst   GET    /fortune/worst.:sf_format
fortune         GET    /fortune.:sf_format
fortune_new     GET    /fortune/new.:sf_format
fortune_create  POST   /fortune.:sf_format
fortune_edit    GET    /fortune/:id/edit.:sf_format
fortune_update  PUT    /fortune/:id.:sf_format
fortune_delete  DELETE /fortune/:id.:sf_format
fortune_show    GET    /fortune/:id.:sf_format
fortune_comment POST   /fortune/:id/comment.:sf_format
fortune_down    PUT    /fortune/:id/down.:sf_format
fortune_up      PUT    /fortune/:id/up.:sf_format
homepage        ANY    /

By calling an url such as /fortune/2.html using the GET verb, you’ll obtain a response in text/html format, and Symfony will use a standard HTML template to decorate it – whereas if you call /fortune/2.json using the DELETE verb, you’ll ask for the deletion of the fortune related instance and receive a response in JSON format (at least if you provided the related JSON decoration template, of course).

That’s pretty fancy, but a common mistake is to generate links from templates calling verbs other than GET, for example here the fortune_up and fortune_down routes aim to be called via PUT (because they imply a modification of a Fortune object instance), and you may be tempted to write something like this in you templates:

<?php echo link_to('Vote down this fortune', 'fortune_down', $fortune, array(
  'method' => 'put',
)) ?>

While this is perfectly possible technically speaking, a quick look at the rendered HTML code will temper the interest of this approach:

<a onclick="var f = document.createElement('form'); f.style.display = 'none';
  this.parentNode.appendChild(f); f.method = 'post'; f.action = this.href;
  var m = document.createElement('input'); m.setAttribute('type', 'hidden');
  m.setAttribute('name', 'sf_method'); m.setAttribute('value', 'put');
  f.appendChild(m);var m=document.createElement('input');m.setAttribute('type', 'hidden');
  m.setAttribute('name', '_csrf_token');
  m.setAttribute('value', 'd26d99f7f4f97lsdhklqejshdjkshdf860124');
  f.appendChild(m);f.submit();return false;" href="/main_dev.php/fortune/19/down">
    Vote down this fortune
</a>

Yes, calling link_to() with the method option set to something else than GET will generate a form to challenge the url with the correct HTTP verb (through the kinda magic sf_method request parameter), dynamically using Javascript. Not really clean, unobstrusive and accessible. A link should always only handle GET verb, because it’s just a link to another resource in view mode, not a modification of it. So you should rather use a <form/> tag to deal with such kind of operations in your code, always.

But there’s more: imagine you want to deal with a Fortune modification form, still by using the PUT http verb and the fortune_update route, in a standard html context (not a WebService one); you have a form so it’s okay? It’s not, a browser, even the most modern one in 2010, will not understand something else than GET and POST. That’s a shame actually. Symfony circumvents the problem by adding a supplementary sf_method hidden parameter to the form fields, so the targeted controller will be able to detect an incoming PUT request, but this is clearly a kind of patch applied to HTTP support in Browsers.

I’m searching for a conclusion, but can’t find one except why on Earth modern browsers don’t deal with something else than GET and POST nowadays?

Feel free to provide hints on this topic in the comments.

Edit: Just learnt via the comments that HTML5 draft spec includes support of PUT and DELETE HTTP verbs in forms (source - thx jblanche).

Great, can’t wait for 2022!