ZEP-02 : Elaboration d'une API

a marqué ce sujet comme résolu.
Cartouche
ZEP 2
Titre Elaboration d'une API
Révision 4
Date de création 11 Juillet 2014
Dernière révision 29 Janvier 2015
Type Feature
Statut Rédaction

Le contexte de la proposition

Il est presque inutile d'expliquer pourquoi cette fonctionnalité pourrait beaucoup apporter au projet. Selon moi, une API est totalement dans l'esprit de Zeste de Savoir en s'ouvrant au "monde" et donc, permettre à des projets tiers de l'utiliser (citons le projet Extensions Notificateurs) pour faciliter leur développement (pas cool de parser une page HTML et être dépendant de la structure d'une page qui peut évoluer à tout moment).

Objet de la proposition

Pour ceux qui vivent dans une grotte depuis plusieurs années, une API est une interface de programmation, une façade par laquelle un logiciel offre des services à d'autres logiciels. Par exemple, pour Zeste de Savoir, cela permettra de proposer, dans un format de données textuel comme le JSON, de fournir la liste des tutoriels selon certains filtres ou un tutoriel en particulier avec plus de détails.

L'API serait de type REST, donc orienté ressource. Chaque ressource est soumis à plusieurs méthodes HTTP du type : GET, POST, PUT et DELETE.

Informations sur une requête HTTP

Version de l'API

L'api est spécifié comme sous-domaine de l'URL pour ne pas la surcharger :

1
http://api.zestedesavoir.com/...

Pour la preprod, nous pouvons imaginer une adresse plus ou moins équivalente :

1
http://api.preprod.zestedesavoir.com/...
User-Agent

L'en-tête HTTP User-Agent doit être renseigné par le client.

Pour des informations complémentaires sur les User-Agent, rendez-vous sur son article Wikipedia.

Cross-Origin Request Sharing

Le serveur doit renvoyer les en-tête HTTP suivants afin d'éviter les soucis de CORS :

1
2
3
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link
Access-Control-Allow-Credentials: true

Pour des informations complémentaires sur CORS, rendez-vous sur son article Wikipedia version anglaise.

Considération générales

Gestion des droits d'accès aux ressources

La gestion des droits d'accès aux ressources n'a pas été statutée de manière définitive et restera du ressort du développeur de choisir la solution la plus adaptée en fonction du code existant dans le projet.

Ci-dessous les deux possibilités détaillées.

Possibilité n°1 : Plusieurs paths

La première solution consiste à faire plusieurs APIs (plusieurs paths REST) reposant sur un même socle côté serveur.

Avantages :

  • Plus simple de repérer les appels à l'API pour différents groupes
  • Ouvrir et fermer certaines routes directement dans un fichier de routes plutôt que dans des conditions (if/else).
  • Permet de mapper de façon plus naturelle sur le code serveur, plutôt que de router en fonction du token passé dans les headers HTTP.

Inconvénients :

  • Demande de rester cohérent entre tous les paths et de donner des responsabilités bien précises.
Possibilité n°2 : Un seul path et router sur le token

La seconde solution consiste à coller tout dans la même API et router en fonction du token passé dans les headers HTTP.

Avantages :

  • Gère un seul path par ressource. Les routes sont donc plus épurées.
  • Se rapproche sans doute plus du code actuel dans le back-end.

Inconvénients :

  • Pour un membre du groupe "staff" (par exemple), il ne peut pas "voir" des ressources comme un simple membre.
Formatage des erreurs

Lorsqu'un appel API provoque une erreur, il faut s'appuyer sur les codes HTTP standards. La réponse HTTP doit donc systématiquement utiliser un code interprétable pour le client. En plus de cela, le contenu de la réponse doit indiquer un code (par défaut le même que celui de la réponse HTTP, mais peut être différents pour donner de plus amples informations) ainsi qu'un message d'erreur compréhensible par le développeur du client de l'API.

Exemple, une réponse HTTP 404 devrait avoir le body suivant :

1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:404, 
            message:"Ce membre n'existe pas"
        }
    ]
}
Format d'entrée
  • A minima, le JSON doit être géré. Les autres formats sont facultatifs.
  • Lecture du header Content-Type, seule la valeur application/json est acceptée. Les autres formats donnent lieu à un code d'erreur HTTP 406 Not Acceptable. (NB : Twitter fait la différence entre le format des données envoyées et le format dans lequel les données sont envoyées. Dans le cas présent, ils renverraient une 404. A nous de décider ce qu'on fait).
