Thursday, May 25, 2006

Web services REST con Python e Twisted in un secondo

(Foto by Very Good With Computers)


C'era bisogno di inventare i Web service in Soap così complicati, così interoperabili solo a parole, così pesanti da implementare, da testare e da mettere in moto? Forse no, c'è REST; c'è sempre stato; leggere questo per credere. In poche parole, qualsiasi applicazione web può essere progettata a partire dai dati e dalle 4 operazioni fondamentali che ci permettono di leggerli, crearli, modificarli e cancellarli. Manco a dirlo GET, POST, PUT e DELETE. I dati sono ad esempio ordini, descrizioni anagrafiche, prenotazioni, e questi possono essere in XML, oppure no. A che ci serve XML se poi la nostra applicazione usa solo come client HTML e Javascript? Molto meglio usare oggetti Javascript direttamente serializzati in formato JSON. Inoltre, Javascript e Python hanno in comune costrutti come questi:

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

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

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

Queste espressioni sono valide sia in Python che in Javascript, e questo suggerisce che scrivendo il server in Python possiamo sviluppare web application basate su Ajax in tempo zero.


Vediamo un caso pratico. Costruiamo un web service che permetta di gestire una lista di utenti in modo che possiamo vedere la lista degli utenti, creare un nuovo utente, vedere i dettagli di un utente e cancellare un utente dalla lista. Per fare questo ci bastano due URL ed i quattro verbi dell'HTTP.


http://myserver/listUsers

da usare solo con GET


http://myserver/user?name=davide


POST crea un utente il cui name è davide

GET visualizza i dettagli di davide

PUT modifica l'utente davide

DELETE cancella davide dalla lista



Di seguito il codice che richiede, come ovvio, Twisted. Per testare le GET e POST potete usare un form HTML, mentre per PUT e DELETE dovrete usare Ajax o un altro client che vi consenta di usare questi metodi.

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())
#ATTENZIONE, L'UTILIZZO DI EVAL SU UN DATO TRASMESSO DALL'
#UTENTE E' UNA FALLA DI SICUREZZA. OCCORRE UTILIZZARE UN
#PARSER AD-HOC COME SIMPLEJSON

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())
#ATTENZIONE, L'UTILIZZO DI EVAL SU UN DATO TRASMESSO DALL'
#UTENTE E' UNA FALLA DI SICUREZZA. OCCORRE UTILIZZARE UN
#PARSER AD-HOC COME SIMPLEJSON

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()