L'API est toujours en développement et n'est pas encore versionnée. Même si elle le deviendra une fois son développement stabilisé, elle doit être vue comme une bêta avec les changements que cela peut impliquer dans ses paramètres et ses réponses.
L'API est accessible à partir du domaine zestedesavoir.com/api/
(prochainement api.zestedesavoir.com
) et en HTTP ou en HTTPS mais dès lors où vous effectuez des requêtes authentifiées, le HTTPS devient obligatoire. De base, toutes les réponses sont renvoyées en JSON.
$ curl -i https://zestedesavoir.com/api/membres/
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 14 Feb 2015 19:41:29 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
ETag: "d8f9437e4f88b4e6e2ad0a6d770d970bfdd5bbbc689cc3b3390759d06d4f105a"
Vary: Accept, Cookie
Allow: GET, POST, HEAD, OPTIONS
P3P: CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"
{
"count":0,
"next":null,
"previous":null,
"results":[]
}
Tous les timestamp sont retournés sous le format ISO 8601 : YYYY-MM-DDTHH:MM:SSZ
.
GET
: Utilisez pour récupérer des ressources.POST
: Utilisez pour créer des ressources.PUT
: Utilisez pour mettre à jour des ressources.DELETE
: Utilisez pour supprimer des ressources.
Les autres verbes ne sont pas supportés.
Par défaut, le serveur renvoie les réponses sous le format JSON
mais il gère aussi le XML
. Pour demander au serveur de renvoyer les réponses sous ce dernier format, il faut utiliser l'en-tête Accept
en spécifiant application/xml
comme valeur (ou application/json
pour renvoyer du JSON
).
$ curl -H "Accept: application/xml" https://zestedesavoir.com/api/membres/
Les formats de sorties (en) sont renseignés dans le fichier settings.py
sous l'attribut DEFAULT_RENDERER_CLASSES
du dictionnaire REST_FRAMEWORK
. Pour Django Rest Framework, tous les formats de sorties sont des renderer
.
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.XMLRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
}
Plusieurs formats d'entrées sont supportés par le serveur, à savoir le JSON
(par défaut), l'XML
, le formulaire et le multi part (x-www-form-urlencoded
). Ces formats peuvent être renseignées avec l'en-tête Content-Type
.
$ curl -H "Content-Type: application/xml" https://zestedesavoir.com/api/membres/
Les formats d'entrées (en) sont renseignés dans le fichier settings.py
sous l'attribut DEFAULT_PARSER_CLASSES
du dictionnaire REST_FRAMEWORK
. Pour Django Rest Framework, tous les formats de sorties sont des parser
.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.XMLParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser',
),
}
Un cache spécifique à l'API est mis en place pour mettre en cache toutes les méthodes GET
. Le système n'est pas spécifique à Django Rest Framework mais est disponible via une librairie tierce qui a été développée spécialement pour fonctionner avec DRF, DRF-Extensions (en).
Pour placer un cache, il suffit d'annoter la méthode GET
voulue par l'annotation @cache_response()
(comme le mentionne la documentation à ce sujet (en)
). Par exemple, la méthode GET
pour récupérer la liste paginée des membres ressemblerait au code ci-dessous.
class MemberListAPI(ListCreateAPIView, ProfileCreate, TokenGenerator):
queryset = Profile.objects.all()
list_key_func = PagingSearchListKeyConstructor()
@cache_response(key_func=list_key_func)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Dans le contexte de Zeste de Savoir, ce n'est pas suffisant. Comme la plupart des routes GET
peuvent prendre des paramètres, il faut permettre au cache de distinguer une URL X avec des paramètres et une URL Y avec d'autres paramètres. Ceci se fait en spécifiant une clé au cache de la méthode. Par exemple, pour la pagination, si aucune clé n'est renseignée, le cache renverra toujours le même résultat peu importe la page souhaitée.
Pour enrichir la clé d'un cache, DRF-Extensions propose les KeyConstructor
. Toutes les informations et les possibilités à ce sujet sont disponibles dans la documentation de cette librairie (en).
Un ETag est un identifiant unique assigné par le serveur à chaque version d'une ressource accessible via une URL. Si la ressource accessible via cette URL change, un nouvel ETag différent du précédent sera assigné. Cela permet notamment d'alléger le serveur lorsque le client utilise cet en-tête pour que le serveur ne calcul que l'ETag de la ressource et juge nécessaire ou non d'effectuer la requête en base de données si l'ancien et le nouveau ETag sont différents.
Le calcul de l'ETag n'est pas natif à Django Rest Framework mais est accessible via la bibliothèque DRF-Extensions (en). Le calcul est ajouté sur toutes les méthodes GET
et PUT
. Il est inutile de calculer des ETags pour des requêtes POST
et DELETE
puisque ces deux méthodes ont pour objectif de créer et supprimer des ressources.
Pour placer un ETag, il suffit d'annoter la méthode voulue par l'annotation @etag()
. Par exemple, la méthode GET
pour récupérer la liste paginée des membres ressemblerait au code ci-dessous.
class MemberListAPI(ListCreateAPIView, ProfileCreate, TokenGenerator):
queryset = Profile.objects.all()
list_key_func = PagingSearchListKeyConstructor()
@etag(key_func=list_key_func)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
Dans le contexte de Zeste de Savoir, ce n'est pas suffisant. Comme la plupart des routes GET
et PUT
peuvent prendre des paramètres, il faut permettre au cache de distinguer une URL X avec des paramètres et une URL Y avec d'autres paramètres. Ceci se fait en spécifiant une clé à l'ETag de la méthode. Par exemple, pour la pagination, si aucune clé n'est renseignée, l'ETag ne sera jamais recalculé peu importe la page souhaitée.
Pour enrichir la clé de l'ETag, DRF-Extensions propose les KeyConstructor
. Toutes les informations et les possibilités à ce sujet sont disponibles dans la documentation de cette librairie (en).
Note : L'ETag et le cache peuvent fonctionner ensemble. Une méthode peut être annotée avec @etag()
et @cache_response()
.
Pour utiliser un ETag, faite une requête vers n'importe quelle ressource en GET
ou PUT
. Dans les en-têtes de la réponse, il y figurera l'ETag avec sa valeur. Pour les prochaines requêtes vers cette même ressource, renseignez l'en-tête If-None-Match
et l'ETag sauvegardé comme valeur.
$ curl -H "If-None-Match: da54a5d285fbfc52bf62637147ecb5c11c7199ed78848b7f43781df0cd039b89" https://zestedesavoir.com/api/membres/
Si le serveur constate qu'il n'y a aucun changement dans la ressource, il renverra une réponse 304 Not Modified
avec un corps vide. Il n'est alors pas nécessaire de mettre à jour les valeurs sauvegardées en locale pour les ressources désirées. Dans le cas contraire, les ressources demandées seront renvoyées avec un nouvel ETag à sauvegarder.
Le throttling permet de poser des limites quant au nombre de requêtes possibles pour un utilisateur anonyme et connecté. Cette fonctionnalité est native à Django Rest Framework et se met en place facilement via le fichier settings.py
du projet sous l'attribut DEFAULT_THROTTLE_CLASSES
du dictionnaire REST_FRAMEWORK
pour spécifier les types de throttling à appliquer et sous DEFAULT_THROTTLE_RATES
pour spécifier les taux.
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'anon': '60/hour',
'user': '2000/hour'
}
}
Il existe d'autres configurations possibles. Pour en prendre conscience, rendez-vous dans la documentation du throttling (en).
La pagination permet d'éviter au serveur de faire des requêtes trop lourdes sur la base de données. Par exemple, si un client désire récupérer la liste de tous les utilisateurs de la plateforme et que cette même plateforme dispose d'un très grand nombre d'utilisateurs, la requête en base de données pourrait être lourde. Coupler à ceci des intentions malveillantes pour faire tomber le serveur, cela en devient presque une sécurité de paginer les listes de ressources.
La pagination peut être configurée directement dans les vues de l'API mais aussi dans le fichier settings.py
pour s'appliquer à l'ensemble des listes de toutes les ressources de l'API. Dans le fichier settings.py
, PAGINATE_BY
renseigne la taille d'une page, PAGINATE_BY_PARAM
permet aux clients de modifier la taille d'une page et MAX_PAGINATE_BY
permet de limiter cette dernière customisation.
REST_FRAMEWORK = {
'PAGINATE_BY': 10, # Default to 10
'PAGINATE_BY_PARAM': 'page_size', # Allow client to override, using `?page_size=xxx`.
'MAX_PAGINATE_BY': 100, # Maximum limit allowed when using `?page_size=xxx`.
}
Toutes les informations complémentaires à ce sujet sont disponibles dans la documentation de la pagination (en).
Son utilisation est simple, il suffit de renseigner la page avec le paramètre page
et, optionnellement, page_size
pour renseigner la taille de la page. Par exemple, récupérer la page 2 d'une page de taille 2 ressemblera à la requête suivante.
$ curl https://zestedesavoir.com/api/membres/?page=2&page_size=2
Dans la réponse, on retrouve des méta informations à propos de la liste : la taille totale de la liste, l'URL vers la page suivante et précédente et la liste attendue avec la ressource souhaitée.
{
"count": 43,
"next": "https://zestedesavoir.com/api/membres/?page=3&page_size=2",
"previous": "https://zestedesavoir.com/api/membres/?page=1&page_size=2",
"results": [
{
"pk": 41,
"username": "boo123451234",
"is_active": false,
"date_joined": "2015-02-08T15:53:12.666839"
},
{
"pk": 40,
"username": "boo12345123",
"is_active": false,
"date_joined": "2015-02-08T15:53:09.436657"
}
]
}
Django Rest Framework supporte plusieurs systèmes d'authentification (comme en témoigne la documentation sur l'authentification (en)). Sur Zeste de Savoir, il a été décidé d'utiliser l'OAuth2 (dont la spécification du protocole est disponible via ce lien (en)) pour tenter d'avoir le système le plus sécurisé possible.
L'authentification n'est pas directement dans Django Rest Framework, il ne fait que supporter des librairies tierces qui s'en occupe. La librairie choisie est Django OAuth Toolkit pour sa forte compatibilité avec Django Rest Framework, sa maintenance et sa compatibilité Python 3 et Django 1.7 (ou plus).
Toute sa configuration est détaillée dans la documentation de cette bibliothèque.
Des requêtes authentifiées ne peuvent se faire sans un client. Ce client est appelé "Application" dans Django OAuth Toolkit. C'est pourquoi, il sera nommé ainsi dans la suite de cette documentation. Pour créer une application, il faut en demander la création auprès d'un administrateur de la plateforme où il sera en mesure d'en créer 2 types : confidentiel et public. Une application confidentielle permet l'utilisation d'un refresh_token
au contraire d'une application publique qui se contente de renvoyer un access_token
.
Pour l'administrateur, il doit se rendre dans la section "OAuth2_provider", puis créer une application. Un identifiant et une clé secrète cliente seront automatiquement générés et seront les informations à communiquer auprès du développeur tiers. Après, il doit renseigner au minimum l'utilisateur concerné par la demande, le type du client et le grant type.
- Utilisateur concerné : Cela ne veut pas dire que cet utilisateur est le seul à pouvoir s'authentifier avec l'application. Cela le rend juste responsable en cas de dérive.
- Type du client : Privilégiez le type confidentiel au public pour permettre aux clients tiers de ne pas redemander aux utilisateurs leurs informations de connexion après l'expiration de leur token.
- grant type : Renseignez Resource owner password-based pour baser l'authentification sur le mot de passe de l'utilisateur final.
Pour récupérer les tokens, le développeur doit exécuter une requête en POST
et en spécifiant l'identifiant et la clé secrète de l'application, le grant type spécifié dans l'application et le pseudo/mot de passe de l'utilisateur qui souhaite s'authentifier. Une requête basique ressemblerait à la commande ci-dessous. Toute fois, sachez que les caractères spéciaux doivent être échappés dans une commande curl
comme celle exposé dans cette documentation. On ne peut que vous conseiller d'exécuter cette même requête plutôt dans une console REST comme il en existe des centaines.
$ curl -X POST -d "client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD" https://zestedesavoir.com/oauth2/token/
Si l'application est bien en confidentielle, la réponse à cette requête exposera 2 tokens, son type, sa date d'expiration et sa portée.
access_token
: Token a utiliser dans les requêtes que vous souhaitez authentifier.token_type
: Le type de l'OAuth2 sera toujours Bearer et devra être spécifié dans les prochaines requêtes.expires_in
: Le timestamp correspondant à la date d'expiration de l'access_token
.refresh_token
: Permet d'effectuer une nouveau requête pour récupérer les tokens d'authentification sans spécifier le pseudo et le mot de passe de l'utilisateur.scope
: Portée du token d'authentification générée pour le serveur.
{
"access_token": "wERPXXHpYAsJV29eATLjSO2u5bamyw",
"token_type": "Bearer",
"expires_in": 36000,
"refresh_token": "1HJaUfFYA5jE54e2Wz1yEMRi89z6er",
"scope": "read write"
}
Note : S'il existe déjà un token actif pour l'utilisateur final, l'ancien token sera invalidé au profit du nouveau.
Pour utiliser l'access_token
, il faut le renseigner dans l'en-tête de la requête sous l'attribut Authorization
avec comme valeur Bearer wERPXXHpYAsJV29eATLjSO2u5bamyw
.
$ curl -H "Authorization: Bearer wERPXXHpYAsJV29eATLjSO2u5bamyw" https://zestedesavoir.com/api/membres/1/
Attention : La requête doit se faire en HTTPS obligatoirement.
Si le token n'est plus valide ou que vous avez perdu l'access_token
de l'utilisateur final, il faut en récupérer un nouveau grâce au refresh_token
. Son utilisation est similaire à l'authentification sauf qu'il n'est pas nécessaire de renseigner le pseudo et le mot de passe mais le refresh_token
à la place et de spécifier un grant_type
avec comme valeur refresh_token
.
$ curl -X POST -d "client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN" https://zestedesavoir.com/oauth2/token/
A la suite de cela, de nouveaux tokens seront renvoyés et devront être sauvegardés pour une prochaine utilisation si nécessaire.
Django REST Swagger est une bibliothèque qui génère automatiquement la documentation d'une API Django basé sur la bibliothèque Django REST framework.
Cette documentation est accessible par l'url http://zestedesavoir.com/api/
et, via cette page, il est possible de :
- Lister toutes les APIs pour toutes les ressources.
- Connaitre les paramètres, les codes d'erreur et un exemple de réponse.
- Exécuter toutes les routes disponibles dans l'API.
Pour maintenir cette documentation, rendez-vous sur sa documentation (en) qui explique sur quoi se base la bibliothèque pour générer la documentation et comment y rajouter de l'information.