1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:406, 
            message:"Le client doit envoyer des données au format application/json et positionner le header HTTP Content-Type de façon à le signaler"
        }
    ]
}
Format de sortie
  • A minima, le JSON doit être géré. Les autres formats sont facultatifs.
  • Lecture du header HTTP Accept, si la valeur application/json est absente, envoi d'un code HTTP 406 et de l'erreur suivante :
1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:406, 
            message:"Le client doit accepter des réponses au format JSON et le signaler en positionnant le header HTTP Accept avec la valeur application/json"
        }
    ]
}
Format de sortie pour les fichiers de contenu

Les ressources Markdown sont renvoyées dans le corps de la requête HTTP et suivant le format indiqué dans le header de la requête avec la clé X-Data-Format.

Les formats supportés seront markdown, html et texte.

Caching

Pour obtenir un premier système de cache, l'ETag sera utilisé. L'avantage se situe principalement sur un niveau : d'alléger le back-office dans sa charge de travail. Il ne sera pas toujours nécessaire d'aller chercher l'information s'il remarque qu'elle n'a pas été modifiée entre deux appels.

Pour y parvenir, voici le fonctionnement basique d'un ETag :

  1. Lorsqu'un client fait appel à l'API, la réponse inclue un ETag avec une valeur correspondant à un hash de la donnée retournée par l'API. La valeur de cette ETag est sauvegardée pour une prochaine utilisation si nécessaire.
  2. Au prochain même appel du client sur l'API, le client peut inclure dans le header de sa requête, l'attribut If-None-Match avec le précédent ETag renvoyé par le back-office.
  3. Si l'ETag n'a pas changé, le code de la réponse sera 304 - Not Modified et aucune donnée ne sera renvoyée.
  4. Si l'ETAg a changé, le back-office fera la requête et renverra la donnée avec un nouveau ETag. Ce nouveau ETag sera sauvegardé et utilisé pour de prochains appels.
Pagination
  • Les résultats de requête doivent être paginés. Par défaut, une page de résultat doit contenir 10 résultats

  • L'utilisateur indique la page de résultats qu'il souhaite récupérer à l'aide du paramètre &page=. Dans le cas où ce paramètre est invalide (n'est pas un nombre entier ou en dehors des limites de la pagination), l'API retourne une erreur 4001 ou 4002 :

1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:4001, 
            message:"le paramètre indiqué pour la pagination n'est pas un nombre entier valide"
        }
    ]
}
1
2
3
4
5
6
7
8
{
    errors: [
        {
            code:4002,
            message:"la page indiquée n'existe pas"
        }
    ]
}
  • L'API fournit des indications dans les headers de la réponse HTTP afin d'aider le client à naviguer dans les ressources paginées à l'aide du header Link. de la façon suivante :
1
2
3
4
Link: <http://zestedesavoir.com/membres/?search=toto&page=4>; rel="next",
  <http://zestedesavoir.com/membres/?search=toto&page=5>; rel="last",
  <http://zestedesavoir.com/membres/?search=toto&page=1>; rel="first",
  <http://zestedesavoir.com/membres/?search=toto&page=2>; rel="prev"

NB : Ne pas oublier de remettre tous les paramètres utilisés par le client dans l'URL (ici search=)

  • Dans de futures versions, l'utilisateur pourra indiquer le nombre de résultats souhaités en positionnant le paramètre &page_size= dans sa requête
Format de dates

Le format de dates retenu pour la communication avec le serveur est le format ISO 8601 (attention Java 7 nécessaire pour les utilisateurs de Java), pas de soucis en Python a priori.

En ce qui concerne les en-têtes HTTP, voici la liste des formats à gérer côté serveur.

Authentification

Permettre une authentification OAuth2 (dont la spécification du protocole est disponible via ce lien).

