Django e i web services

Creiamo una api minima per interrogare il database degli utenti di un sito fatto in Django.

Una delle caratteristiche più interessanti di Django è il supporto per i web services, proviamo quindi a crearne uno.

Immaginiamo di avere un nostro sito e di volere esporre delle api per l'interrogazione della base dati degli utenti iscritti al sito.

La nostra api, molto semplice, potrebbe esporre possibilità di interrogazione:

  • http://<<nostrosito>>/api/user/<user_id>
  • ttp://<<nostrosito>>/api/users/

La prima dà la possibilità, ad utenti accreditati, di ottenere le informazioni su un certo utente <user_id>, mentre la seconda ritorna la lista degli utenti iscritti al portale.

Per implementare questa tecnologia utilizziamo il pacchetto django-piston, che fornisce un mini framework pensato apposta per l'implementazione di api "restful", ovvero aderenti allo standard REST, sul cui sito potrete trovare molta documentazione.

 

Il nostro meccanismo di autenticazione sarà basato su quella standard http (rappresentata dall'invio delle informazioni di username / password al web service).

 

La prima cosa da fare è predisporre Django affinchè sia in grado di "gestire" le url che abbiamo ipotizzato di voler utilizzare prima; ora, stabiliamo di avere una applicazione chiamata myapp (quindi presente nella direttiva INSTALLED_APPS in settings.py), e aggiungiamo il seguente codice ad urlpatterns di primo livello di Django:

 

    (r'^api/', include("myapp.urls")),

 

da questo momento in poi, la richiesta di una pagina del tipo http://<nostrosito>>/api/<qualcosa> verrà gestita invocando le logiche presenti nel file urls dell'applicazione myapp.

 

Siamo così pronti per implementare il web service: all'arrivo di una richiesta, il pacchetto myapp utilizzerà l'handler predisposto, e potrà tornare le informazioni ricercate oppure una pagina che indica la necessità di loggarsi (magari a seguito dell'invio di informazioni di autenticazione errate).

 

A questo punto è necessario un breve approfondimento su come viene strutturata l'idea di web service in Django.

 

Una richiesta fatta ad un web service corrisponde alla richiesta di una qualunque risorsa tra quelle alle quali il richiedente può accedere, e Django rispecchia fedelmente questa architettura.

Nel file urls.py dell'applicazione myapp, aggiungiamo le seguenti istruzioni:

 


from django.conf.urls.defaults import *

from piston.resource import Resource

from piston.authentication import HttpBasicAuthentication

from myapp.handlers import UserHandler

auth = HttpBasicAuthentication(realm="My Realm")

ad = { 'authentication': auth }

user_resource = Resource(handler=UserHandler, authentication = auth)

urlpatterns = patterns('',

    url(r'^users/(?P<user_id>[^/]+)$', user_resource),

    url(r'^users/', user_resource),

)

 

Gli import sono abbastanza ovvi tranne l'ultimo, su cui ci soffermiamo. UserHandler rappresenta l'oggetto in grado di interagire con le risorse che saranno interessare da richiesta giunta al web service, e quindi, nel nostro caso, potrà interrogare il database degli utenti iscritti al portale. La risorsa invocata, quindi, sarà user_resource, che come secondo parametro prende il tipo di autenticazione, HttpBasicAuthentication, ad indicare che l'autenticazione dell'utente che effettua la richiesta è fatta attraverso username e password.

Ecco come si presenta, all'interno del file handlers.py, l'oggetto UserHandler:

from piston.handler import BaseHandler

from django.contrib.auth.models import User

class UserHandler(BaseHandler):

    """

    """

    allowed_methods = ('GET',)

    model = User

    def read(self, request, id=None):

        """ This method returns a specific user info if id is not None,

            otherwise it returns the list of the ids of the available users

        """

        result = None

        if id:

            result = User.objects.get(id=id)

        else:

            users = User.objects.all()

            result = [ user.id for user in users]

        return result

Come vedete, in questo specifico caso, si tratta di un comportamento molto semplice: con la variabile di classe model indichiamo all'handler con quali oggetti avrà a che fare; quest'opzione non è obbligatoria, ma torna utile in certi caso (leggete qui per approfondire). Con allowed_methods, invece, indichiamo quali delle cosiddette operazioni CRUD sono lecite su tale risorsa.
Avendo definito solo l'operazione di GET, dobbiamo implementare il corrispondente metodo read, che non fa altro che cercare utenti con il dato id, e ne ritorna l'oggetto. Nel caso in cui non riceva alcun id in ingresso, ritorna la lista di quelli di tutti gli utenti del portale.
Proviamo ad invocare il web service:

Flyer:python2.6 j0k3r$ curl -u admin:admin "http://127.0.0.1:8000/api/user/1"
{
"username": "admin",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"last_login": "2010-05-18 10:41:10",
"password": "sha1$eda01$ab1636e666b8952dc66d5fd542dba93b67c7f474",
"email": "please_dont_spam_my_mail_at_gmail_dot_com",
"date_joined": "2010-05-18 10:41:10"
}

 

La richiesta la facciamo da shell, utilizzando curl, molto comodo per fare interrogazioni http veloci; ovviamente la stessa cosa potrebbe essere fatta da browser.
Dalla risposta del web service si evince che l'utente del sito con id uguale a "1" è l'utente admin.
Proviamo ora a ripetere l'invocazione del web service senza passare le credenziali di accesso:

Flyer:python2.6 j0k3r$ curl "http://127.0.0.1:8000/api/user/1"
Authorization Required

 

Come vedete in questo caso il web service ci risponde con una stringa, "Authorization required".
Infine, proviamo a richiedere la lista degli utenti del portale:

Flyer:python2.6 j0k3r$ curl -u admin:admin "http://127.0.0.1:8000/api/users/"
[
1
]

 

Considerazioni

Il codice di esempio di cui sopra ha funzioni soltanto didattiche, ed alcune cose andrebbero definite meglio: innanzitutto una api deve avere delle funzionalità ben specifiche, e per ogni possibile richiesta la risposta deve essere univoca e determinata. Qui abbiamo scelto di utilizzare uno stesso handler per due tipi di richiesta esclusivamente per brevità.
Inoltre, il risultato andrebbe rappresentato in modo specifico ed utile a chi lo deve usare, ad esempio potrebbe essere anche formattato in jSon oppure in YAML o in qualche altro modo.
Ultima cosa, l'autenticazione http base non è molto robusta, e soprattutto è da EVITARE nel caso in cui non possa avvenire (per qualunque motivo) con un minimo contesto di sicurezza garantito, come una connessione HTTPS.
Valida alternativa, un pò più complessa ma molto apprezzata ed in rapida diffusione, è rappresentata da OAuth, che permette l'autenticazione di un utente che richiede una risorsa ad un web service SENZA l'invio di username e password, ma attraverso un meccanismo di riconoscimento e generazione di token per l'accesso.
Di OAuth parlerò in un prossimo articolo.

Share this on

Share |

On same topics

Commenti

comments powered by Disqus