Tornado – CRUD Web Services

In my last post, I spoke about Richardson Maturity model and its useful to demarcate web services based on their implementation maturity. We also understood, how CRUD Web Services fall under Level2 in Maturity model. Let’s take an example of CRUD Web Service developed with Tornado.

In this example, we develop a ‘blogger’ Web  service by using multiple logical resources (/blog, /blog/id, /blogs) and HTTP Verbs (GET, POST, PUT and DELETE) to perform various operations offered by the blogger service. Services offered are detailed below.

              Resources   HTTP Verbs    Implementation
http://localhost/blog/        POST Creates a blog
http://localhost/blog/id        GET Displays the blog
http://localhost/blog/id        PUT Updates the blog
http://localhost/blog/id      DELETE Deletes the blog
http://localhost/blogs/        GET Lists all the blogs
http://localhost/blogs/       DELETE Deletes all the blogs

Code implementation:


import tornado.ioloop
import tornado.web
from datetime import datetime
import urlparse
from bson.json_util import dumps
import pymongo
from pymongo import Connection
class Home(tornado.web.RequestHandler):
def initialize(self, connection):
self.conn = connection
self.db = self.conn['blogger']
self.collection = self.db['blogs']
## Blog's Welcome entry
timestamp = datetime.now()
blog = {
"_id": 1,
"title": "Welcome blog!",
"tags": ["hello world"],
"category": ["welcome"],
"timestamp": timestamp,
}
self.db.blogs.insert(blog)
class Blog(Home):
def get(self, blogid):
blog = self.db.blogs.find_one({"_id":int(blogid)})
self.set_header('Content-Type', 'application/json')
self.write(dumps(blog))
def post(self):
_id = self.db.blogs.count() + 1
timestamp = datetime.now()
body = urlparse.parse_qs(self.request.body)
for key in body:
body[key] = body[key][0]
blog = {
"_id": _id,
"title": body['title'],
"tags": body['tags'],
"category": body['category'],
"timestamp": timestamp
}
self.db.blogs.insert(blog)
location = "/blog/"+ str(_id)
self.set_header('Content-Type', 'application/json')
self.set_header('Location', location)
self.set_status(201)
self.write(dumps(blog))
def put(self, blogid):
## Convert unicode to int
_id = int(blogid)
timestamp = datetime.now()
body = urlparse.parse_qs(self.request.body)
for key in body:
body[key] = body[key][0]
blog = {
"title": body['title'],
"tags": body['tags'],
"category": body['category'],
"timestamp": timestamp
}
self.db.blogs.update({"_id":_id}, {"$set":blog})
self.set_header('Content-Type', 'application/json')
self.write(dumps(blog))
def delete(self,blogid):
## Convert unicode to int
_id = int(blogid)
blog = {
"title": None,
"tags": [],
"category": [],
"timestamp": None,
}
self.db.blogs.update({"_id":_id}, {"$set":blog})
self.set_header('Content-Type', 'application/json')
self.write(dumps(blog))
class Blogs(Home):
def get(self):
blogs = str(list(self.db.blogs.find()))
self.set_header('Content-Type', 'application/json')
self.write(dumps(blogs))
def delete(self):
blogs = str(list(self.db.blogs.find()))
self.set_header('Content-Type', 'application/json')
self.db.blogs.drop()
self.write(dumps(blogs))
application = tornado.web.Application([
(r"/", Home),
(r"/blog/([0-9]+)", Blog, dict(connection = Connection()) ),
(r"/blog/", Blog, dict(connection = Connection()) ),
(r"/blogs/", Blogs, dict(connection = Connection()) ),
],debug=True)
if __name__ == "__main__":
application.listen(7777)
tornado.ioloop.IOLoop.instance().start()

view raw

tornadocrud.py

hosted with ❤ by GitHub

In the above code snippet:

  • We have a base class Home that initializes (method initialize) the blogger service with a Welcome blog. (as it’s true with Google blog or WordPress).
  • Class Blog inherits from class Home for the initialization and exports methods get(), put() and delete() for performing operations like reading,  updating and deleting a blog (based on blog id).
  • post() method doesn’t take any argument but it creates a new blog and assigns a blogid. It also creates a resource /blog/id and returns a HTTP status code of 201 along with Location in Http headers.
  • Class Blogs export operations to be performed on all blogs and enables GET and DELETE Http operations to enable the user to view all his blogs or delete all blogs respectively.
  • All the responses from the blogger web service are JSON based (application/json).


import httplib2
from urllib import urlencode
import tornado.escape
import tornado.httpclient
http = httplib2.Http()
print "\nView welcome blog – GET"
headers, content = http.request("http://localhost:7777/blog/1", "GET")
print "Response:", tornado.escape.json_decode(content)
print "Headers:", headers
print "\nCreate new blog – POST"
tags = ['python', 'web']
category = ['www']
data = {'title':'Tornado Web Server', 'tags': tags, 'category':category}
body = urlencode(data)
headers, content = http.request("http://127.0.0.1:7777/blog/", "POST", body=body)
print "Response:", tornado.escape.json_decode(content)
print "Headers:", headers
print "\nView all blogs – GET"
headers, content = http.request("http://localhost:7777/blogs/", "GET")
print "Response:", tornado.escape.json_decode(content)
print "Headers:", headers
print "\nUpdate blog – PUT"
title = "mongo"
tags = ['python', 'DB']
category = ['db']
data = {'title': title, 'tags': tags, 'category':category}
body = urlencode(data)
headers, content = http.request("http://127.0.0.1:7777/blog/2", "PUT", body=body)
print "Response:", tornado.escape.json_decode(content)
print "Headers:", headers
print "\nDeactivate blog – DELETE"
headers, content = http.request("http://127.0.0.1:7777/blog/2", "DELETE")
print "Response:", tornado.escape.json_decode(content)
print "Headers:", headers
print "\nView all blogs – GET"
headers, content = http.request("http://localhost:7777/blogs/", "GET")
print "Response:",tornado.escape.json_decode(content)
print "Headers:", headers
print "\nClear all blogs – DELETE"
http = httplib2.Http()
headers, content = http.request("http://127.0.0.1:7777/blogs/", "DELETE")
print "Response:", tornado.escape.json_decode(content)
print "Headers:", headers

The client more or less uses all the capabilities of blogger web service and displays the JSON responses and Http headers as sent by the web service.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.