REST - REpresentational State Transfer - è uno stile architetturale per servizi web. Enfatizza semplicità e libertà di personalizzazione, ponendo attenzione a:
- uniformità delle interfacce
- scalabilità
- indipendenza dei componenti
#Nota spesso si ci riferisce a API HTTP identificandole come RESTful. Questo è un uso improprio del termine.
Idea: un server risponderà a richieste da parte del client con oggetti in formato HTML, JSON, XTML, etc. contenenti hyperlink che possono essere seguiti dal client per cambiare lo stato del sistema. Solo il primo identificatore deve essere conosciuto a priori, il resto è scoperto: loose coupling tra client e server; agilità nel cambiamento dei link.
HTTP è stateless by-design. Robusto contro interruzioni di comunicazione.
REST permette di costruire web API. Gli endpoint che seguono REST hanno le seguenti caratteristiche:
- stateless
- supporta verbi HTTP
- restituisce dati in formato JSON, XML, HTML, etc.
L'applicazione REST framework permette di integrare al framework tradizionale la gestione di web API.
Installabile con:
pip3 install djangorestframework
Poi da aggiungere alle applicazioni installate:
INSTALLED_APPS = [
'rest_framework',
...
]
Questa app si occupa di serializzare i dati e servirli mediante gli URL.
L'oggetto
Serializer
permette di convertire un oggetto complesso (eg. appartenente aModel
) in formato json
Esempio di serializzazione di un libro Book
:
from rest_framework import serializers, generics
from books.models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'description')
class BookListAPIView(generics.ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
Progettare API REST permette di disaccoppiare (rendere indipendenti) frontend e backend.
Come fa il frontend ad avere accesso ai dati serviti dal backend?
Il meccanismo CORS - Cross-Origin Resource Sharing - è un meccanismo per l'accesso a risorse servite da un server su un dominio differente (eg. immagini, stylesheets, iframes, videos, etc.). Client e server interagiscono per determinare se sia sicuro permettere la cross-origin request.
Installabile con pip install django-cors-headers
. Aggiunta tra le app installate:
INSTALLED_APPS = [
'corsheaders',
...
]
Aggiunta del middleware corsheaders.middleware.CorsMiddleware
. Nota che deve essere inserito PRIMA di commonMiddleware
.
L'accesso viene quindi permesso ai domini localhost:8000
e localhost:3000
:
CORS_ORIGIN_WHITELIST = [
'http://localhost:8000',
'http://localhost:3000'
]
Tra i tipi di API che si possono utilizzare esistono:
ListAPIView
CrateAPIView
RetrieveAPIView
UpdateAPIView
DestroyAPIView
Le prime 2 hanno bisogno di specificare l'ID dell'oggetto nell'header.
Evitano la scrittura di codice boilerplate, in particolare la riscrittura di tutte le 5 views per ogni oggetto. Django REST framework mette a disposizione i viewsets
, ovvero classi che integrano tutte le funzioni CRUD.
from rest_framework import viewsets
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
Per risparmiare il codice degli url:
from rest_framework import routers
router = SimpleRouter()
router.register('books', BookViewSet, basename='books')
urlpatterns = router.urls
I permessi possono essere impostati a livello di:
- Project: policy di accesso applicate globalmente, su tutto il progetto
- App: policy di accesso appliate a tutti gli endpoint di una certa APP
- View: policy di accesso applicate a una singola API
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSSES': ['rest_framework.permissions.IsAuthenticated']
}
IsAuthenticated
si contrappone a AllowAny
.
I permessi su una singola view si definiscono mediante l'attributo permission_classes: Tuple[permissions.BasePermission]
di una APIView.
Tipicamente nel file permisssions.py si inseriscono classi di permesso come:
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
def has_object_permissions(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.created_by == request.user
Data la natura stateless del protocollo HTTP, esso non fornisce la logica per l'autenticazione.
Esistono 3 tipi di autenticazione:
- Base
- Di sessione
- Con token
- Request from c to s
- S responds with
401 Unauthorized
. Invia headerWWW-Authenticate
- C invia le credenziali contenute in
Authorization
- S risponde con
200 OK
o403 Forbidden
Da questo momento in poi il client invia le future richieste usando le credenziali di Authorization
header.
Pro: standardizzato e di semplice implementazione Contro: autenticazione ad ogni richeista. Credenziali in chiaro (codificate in b64), quindi da usare solo se abbinata a HTTPS.
Per arginare le limitazioni della statelessness di HTTP, si introducono due strumenti per memorizzare variabili lato client: sessioni e cookies.
- C si autentica
- S rilascia cookie, identificativo della sessione
- Il cookie di sessione sarà inserito in ogni richiesta HTTP futura
- L'ID di sessione viene distrutto al logout
#Nota: questo approccio è stateful, poichè viene mantenuto lo stesso stato sia sul server che sul client
Autenticazione stateless che prevede la memorizzazione di un token univoco lato client.
- Utente accede dal C
- C invia le credenziali a S
- S verifica e in caso di successo genera un token
- Il token viene salvato nel local storage di C
- Il token viene passato nell'header di ogni richiesta HTTP
Il controllo di validità viene fatto sul token, senza essere a conoscenza delle informazioni sull'utente.
Pro: scalabile e multipiattaforma Contro: inefficiente, in quanto le informazioni sul client devono essere inviate ad ogni richiesta.
Su Django è supporata da TokenAuthentication
. L'implementazione è minimale.
#Vedi: JWT - JSON Web Token
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [...],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SesssionAuthentication',
'rest_framework.authentication.TokenAuthentication'
]
}
Aggungi l'app rest_framework.authtoken
e migra.
Per l'autenticazione si usa django-rest-auth
che espone dei suoi endpoint di autenticazione, ad esempio:
urlpatterns = [
path('api/v1/dj-rest-auth', include('dj_rest_auth.urls'))
]
Per la registrazione dell'utente si usa l'applicazione django-allauth
. Poi aggiungi tra le app installate:
INSTALLED_APPS = [
...,
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'dj_rest_auth.registration'
]
Per la registrazione dell'utente aggiungere l'endpoint:
urlpatterns = [
...,
path('api/v1/dj-rest-auth/registration/', include('dj_rest_auth.registration.urls'))
]
Per evitare la configurazione di un server mail si può definire email backend:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
SITE_ID = 1