Posts Tagged ‘tutorials’

Django and JSON-RPC

Tuesday, November 3rd, 2009

In application development, I’ve yet to be the bearer of a product who’s primary functionality has not been aided or entirely supported by a central web application. It is uncommon for developers in this situation to have the luxury of using a common programming language between server and client implementations. A lot of developers will end up rolling their own RPC which evolves over the lifetime of the product to meet its needs. This puts unneeded strain on the developer to fulfill the role of RPC expert as well as interface designer, and expert-everything-else.

I have done many primarily-RPC web applications using tomcat or PHP or twisted, but by far the best way is using Python and Django. However, I have always viewed the state of RPC in Django broken. While using Django for all sorts of other web application purposes is incredibly easy, getting started with RPC presents a lot more friction.

Introducing django-json-rpc, a very easy way to expose your web application to the world of integrated applications.

Installation is as simple as:

  easy_install django-json-rpc

Add a mount point to your urls.py file and import django-json-rpc.

  from jsonrpc import jsonrpc_site
 
  urlpatterns += patterns('', 
    (r'^json/', jsonrpc_site.dispatch)
  )

The interface to django-json-rpc is exposed through only one function – the jsonrpc_method decorator. Functions that use this decorator may be placed anywhere in the source tree and need only import jsonrpc.jsonrpc_method.

  from jsonrpc import jsonrpc_method
 
  @jsonrpc_method('app.sayHello')
  def say_hello(request, name):
    return "Hello, " + name

To hook your views into the default jsonrpc site, they must simply be imported somewhere by Django. The best place for this is in your urls.py file, which should now look something like this:

  from jsonrpc import jsonrpc_site
  import myapp.views
 
  urlpatterns += patterns('', 
    (r'^json/', jsonrpc_site.dispatch)
  )

Test your service with the provided Proxy:

  ./manage.py runserver 8080
 
  ./manage.py shell
  >>> from jsonrpc.proxy import ServiceProxy
  >>> s = ServiceProxy('http://localhost:8080/json/')
  >>> s.app.sayHello('Sam')
  {u'error': None, u'id': u'jsonrpc', u'result': u'Hello Sam'}

HTTP GET

Django-json-rpc supports JSON-RPC version 1.1 which includes support for HTTP GET, meaning, all your REST are belong to us as well. Add the HTTP GET mount point to your urls.py file:

  from jsonrpc import jsonrpc_site
  import myapp.views
 
  urlpatterns += patterns('', 
    (r'^json/', jsonrpc_site.dispatch),
    (r'^json/(?P<method>[a-zA-Z0-9.]+)$', jsonrpc_site.dispatch)
  )
</method>

It is required by django-json-rpc to mark each method safe for dispatch through HTTP GET. jsonrpc_method('sayHello') becomes:

  jsonrpc_method('app.sayHello', safe=True)

Now your method will be available at http://localhost:8080/json/app.sayHello?name=Sam. HTTP GET requests only support string and number typed arguments.

Authentication

The django-json-rpc package also supports authentication, by default using django.contrib.auth’s model backend – the User object we’re all so familiar with, however you may provide any method, including using any authentication middleware. If you aren’t using middleware, username and password arguments will automatically be added to the beginning of the argument list for your method.

  @jsonrpc_method('app.sayHello', authenticated=True)
  def say_hello(request):
    return "Hello, " + request.user.first_name
  >>> s.app.sayHello('samuraiblog', 'password')
  {u'error': None, u'id': u'jsonrpc', u'result': u'Hello Sam'}

Version Agnostic

django-json-rpc will continue to work regardless of which version JSON-RPC client you happen to be using. It supports all argument types supported in JSON-RPC versions 1.0, 1.1 and 2.0, and will respond if a client specifies the version in either the jsonrpc or version key.

Conclusion

I have used this package for the development of several commercial iPhone applications and it has greatly improved my productivity in developing a web service backend. Up next? A django-xmlrpc? Haha! Fuck that!

File Uploads in twisted.web

Friday, October 30th, 2009

Twisted is a python package for building asynchronous applications. It’s wide range of uses and sub-packages are daunting; I’ve developed with Twisted for several years now and still find it’s vast expanse of code intimidating. Twisted in 60 seconds is an excellent resource for anyone beginning with twisted and twisted.web. Here, we’re filling yet another gap in the twisted documentation.

Not surprisingly, handling file uploads in twisted.web is straight forward. Files are accessed through the args attribute of the request parameter to your render method. Since there can be multiple files for that given field, whether there are one or more, request.args[FIELD_NAME] will always be an array.

from twisted.internet import reactor
from twisted.web import resource, server
 
class FileUploadService(resource.Resource):  
  def render_GET(self, request):
    return '''<form enctype="multipart/form-data" method="post" action=".">
                 <input type="file" name="da_file"/>
                 <input type="submit"/>
              </form>'''
 
  def render_POST(self, request):
    return 'You submitted a file that was %i bytes' % len(request.args['da_file'][0])
 
root = resource.Resource()
root.putChild("upload", FileUploadService())
factory = server.Site(root)
reactor.listenTCP(8088, factory)
reactor.run()

According to the twisted.web documentation. This method of handling file uploads is not preferred. In fact, the preferred way is to use the more complex twisted.web2 package which supports streaming uploads. However, the twisted devs are working on porting most of twisted.web2’s features to twisted.web in an effort to consolidate the two. In production code, I have chosen to stick with twisted.web since it seems to be the package which will remain the most compatible with future versions of Twisted.