Saturday, January 13, 2007

REST Web Services at your fingertips with Python and twisted


Digg!


(Foto by Very Good With Computers)

Why using Soap? Soap is so complex and so heavy to implement, to test and to deploy. You have an alternative: you can use REST. It is not a brand new protocol. REST was already here, from the beginning of the Web. You don't trust me? Read this.



In very few words, every web application can be designed around a good data definition and on 4 fundamental operations: create new data, edit existing data, read data, delete data. You can trust me: you have these operations in HTTP: POST, PUT, GET and DELETE. Your data may represent orders, user profiles, bookings and they are usually defined in XML. Or not? In some cases other formats can be used more convenientely. For instance JSON if your client is a web browser. JSON is a format that uses Javascript literals to serialize data. Moreover, Javascript and Python have some syntax in common. Expression like these:

a={“x”:32 , “y”:25}

b=[23,33, “I'm a string”]

c=[[34,35,36],[44, “s”]]


... are valid both in Python and Javascript. This fact suggests that with a little effort we can write web applications using Python in the server side and Ajax in the client side.

Let's see an example. We can build a very simple web service to manage a list of people. The operations are: create a user, see a user profile, remove a user. To do that we need two URLs and the 4 HTTP methods.


http://myserver/listUsers

to be used only in GET

http://myserver/user?name=davide

where

POST creates a new user davide

GET reads the profile of davide

PUT modify the record for user davide

DELETE remove the user davide from the list



Here you have the code, that depends on Twisted.

To test GET and POST you can use a HTML form. To test PUT and DELETE you need to make some Ajax call or to use another client able to implement these methods.

from twisted.internet import reactor
from twisted.web import server, resource
from twisted.web.static import File

user1={"name":"john", "surname":"smith", "age":23, "email":"john@foo.bar"}
user2={"name":"davide", "surname":"carboni", "age":35, "email":"davide@foo.bar"}
user3={"name":"stefano", "surname":"sanna", "age":32, "email":"stefano@foo.bar"}

users=[user1,user2,user3]

class Root(resource.Resource):
isLeaf=False
def render_GET(self, request):
return "Arkanoid?"





class ListUsers(resource.Resource):
isLeaf=True
def render_GET(self, request):
userList=[us['name'] for us in users]
return str(userList)




class User(resource.Resource):
isLeaf=True
def render_GET(self, request):
name=request.args['name'][0]
record=filter(lambda(x): name==x['name'], users)[0]
return str(record)



def render_POST(self,request):
record=eval(request.content.read())
#WARNING. USING EVAL ON USER TRANSMITTED DATA
#IS A SEVERE SECURITY FLAW.
#USE A PARSER LIKE SIMPLEJSON INSTEAD

users.append(record)
return 'http://localhost/user?name=%s' % (record['name'])


def render_PUT(self,request):
name=request.args['name'][0]
newrecord=eval(request.content.read())
#WARNING. USING EVAL ON USER TRANSMITTED DATA
#IS A SEVERE SECURITY FLAW.
#USE A PARSER LIKE SIMPLEJSON INSTEAD

oldrecord=filter(lambda(x): name==x['name'], users)[0]

for key in oldrecord:
oldrecord[key]=newrecord[key]

return "OK"

def render_DELETE(self,request):
name=request.args['name'][0]
record=filter(lambda(x): name==x['name'], users)[0]
users.remove(record)
return "OK"



if __name__ == "__main__":
root=Root()
root.putChild("user",User())
root.putChild("listUsers",ListUsers())
root.putChild("d",File("."))
site = server.Site(root)
reactor.listenTCP(8000, site)
reactor.run()

3 comments:

felix said...

If you want to see the "Arkanoid?" when opening http://localhost:8000, set

isLeaf=False

in Root.

Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!

felix said...

Better yet:

omit the isLeaf stuff (defaults to False anyways; should have been True in my previous post)

Instead overwrite the getChild method of Root like this:

class Root(resource.Resource):
    def render_GET(self, request):
        return "Arkanoid?"

    def getChild(self, name, request):
        if name == "":
            return self
        return Resource.getChild(self, name, request)

This way a request like http://localhost:8000/ will yield "Arkanoid?". Nested URLS like http://localhost:8000/listUsers/ will work too.

See Twistedweb Howto.

I hope this is indented. Used lots of nbsp tags. Why does blogger.com disallow pre and code tags?