Son fonctionnement est le suivant :

  • Chaque client de l'API s'inscrit en tant qu'application tierce : ça signifie qu'il faut prévoir une table de stockage des clients de l'API
  • A la fin de l'inscription, le système devra renvoyer une ZDS_AUTH_KEY et une ZDS_AUTH_SECRET au développeur
  • Lorsqu'un membre veut se logguer au site via un client externe, le client devra donc envoyer à l'API:
    • le login : login de l'utilisateur sur le site
    • le password : mot de passe de l'utilisateur sur le site
    • le ZDS_AUTH_KEY : identifiant du client de l'API
    • le ZDS_AUTH_SECRET : clé secrète du client de l'API
  • Un token doit être généré qui signifie "le membre x veut se connecter à l'API via le client y". Ce token doit avoir une date d'expiration (à définir) et devra être utilisé par le client de l'API dans les entête HTTP.
  • A partir du token notamment on saura à quoi peut accéder tel ou tel membre.

L'authentification se base sur l'utilisation d'access token et de refresh token :

1
2
3
4
5
6
{
  "access_token": "<your-access-token>", 
  "scope": "read", 
  "expires_in": 86399, 
  "refresh_token": "<your-refresh-token>"
}
  1. L'utilisateur se log pour la première fois grâce à un client à l'API
  2. Le serveur lui renvoi son access token avec son expiration et son refresh token sans expiration
  3. Durant la session, l'access token est utilisé dans les requêtes
  4. A chaque fois que l'access token expire, le client refait une authentification avec le refresh token et la clé secrète

Ce processus a plusieurs intérêts :

  • Le token d'accès est le maillon faible, s'il est intercepté, tu peux prendre la place de l'utilisateur. En limitant sa durée de vie à une "session d'utilisation" (en gros inactivité pendant quelques minutes => expiration) voire en limitant sa durée de vie tout court à quelques minutes, tu limites le danger.

  • L'application n'a pas besoin de stocker le login et le mot de passe de l'utilisateur, et ne devrait pas le faire . Et c'est bien mieux comme ça pour tout le monde. (si jamais l'application est compromise, faille de sécurité, …)

  • Si la réponse du serveur contenant l'accessToken et le refreshToken est interceptée, l'accessToken va pouvoir être utilisé (mais c'est limité dans le temps… même si c'est extrêmement dangereux) par contre pour le refreshToken il faut le client_secret. Qui n'est pas dans la réponse du serveur.

  • Permet également à un client, n'importe où, n'importe quand d'invalider des accessToken, sans avoir besoin de saisir à nouveau ses login / mot de passe sur tous ses appareils. En gros, je dis "oulà, y'a une activité suspecte sur mon compte, un de mes accessToken a été intercepté, invalide les tous"

Route utilisée pour l'authentification :

URL : http://api.zestedesavoir.com/oauth2/access_token

Methode : POST

Headers : Aucun obligatoire

Paramètres :

  • client_id : Identifiant récupérer à la création du client.
  • client_secret : Secret récupérer à la création du client.
  • username : Username de l'utilisateur.
  • password : Mot de passe de l'utilisateur.
  • grant_type : password

Codes HTTP :

  • 200 : La réponse est renvoyée sans problème.

Permissions nécessaires : N/A

Exemple de résultat :

1
2
3
4
5
6
7
{
    "access_token": "27a59cd8b1f3322707781c7347ca20f8e141e594",
    "token_type": "Bearer",
    "expires_in": 31535999,
    "refresh_token": "e751cc50ae9a74e20d9d1f4890b86ab01ad5f343",
    "scope": "read"
}

Les moyens mis en œuvre

Comment nous pouvons développer cette API ? Il existe une librairie Django REST framework. Devant le fait que la plupart des méthodes d'une API se ressemble, il simplifie grandement sa réalisation pour les opérations usuelles CRUD.

L'approche pour la réalisation, au sens large, de l'API est la suivante : couvrir module par module, tel que les membres ou les forums, mais en suivant un workflow complet en lecture/écriture avec une ZEP pour chacun de ces modules. Cette solution offre des avantages, notamment :

  • Moins de développement est nécessaire pour une V1 comparé au temps de développement nécessaire pour couvrir tous les modules en lecture seule ;
  • Il pourrait y avoir plus de possibilités et d'utilité à sa sortie jusqu'à la V2 qui couvrirait d'autres modules ;
  • Cela assure qu'un workflow complet peut être couvert ; à savoir l'authentification, la consultation, l'écriture et la déconnexion. L'approche serait "verticale" ;
  • Les contributions se feront uniquement sur le module le plus stable du projet puisque les tutoriels et les articles ont pour vocation d'évoluer à court/moyen terme.

Différentes ZEP de l'API

ZEP Module concerné
ZEP-17 Membres
+9 -0

J'ajouterai que le framework que tu proposes permet d'utiliser OAuth 2 out of the box i.e de manière simplifiée.

C'est super important, parce que ça permettra de faire une API qui aidera à la rédaction sur tablettes (les 10'' permettront très clairement d'aller faire des modifications légères sur les tutos) ou pour une appli universelle windows store.

