API Web¶§
Motivation¶§
Hétérogénéité des clients¶§
- client mobile
- minimiser les échanges (bande passante, batterie, forfait...)
- gérer l’affichage coté client
- autre serveur
- exemple : agence de voyage / SNCF
- pas besoin d’affichage
Note
Même dans le cas d’une application HTML “classique”, on peut bénéficier d’une API grâce à AJAX.
Définition¶§
- API = Application Programming Interface
- Permet donc de programmer une application s’appuyant sur le service Web
- (par opposition à une utilisation “manuelle” à travers un navigateur).
Comment tester une API ?¶§
- Sous Firefox : RestClient
- Sous Chrome : PostMan
Le format JSON¶§
Présentation¶§
- JavaScript Object Notation (JSON) est un format largement utilisé pour échanger des données sur le Web.
- Inspiré du langage Javascript, il est en également bien adapté à de nombreux langages, dont Python.
Sémantique¶§
JSON permet de représenter les données Python suivantes :
None
,- des chaînes de caractères,
- des nombres (entiers et flottants),
- des booléens,
- des listes,
- des dictionnaires dont les clés sont des chaînes de caractères.
Exemple de données représentables en JSON¶§
{
'id': 1,
'label': "File",
'tooltip': None,
'items': [
{'label': "New", 'visible': True},
{'label': "Open", 'visible': True},
{'label': "Close", 'visible': False},
],
}
Syntaxe¶§
Très similaire à Python, à quelques exceptions près :
None
s’écritnull
,True
s’écrittrue
,False
s’écritfalse
,- les chaînes de caractères sont obligatoirement encadrées par des double quotes.
Note
Ces variantes proviennent principalement du langage Javascript, mais aussi d’une volonté de garder le langage JSON simple.
En terme de vocabulaire,
- on parle de tableau (array) plutôt que de liste,
- on parle d’objet plutôt que de dictionnaire.
Exemple précédent dans la syntaxe JSON¶§
{
"id": 1,
"label": "File",
"tooltip": null,
"items": [
{"label": "New", "visible": true},
{"label": "Open", "visible": true},
{"label": "Close", "visible": false}
]
}
Utilisation en python¶§
En Python standard :
import json
# avec des fichiers
data = json.load(open("file.json"))
json.dump(data, open("file2.json", "w"))
# avec des chaînes de caractères
txt = json.dumps(data)
data2 = json.loads(txt)
Dans Flask:
request.get_json()
fournit le contenu JSON de la requête (le cas échéant), ouNone
si le contenu est absent ou dans autre format.flask.jsonify(data)
produit un objet réponse dont- l’en-tête
content-type
estapplication/json
, et - le contenu est la sérialisation JSON des données passées en paramètre.
- l’en-tête
Verbes HTTP¶§
Introduction¶§
Avec HTML, on utilise exclusivement les verbes GET
et POST
,
mais HTTP définit d’autres verbes,
qui sont particulièrement utiles lors de la définition d’APIs.
GET¶§
- n’attend pas contenu
- réclame une représentation de la ressource
- est sans effet de bord (i.e. ne modifie pas l’état de la ressource)
- rend possible l’utilisation des caches
HEAD¶§
- a la même sémantique que GET
- mais ne réclame que les en-têtes
- et pas la représentation de la ressource
PUT¶§
- attend un contenu
- demande la création ou la modification de la ressource
- de sorte que le nouvel état de la ressource corresponde au contenu fourni
- a donc un effet de bord idempotent
DELETE¶§
- n’attend pas de contenu
- demande la suppression de la ressource
- a donc un effet de bord idempotent
POST¶§
- attent un contenu
- peut avoir n’importe quel effet de bord (y compris non-idempotent)
- est souvent utiliser pour créer une ressource sans connaître a priori son URL
API RESTful¶§
- On affecte à chaque ressource une URL qui l’identifie,
- et chaque URL identifie une ressource.
- Les manipulations se font en utilisant les verbes HTTP appropriés.
Avertissement
Dans le TP précédent,
nous n’avons pas tout à fait respecté ces principes...
(exemple : quelle ressource identifie l’URL /Genes/del/<id>
?)
Note
L’acronyme REST et ses dérivés (comme “RESTful”) a été inventé par Roy Fieldings dans sa thèse.
Patron “collection/élément”¶§
- une collection contient une liste d’éléments
- la collection supporte
GET
pour obtenir la liste de ses éléments (avec leurs URLs)POST
pour créer un nouvel élément
- chaque élément supporte
GET
pour en obtenir une description détailléePUT
pour le modifierDELETE
pour le supprimer
Mise en œuvre en Flask¶§
Il est possible d’implémenter différents verbes dans la même vue :
@app.route("/bidules/", methods=['GET', 'POST'])
def bidules():
if request.method == 'GET':
# ...
else:
# ...
Mais il est également possible de les implémenter dans différentes vues, (et donc d’avoir plusieurs routes ayant la même URL, mais des verbes différents) :
@app.route("/bidules/", methods=['GET'])
def bidules_list():
# ...
@app.route("/bidules/", methods=['POST'])
def bidules_new():
# ...
Le choix entre les deux options dépend de la quantité de code commun aux traitements des différents verbes.
Gestion du cache¶§
Introduction¶§
- HTTP est conçu pour tirer parti des caches.
- Cette gestion de cache n’est pas spécifique au développement d’APIs, elle est également utile pour tous les fichiers réutilisés dans plusieurs pages (images, feuilles de styles, etc).
- Mais elle est d’autant plus importante avec les APIs : une machine peut lancer des requêtes beaucoup plus vite qu’un humain.
En-tête Cache-control
¶§
Voici deux valeurs typiques pour cache-control
dans une réponse :
max-age=T
(ou T est une durée en secondes) : informe le client sur la durée pendant laquelle il est pertinent de garder l’information en cache.no-cache
informe le client qu’il ne doit pas conserver cette réponse en cache, pour le forcer à ré-interroger le serveur à la prochaine requête.
En-tête ETag
¶§
- Un etag (entity tag) est une chaîne de caractère identifiant une version de la ressource. L’etag change chaque fois que la ressource est modifiée.
- Elle permet au client d’indiquer au serveur la dernière version de la ressource dont il a connaissance.
- Ceci ce fait en conditionnant l’exécution d’une requête
à certaines versions de la ressource, avec les en-têtes
If-Match
: seulement si la ressource correspond à l’un des etags fournisIf-None-Match
: seulement si la ressource ne correspond à aucun des etags fournis
Mise en œuvre en Flask¶§
L’objet
Response
a une méthodeset_etag
qui permet de lui associer un etag.Note
Il ne faut pas utiliser
resp.headers["etag"] = my_etag
car les etags doivent être encodés d’une manière spécifique.L’objet
request
a des attributsif_match
etif_none_match
qui contiennent la liste des etags fournis par le client (correctement décodés).Exemple d’utilisation :
@app.route('/my/url') def smart_view(): etag = get_etag() if etag in request.if_none_match: resp = make_response("", 304) # 304 not modified resp.set_etag(etag) return resp # here, make the "normal" response
Note
Flask offre une manière plus intégrée de gérer les requêtes GET conditionnelles :
@app.route('/my/url') def smart_view(): # here, make the "normal" response resp.set_etag(get_etag()) resp.make_conditional(request) # transform the response according to conditional request return resp
La réponse sera modifiée pour tenir compte de l’en-tête
If-None-March
, mais également de l’en-têteRange
(RFC 7233). Cette solution est donc plus concise et plus puissante, mais elle oblige à construire systématiquement la réponse, ce qui peut avoir un coût significatif (accès à la base de données).Le choix de l’une ou l’autre solution est donc une question de compromis.