POST Tunneling: HTTP DELETE and PUT requests with JQuery and Django-Piston
Note: This post assumes some knowledge of Django. Everyone should have some knowledge of Django, so go get some!
Writing code in the Warren and Youngstown area is probably a lot like selling fertilizer to New Yorkers. Nobody cares how exactly it works, just so long as they don't have to touch it themselves and it does the job they want it to do. I've had a couple of clients lately who have asked me to build them a REST API for their web applications without asking me to build a REST API for their web applications. One of them wanted a simpler user experience ("I want it to be more user friendly, on one page, and I don't want all these forms jumbled up in different places"), and the other wanted a more maintainable codebase (somebody had all but re-written the Django Forms API in some of the ugliest code you've ever seen).
Both clients (customers, not browsers) had a very similar problem - their applications called for all of the data to be input on the same web page in the same context, and too many forms were either confusing the user, or the maintenance programmers. The client (browser, not customer) needed to be communicating to the server throughout the data creation process.
So I decided to build REST APIs for the both of them using Django-Piston.
The great thing about Piston is that it provides classes with methods that correspond to HTTP methods, and those classes and methods can be overriden. The classes can be turned into resources that URLs can be pointed to, which means that one URL can point to one class and different HTTP methods requested at a URL can execute different code on the server.
If you didn't catch all of that, don't worry - here's an example.
Let's say you have two URLs for blog posts in Django:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
   url(r'^blog/post/$', 'blog.handlers.blog_post'),
   url(r'^blog/post/(?P<post_id>[\d]+)/$', 'blog.handlers.blog_post'),
)
So when a GET request is made to "http://hostname/blog/post/", the response is a list of blog posts in JSON. When a GET request is more specifically made to "http://hostname/blog/post/118/", JSON for that specific blog post is returned.
To provide this functionality, we'll have to write handlers to return responses to requests to these urls. Our blog.handlers module (referenced in the URLs above) will take care of that:
   from django.shortcuts import get_object_or_404
   from piston.handler import BaseHandler    from piston.utils import rc    from piston.resource import Resource    from blog.models import Post    class BlogPost(BaseHandler):        allowed_methods = ('GET',)        def read(self, request, post_id=None):            if post_id is None:                return Post.objects.all()            else:                return get_object_or_404(Post, pk=post_id)
Let's break this down a bit:
The BlogPost class inherits from the Django-Piston BaseHandler class.
The allowed_methods field is a tuple that contains strings that correspond to HTTP methods. Allowed values are 'GET', 'POST', 'PUT', 'DELETE'
The read method overrides the base class method. It corresponds to the GET HTTP method.
Because two different URLs point to this handler, the post_id parameter may or may not be available, so its optional.
An if statement here checks if post_id is set. If it isn't, it returns all of the posts. If post_id is available, the method returns that specific record, or a 404 if the record doesn't exist.
Note that Piston takes care of the JSON serialization.
That takes care of retrieving blog posts, but what if we wanted to create a new one, update a current blog post, or delete a blog post? Well any good REST developer would know that you'd use the same URL and a different HTTP method to achieve the specific functionality. Let's have a peak at the code:
from django.shortcuts import get_object_or_404 from piston.handler import BaseHandler from piston.utils import rc from piston.resource import Resource from blog.models import Post class BlogPost(BaseHandler): Â Â Â allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
   def read(self, request, post_id=None):        if post_id is None:            return Post.objects.all()        else:            return get_object_or_404(Post, pk=post_id)    def create(self, request):        blogpost = request.POST['blogpost_data']        post, created = Post.objects.get_or_create(blogpost)           if created:            return post        else:            response = rc.BAD_REQUEST            response.write('Record already exists')            return response    def update(self, request, post_id):        post = get_object_or_404(Post, pk=post_id)        post.blogpost = request.POST['blogpost_data']        post.save()        return post    def delete(self, request, post_id):        post = get_object_or_404(Post, pk=post_id)        post.delete()        return rc.DELETED
A few things to notice here:
allowed_methods has been updated to accept all of the possible values.
The create, update, and delete methods correspond to the HTTP methods 'POST', 'PUT', and 'DELETE' respectively.
For anyone unfamiliar with Django - I'm taking some very large liberties with model instance creation here. Conceivably, Django model instances could be created and updated this way, but I wouldn't recommend it.
As an example, a POST request to 'http://hostname/blog/post/' will use the POST payload to create a new record for the Post model. A PUT request to 'http://hostname/blog/post/118/' will update the Post that has an id of 118. A DELETE request to the same address will delete record id 118.
POST Tunneling
When my web apps consume the APIs that I write, I like to do so with Javascript. Why should my server have to handle the load of consumption when I can have the users' computers do it?Â
The problem with consuming a REST API with client-side Javascript with AJAX is that most browsers only give access to two HTTP request methods - GET and POST. We definitely still want to be able to update and delete records in our example. We could just stuff more data into POST requests and do all the handling inside the create method, but that wouldn't be RESTful or very elegant.
Luckily, there's an HTTP Header that was dreamt up for just such an occasion: HTTP_X_METHODOVERRIDE. You can add it to your JQuery ajax requests like so:
$.ajax({ Â Â Â type: 'POST', Â Â Â url: 'http://hostname/blog/post/118', Â Â Â data: {BlogPost: 'some data'}, Â Â Â success: function(){ alert('Post Deleted'); }, Â Â Â headers: {'X_METHODOVERRIDE': 'DELETE'} });
This is known as POST Tunneling - making a POST request act like a different type of HTTP request. Of course, without some modifications, your Django server application is still going to handle this as if its a POST request. What you want to do is change the request.method for every incoming request that contains the HTTP_X_METHODOVERRIDE header. To do that, we'll create a new file in our blog app called middleware.py. See the documentation for more information on creating middleware. Here's the one we'll create:
class TunnelingMiddleware(object): Â Â Â def process_request(self, request): Â Â Â Â Â Â Â if request.META.has_key('HTTP_X_METHODOVERRIDE'): Â Â Â Â Â Â Â Â Â Â Â http_method = request.META['HTTP_X_METHODOVERRIDE'] Â Â Â Â Â Â Â Â Â Â Â if http_method.lower() == 'put': Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â request.method = 'PUT' Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â request.META['REQUEST_METHOD'] = 'PUT' Â Â Â Â Â Â Â Â Â Â Â if http_method.lower() == 'delete': Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â request.method = 'DELETE' Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â request.META['REQUEST_METHOD'] = 'DELETE' Â Â Â Â Â Â Â return None
Now every HTTP Method is available to our Piston Handler and each one of its corresponding methods from any type of JQuery AJAX request we want to make.