Personnellement, je pense que l'API doit être développée en deux versions de base :

  • v1 : lecture seule sur tout le site, j'entends par tous le site :

    • lecture des tutos
    • lecture des articles (via flux ou pas d'ailleurs)
    • lecture des forums + réponse
    • obtention des tags afin de naviguer un peu à la "stackoverflow"
    • tri des forums par tag
    • beta zestage des tuto : les apli mobile pourrait avoir un accès privilégié à cette fonctionnalité.
  • v2 :

    • édition des tutos
    • pull request sur les tutos
+2 -0

Je ne vois pas trop de cas d'utilisation pour la lecture seule des forums. A mon sens, si quelqu'un consulte le forum c'est que soit il a une question à poser, soit il veut aider les autres. Dans les deux cas, il a besoin de pouvoir écrire. +1 pour le reste.

+0 -0

Je pense qu'avant de découper en versions vous feriez mieux de déterminer encore, dans le détail, tout ce que vous attendez de cette API, puis qu'on réfléchisse à la spec technique, puis qu'enfin on dégage un plan de bataille. ;)

+2 -0

Je ne dis pas le contraire mais je remarque que la spec manque beaucoup de détail.

Il faut vraiment que quand on la lise, même si on sort d'une grotte, on comprenne le besoin pour cette API et la façon dont vous voulez qu'elle fonctionne. N'oublions pas que nous sommes sur un site à vocation pédagogique. ;)

+0 -0

Je ne dis pas le contraire mais je remarque que la spec manque beaucoup de détail.

nohar

Clairement, je n'étais pas bien inspiré hier …

Précisions les choses, qu'est ce que j'attends, personnellement, d'une API ZdS dans un premier temps :

  • Pouvoir récupérer la liste des tutoriels et un tutoriel en particulier avec son contenu ;
  • Pouvoir récupérer la liste des catégories des tutoriels ;
  • Pouvoir filtrer les tutoriels en fonction de ces catégories ;
  • Pouvoir récupérer la liste des articles et un article en particulier avec son contenu ;
  • Pouvoir récupérer la liste des topics ;
  • Pouvoir récupérer un topic et sa liste de postes qui le constitue ;
  • Pouvoir récupérer la liste des forums et des sous-forums ;
  • Pouvoir filtrer les topics en fonction de ces forums et sous-forums ;
  • Pouvoir récupérer la liste des tags ;
  • Pouvoir filtrer les topics en fonction de ces tags ;
  • Pouvoir m'authentifier et récupérer mes informations personnelles ;
  • Pouvoir récupérer la liste de mes alertes (pour les membres du staff) et tout le contenu qu'il regroupe (auteur, raison de l'alerte, topic concerné) ;
  • Pouvoir récupérer la liste de mes notifications et avoir un lien vers le topic concerné ;
  • Pouvoir récupérer la liste de mes messages privés et un message privé en particulier avec son contenu ;

Déjà, si j'avais toutes ces fonctionnalités de consultation, je pense que cela conviendra à la majorité des applications externes comme, par exemple, pour le projet externe actuel en cours de développement : l'extension Chrome qui affiche la liste des notifications d'un membre connecté sur le site.

la "rumeur" vient d'une réponse qu'un dev historique de ZDS m'a donné lorsqu'on parlait des optimisations.

J'ai dit "aujourd'hui, on doit faire un accès disque pour chaque extrait d'un chapitre et pour chaque membre, ce qui fait rapidement un gros nombre d'accès disque sur les pages de tuto". Alors on m'a répondu "en prod, chaque tuto possède un seul fichier JSON où tout est rassemblé, comme ça on n'a qu'un accès disque par visite".

