Web Services
Contingut
Introducció
Un servei web o web service ve a ser un servei més del sistema informàtic (o sigui, un sistema d'intercanvi d'informació), però amb algunes peculiaritats:
- Utilitza el protocol HTTP per intercanviar les dades.
- Utilitza els mètodes HTTP: GET, POST, PUT, DELETE (inspirat en el CRUD de les BBDD).
- Pot tenir altres mètodes (arbitraris) però no és tan usual.
- Sol aplicar-se a sistemes navegables (enllaços/hipervincles).
- Sol estar al port 80
- Sol utiltizar una arquitectura REST o REpresentational State Transfer.
- Arquitectura client-servidor
- Sense estat
- Cachejable
- Per capes
- Codi "on demand": compartit (al client, p.ex. javascript)
- Interfície uniforme
Avantatges:
- Estandarització
- ...
Exemple en Python i CherryPy
En Python solem utiltizar la llibreria json o bé simplejson. Els mètodes més importants d'aquesta llibreria son:
- json.loads
- json.dumps
Fes un cop d'ull al framework CherryPy per desenvolupament ràpid en Python, l'utilitzarem per fer algunes pràctiques.
En moltes ocasions, els propis "renderers" del nostre framework ens faran la feina de traduir i importar els objecte JSON, tot i que en el fons estaran utilitzant aquesta llibreria.
Exemple: Xat
# http://www.cherrypy.org/
import cherrypy
import json
import pymongo
import datetime
class Xat:
exposed = True
def __init__(self):
# inicialitzem la DB de canals
self.client = pymongo.MongoClient()
self.db = self.client.xats
# llistar missatges o canals (parametre canal per get)
# tipus http://.../api/xat?canal=test
def GET(self,*args,**kwargs):
resposta = {}
# processem parametres
canal = kwargs.get("canal")
if not canal:
# mostrem llista de canals (col.leccions)
include_sys_cols = False
canals = self.db.collection_names(include_sys_cols)
resposta["canals"] = canals
else:
# responem llista de missatges del canal
missatges = self.db[canal].find({})
msgs = []
for msg in missatges:
msg["_id"] = str(msg["_id"])
msg["timestamp"] = str(msg["timestamp"])
msgs.append( msg )
if not msgs:
return json.dumps({"status":False,"missatge":"Canal inexistent"})
resposta["missatges"] = msgs
resposta["status"] = True
print resposta
return json.dumps( resposta )
# enviar missatges
@cherrypy.tools.json_in()
def POST(self,*args,**kwargs):
resposta = {}
try:
dades = cherrypy.request.json
# "dades" conte elements JSON deserialitzats
canal = dades["canal"]
doc = {}
doc["missatge"] = dades["missatge"]
doc["nick"] = dades["nick"]
doc["timestamp"] = datetime.datetime.now()
self.db[canal].save( doc )
resposta["status"] = True
resposta["missatge"] = "Missatge guardat correctament."
except:
resposta["status"] = False
resposta["missatge"] = "Error indefinit"
return json.dumps( resposta )
if __name__ == '__main__':
cherrypy.tree.mount(
Xat(), '/api/xat',
{'/':
{'request.dispatch': cherrypy.dispatch.MethodDispatcher()}
}
)
cherrypy.engine.start()
cherrypy.engine.block()
Utilitza les següents crides cURL per testejar i veure els canals:
$ curl http://localhost:8080/api/xat -H "Content-Type: application/json"
I per enviar un missatge:
$ curl http://localhost:8080/api/xat -d '{"canal":"DAW_MASTERS","missatge":"Benvinguts al xat DAW MASTERS!!","nick":"Enricus"}' -H "Content-Type: application/json" -X POST
El -X POST és opcional ja que sempre que li posem un "-d" (dades) ja s'assumeix que és POST.
Fixeu-vos en què:
- Les dades d'entrada ens les parseja el framework:
dades = cherrypy.request.json
- Les dades de sortida les hem de transformar a JSON
return json.dumps
- El framwork s'encarrega de posar headers.
Exemple en Python i Pyramid
Teniu el codi íntegre a: https://github.com/lacetans/jsonxat_ws
Fixa't en què en aquest cas:
- El framework ja ens parseja l'entrada:
dades = request.json_body
- O sigui, no cal fer un json.loads
- També el propi framwork ens parseja la sortida, fent un return d'un diccionari Python que es tradueix automàticament a JSON.
- El punt clau per realitzar això és el renderer:
@view_config(route_name='xat_set_missatge_ws', renderer='jsonp')
- El punt clau per realitzar això és el renderer:
@view_config(route_name='xat_set_missatge_ws', renderer='jsonp')
def xat_set_missatge_ws(request):
try:
# ho intentem per GET (JSONP)
remitent = request.GET.get("remitent")
missatge = request.GET.get("missatge")
if remitent and missatge:
if remitent.strip() and missatge.strip():
linia = remitent + "\t" + missatge
else:
# ho intentm per POST (JSON)
dades = request.json_body
linia = dades["remitent"] + "\t" + dades["missatge"]
if "<" in linia or "\n" in linia:
return {
"servei":"xat",
"canal":"principal",
"status":False,
"missatge":"ERROR: caracters ilegals"
}
file = open(STORAGE_FILENAME,"a+")
file.write(linia+"\n");
file.close()
except ValueError:
return {"servei":"xat",
"canal":"principal",
"status":False,
"missatge":"ERROR: JSON incorrecte"
}
except Exception as e:
print type(e).__name__
print e.args
return {"servei":"xat",
"canal":"principal",
"status":False,
"missatge":"ERROR desonegut: " + type(e).__name__
}
return { "servei":"xat",
"canal":"principal",
"status":True,
"missatge":"missatge enregistrat correctament"
}
Més sobre web services
...