Je ne vois pas trop de cas d'utilisation pour la lecture seule des forums. A mon sens, si quelqu'un consulte le forum c'est que soit il a une question à poser, soit il veut aider les autres. Dans les deux cas, il a besoin de pouvoir écrire.

btw03

Cas d'utilisation simple : L'extension notificateur. Admettons je veux rajouter une fonctionnalité de surveillance de forums au choix pour me prévenir de nouveaux sujets, avec ça je peux !

Du coup, la création de l'API implique je suppose des filtres. Quels sont-ils ? Date ? Quantité ? Autre ?

+2 -0

Oui pour la liste énoncée par AndrO, j'ai 50 idées en tête d'utilisation des APIs, pour l'instant je m'amuse avec le RSS mais franchement à terme y'a moyen de faire un truc vraiment sympa pour prendre le pouls de l'activité sur le site.

De là à en faire une app mobile avec PhoneGap / Cordova il n'y a qu'un pas assez simple à franchir.

La liste qui a été dressée (lecture seule) me convient tout à fait à titre perso et me semble intéressante pour une v1 à titre purement objectif.

A mon avis la prochaine étape est de coller une URL sur chacune des primitives proposée.

+2 -0

L'idée d'une API est plutôt interessante. Cependant, il va falloir faire la ZEP un peu plus précisément pour ne pas laisser le developpeur avec des imprécisions.

Je pense ici à des précisions sur les urls associés aux services demandées, sur les différents formats de sortie, sur le détail des données à sortir (vie privée, toussa).

Autre chose, vous avez l'air (si j'en crois les plussoiement du post d'Andr0 ) de vouloir pour une première version de l'API uniquement de la lecture seule sur l'ensemble du contenu. Ne serait-il pas plus interessant de se concentrer premièrement sur un workflow complet en lecture/écriture sur un module particulier tel que les forums ?

Les avantages étant que :

  • ça demanderait moins de dev pour une v1 si on compare au temps de developpement pour tous les modules en lecture uniquement
  • On a plus de possibilités/utilité en sortie en attendant la v2.
  • On s'assure qu'on peut couvrir tout le workflow (authentification -> consultation -> ecriture -> déconnexion ) avec une approche verticale
  • On travaille uniquement sur le module le plus stable du projet (les forums) car les tutos/articles on pour vocation d'évoluer
  • d'autres raison que j'oublie.

Perso, je décorelle complètement, dans mon esprit, la ZEP du plan de développement qu'on adoptera pour l'implémenter.

Je rejoins firm1 sur la nécessité, surtout sur un morceau aussi gros, de vraiment rédiger la ZEP la plus précise possible, et donc de faire rapidement des révisions pour intégrer ce qui a déjà été dit. Ça évite de se taper toute la discussion pour savoir où on en est.

+1 -0

Autre chose, vous avez l'air (si j'en crois les plussoiement du post d'Andr0 ) de vouloir pour une première version de l'API uniquement de la lecture seule sur l'ensemble du contenu. Ne serait-il pas plus interessant de se concentrer premièrement sur un workflow complet en lecture/écriture sur un module particulier tel que les forums ?

Firm1

Tu propose une approche intéressante … Mais qui bloquerai/retarderai le développement d'éventuelles application. L'avantage d'avoir tout en lecture, c'est de pouvoir rendre plus efficace le notificateur et d'éventuelles applications mobiles qui pourraient avoir accès à tout le site en lecture, plus tôt dans le dévellopement

Mais tes arguments sont tout à fait valides :)

EDIT : le DTC a raison, ceci dit.

+0 -0

Tu propose une approche intéressante … Mais qui bloquerai/retarderai le développement d'éventuelles application. L'avantage d'avoir tout en lecture, c'est de pouvoir rendre plus efficace le notificateur et d'éventuelles applications mobiles qui pourraient avoir accès à tout le site en lecture.

pierre_24

Je ne sais pas quelle application ça bloquerait. On pourrait voir des applications externes fleurir dont le but est juste de poster sur les forums, et de recevoir les notifications venant uniquement du forum dans un premier temps. ça me semble déjà plus interessant que d'avoir des données purement lecture seule (et il faudrait à chaque fois aller sur le site pour écrire).

L'idée étant de proposer une approche par module mais àa chaque fois sur toute le workflow.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte