🌐 API webservices et HTTP, FastAPI.

Dans cette partie, on va voir ce qu’est un web service, pourquoi on en utilise et comment en construire un en Python.

Script vs Application

Dans le cours précédent, on a parlé des langages compilés et interprétés. Maintenant, voyons la différence entre un script et une application, deux types de programmes qui ont des usages distincts :

  • Les scripts : ce sont des programmes conçus pour effectuer une tĂąche prĂ©cise et ponctuelle. Ils sont gĂ©nĂ©ralement exĂ©cutĂ©s par des langages interprĂ©tĂ©s comme Python.

    Un script peut ĂȘtre lancĂ© plusieurs fois, mais dans ce cas, on parle d’un batch : on l’exĂ©cute Ă  intervalle rĂ©gulier, il fait son travail, puis s’arrĂȘte.

  • Les applications : elles sont conçues pour fonctionner en continu, tant qu’on ne les ferme pas. Elles sont souvent plus complexes et Ă  l’origine, elles Ă©taient dĂ©veloppĂ©es en langages compilĂ©s (comme Java ou C++), mais aujourd’hui, ce n’est plus forcĂ©ment le cas.

Applications et architectures applicatives

Une application est un programme conçu pour rĂ©pondre Ă  des demandes et fournir un service. Selon la maniĂšre dont elles sont conçues, on distingue plusieurs types d’applications :

  • Applications “client lourd” : elles sont entiĂšrement installĂ©es sur un ordinateur ou un appareil, et exĂ©cutent toutes leurs fonctionnalitĂ©s en local.
    Exemple : le logiciel interne d’une machine Ă  cafĂ©.

  • Applications “client / serveur” : elles sont partiellement installĂ©es sur l’ordinateur de l’utilisateur, mais communiquent avec un serveur distant pour accĂ©der aux donnĂ©es ou exĂ©cuter certaines tĂąches.
    Exemple : l’outil Git en ligne de commande, qui envoie et rĂ©cupĂšre des fichiers depuis un serveur distant.

Ce modĂšle client / serveur est largement utilisĂ©, car il simplifie la gestion des mises Ă  jour : une seule mise Ă  jour sur le serveur suffit pour que tous les utilisateurs en bĂ©nĂ©ficient. De plus, les donnĂ©es sont centralisĂ©es, ce qui Ă©vite les pertes d’information dispersĂ©es sur plusieurs appareils.

  • Applications “n-tiers” : elles suivent le principe du client / serveur, mais avec plusieurs couches intermĂ©diaires pour mieux rĂ©partir les traitements.
    Exemple : SNCF Connect, qui repose sur plusieurs services interconnectés.

OĂč se place un Web Service ?

Un web service repose au minimum sur une architecture client / serveur, mais il peut aussi s’intĂ©grer dans une architecture n-tiers si nĂ©cessaire. Son rĂŽle est de fournir un service accessible Ă  distance, permettant Ă  diffĂ©rentes applications de communiquer entre elles.

Voici comment ça fonctionne :

  1. Un client (site web, application, script
) envoie une requĂȘte au serveur dans un format standard : XML, JSON ou HTTP.
  2. Cette requĂȘte est transmise au serveur via un protocole comme SOAP, REST ou HTTP.
  3. Le serveur traite la demande et rĂ©pond dans le mĂȘme format : XML, JSON ou HTTP.

En résumé, un web service est un moyen de connecter des applications entre elles, en utilisant une communication standardisée.

Protocole HTTP

Protocoles de communication

Pour qu’une communication puisse avoir lieu entre deux machines sur un rĂ©seau, il faut suivre un ensemble de rĂšgles, appelĂ©es protocoles.

Les deux protocoles fondamentaux utilisés pour la transmission des données sont :

  • TCP (Transmission Control Protocol) : permet un Ă©change de donnĂ©es fiable et contrĂŽlĂ©. Chaque paquet envoyĂ© est vĂ©rifiĂ©, garantissant une transmission sans erreur.
  • UDP (User Datagram Protocol) : plus rapide mais moins sĂ©curisĂ©, il ne vĂ©rifie pas la bonne rĂ©ception des paquets, ce qui peut entraĂźner des pertes d’informations.

Ces protocoles constituent la base de la communication entre les applications et les réseaux (LAN, MAN, WAN, PAN).

HTTP et transmission des données

Le protocole HTTP (HyperText Transfer Protocol) est un protocole de niveau supĂ©rieur basĂ© sur TCP. Il permet l’échange de ressources (pages web, API, fichiers
) entre un client et un serveur.

Un client peut envoyer des requĂȘtes HTTP au serveur sur :

  • Le port 80 pour des connexions HTTP classiques.
  • Le port 443 pour des connexions sĂ©curisĂ©es en HTTPS (nĂ©cessitant des certificats de chiffrement).
Pour aller plus loin
Spécification RFC HTTP 1.1 : https://www.rfc-editor.org/rfc/rfc2616
Pour aller plus loin
Three-way handshake TCP : https://fr.wikipedia.org/wiki/Three-way_handshake

Client HTTP - Quelques rappels

Un client HTTP est un programme ou une bibliothĂšque capable d’envoyer des requĂȘtes HTTP Ă  un serveur pour rĂ©cupĂ©rer des informations ou effectuer des actions.

Exemples de clients HTTP

  • Navigateurs web : Chrome, Firefox, Edge

  • Outils en ligne de commande : curl, httpie

  • BibliothĂšques de programmation : requests en Python, axios en JavaScript


Ces clients peuvent envoyer plusieurs types de requĂȘtes, appelĂ©es mĂ©thodes HTTP :

MéthodeDescription
GETRécupÚre une ressource depuis le serveur.
POSTEnvoie des données pour créer une nouvelle ressource.
PUTAjoute ou met Ă  jour une ressource sur le serveur.
DELETESupprime une ressource si elle existe.

RequĂȘte HTTP - Structure d’une requĂȘte HTTP

Une requĂȘte HTTP comprend trois Ă©lĂ©ments principaux :

  1. Une ligne de requĂȘte

    • La mĂ©thode (ex : GET)
    • L’URL de la ressource demandĂ©e
    • La version du protocole HTTP
  2. Des en-tĂȘtes HTTP

    • MĂ©tadonnĂ©es sur la requĂȘte (type de contenu, authentification
)
  3. Un corps de message (facultatif)

    • Contenu de la requĂȘte, souvent en JSON ou XML

Exemple d’envoi d’une requĂȘte avec curl en ligne de commande :

curl http://localhost:8000

Exemple en Python avec requests :

import requests

response = requests.get("http://google.com")
print(response.status_code)  # Affiche le code de statut
# print(response.json())  # Si la réponse est en JSON
# print(response.text)  # Si la réponse est en texte brut
Pour aller plus loin Le client léger qu'on préconise pour python c'est requests, on retrouve la doc ici :

Faire une requĂȘte avec le module requests

Pour aller plus loin

Souvent, les requĂȘtes Curl sont fournies dans la documentation Swagger pour accĂ©der aux services. Le site Curlconverter permet de convertir ces requĂȘtes Curl en requĂȘtes dans le langage de votre choix. Cela peut ĂȘtre utile si vous devez interagir avec une API externe dans votre programme.

RequĂȘte HTTP - Structure d’une rĂ©ponse HTTP

Une réponse HTTP contient :

  1. Une ligne de statut (ex : 200 OK, 404 Not Found)
  2. Des en-tĂȘtes HTTP (type de contenu, encodage
)
  3. Un corps de message (souvent un fichier HTML ou JSON)

Référence complÚte des codes de réponse HTTP

Les différents codes retours :

  • Code retours 2XX: 200, 201 … - La requĂȘte s’est bien passĂ©e et elle a eu un effet dans le systĂšme.
  • Code retours 3XX: 300,301,302 … - La requĂȘte est entrĂ©e dans le systĂšme et a Ă©tĂ© redirigĂ©e.
  • Code retours 4XX: 400,401,402,403,404 … => La requĂȘte a Ă©tĂ© rejetĂ©e par le systĂšme car l’utilisateur de l’API n’a pas effectuĂ© une action valide. (non authentifiĂ©, demande de ressources non prĂ©sentes)
  • Code retours 5XX: 500, 502 - La requĂȘte a Ă©tĂ© rejetĂ©e par l’application pour des raisons internes au systĂšme : plantage interne, le serveur n’Ă©tait pas prĂȘt, etc…

Les navigateurs web sont des clients HTTP qui envoient des requĂȘtes GET lorsqu’on navigue sur une page et POST lorsqu’on soumet un formulaire.

💡 Bonnes pratiques : Il est utile d’implĂ©menter des codes de rĂ©ponse HTTP pertinents dans vos applications pour faciliter le diagnostic des erreurs et l’interaction avec les clients.

Les API : une forme de Web Service

DĂ©finition de la CNIL (Commission Nationale de l’Informatique et des LibertĂ©s) d’une API :

Une API (Application Programming Interface ou « Interface de Programmation d’Application ») est une interface logicielle qui permet de « connecter » un logiciel ou un service Ă  un autre logiciel ou service afin d’échanger des donnĂ©es et des fonctionnalitĂ©s.

Les API offrent de nombreuses possibilitĂ©s, comme la portabilitĂ© des donnĂ©es, la mise en place de campagnes de courriels publicitaires, des programmes d’affiliation, l’intĂ©gration de fonctionnalitĂ©s d’un site sur un autre ou l’open data. Elles peuvent ĂȘtre gratuites ou payantes.

Quel est l’IntĂ©rĂȘt des API ?

Objectif :
Diffuser des donnĂ©es et des services Ă  destination d’autres applications.

Pour mieux comprendre :

Voici la rĂ©ponse d’une application de type IHM (Interface Homme-Machine) :

<li class="list__item">
  <a class="block__link block__link_img" href="/270229/evt.htm">
    <picture>
      <source
        media="(min-width: 650px)"
        srcset="/zg/r115-165-0/vz-F46C96DB-9F48-44B1-9297-A85CBDD9FF1B.jpeg"
      />
      <source
        media="(max-width: 649px)"
        srcset="/zg/r115-165-0/vz-F46C96DB-9F48-44B1-9297-A85CBDD9FF1B.jpeg"
      />
      <img
        class="pub__img"
        alt="Apollo World Live en live streaming Apollo Théùtre - Salle Apollo 360"
        title="Apollo World Live en live streaming Apollo Théùtre - Salle Apollo 360"
        src="/zg/r115-165-0/vz-F46C96DB-9F48-44B1-9297-A85CBDD9FF1B.jpeg"
      />
      <div class="block__offers-container">
        <span class="block__offers block__offers_price">
          <span class="text__mini">À partir de</span> 10€
        </span>
      </div>
    </picture>
  </a>
  <a href="/270229/evt.htm" class="block__link block__link_title">
    <span>
      <b style="color:#1A2E41">Apollo World Live en live streaming</b>
    </span>
  </a>
</li>
<li class="list__item">
  <a class="block__link block__link_img" href="/270099/evt.htm">
    <picture>
      <source
        media="(min-width: 650px)"
        srcset="/zg/r115-165-0/vz-0196DA94-7B3F-4164-B114-6B54405395ED.jpeg"
      />
      <source
        media="(max-width: 649px)"
        srcset="/zg/r115-165-0/vz-0196DA94-7B3F-4164-B114-6B54405395ED.jpeg"
      />
      <img
        class="pub__img"
        alt="Impro Visio en Live Streaming My Digital Arena"
        title="Impro Visio en Live Streaming My Digital Arena"
        src="/zg/r115-165-0/vz-0196DA94-7B3F-4164-B114-6B54405395ED.jpeg"
      />
      <div class="block__offers-container">
        <span class="block__offers block__offers_price">
          <span class="text__mini">À partir de</span> 12€
        </span>
      </div>
    </picture>
  </a>
  <a href="/270099/evt.htm" class="block__link block__link_title">
    <span>Impro Visio en Live Streaming</span>
  </a>
</li>
<li class="list__item">
  <a class="block__link block__link_img" href="/269015/evt.htm">
    <picture>
      <source
        media="(min-width: 650px)"
        srcset="/zg/r115-165-0/vz-577747CF-85FB-4504-87E8-0B1B585E9324.jpeg"
      />
      <source
        media="(max-width: 649px)"
        srcset="/zg/r115-165-0/vz-577747CF-85FB-4504-87E8-0B1B585E9324.jpeg"
      />
      <img
        class="pub__img"
        alt="Le Roi Lion Théùtre de la Tour Eiffel"
        title="Le Roi Lion Théùtre de la Tour Eiffel"
        src="/zg/r115-165-0/vz-577747CF-85FB-4504-87E8-0B1B585E9324.jpeg"
      />
      <div class="block__offers-container">
        <span class="block__offers block__offers_price">
          <span class="text__mini">À partir de</span> 23€
        </span>
      </div>
    </picture>
  </a>
  <a href="/269015/evt.htm" class="block__link block__link_title">
    <span>Le Roi Lion</span>
  </a>
</li>

Et voici la rĂ©ponse d’une application de type API :

{
  "top": [
    {
      "title": "Apollo World Live en live streaming",
      "url": "/270229/evt.htm",
      "image": "/zg/r115-165-0/vz-F46C96DB-9F48-44B1-9297-A85CBDD9FF1B.jpeg",
      "price": "10€"
    },
    {
      "title": "Impro Visio en Live Streaming",
      "url": "/270099/evt.htm",
      "image": "/zg/r115-165-0/vz-0196DA94-7B3F-4164-B114-6B54405395ED.jpeg",
      "price": "12€"
    },
    {
      "title": "Le Roi Lion",
      "url": "/269015/evt.htm",
      "image": "/zg/r115-165-0/vz-577747CF-85FB-4504-87E8-0B1B585E9324.jpeg",
      "price": "23€"
    }
  ]
}

Selon vous, quelle est la réponse la mieux adaptée pour échanger des données ?

Différence entre un site Web et un Web Service :

  • Site Web : Fournit des donnĂ©es et leur mise en forme (HTML + CSS) pour ĂȘtre lisibles par des humains.

  • Web Service (dont les API font partie) : Fournit uniquement des donnĂ©es brutes, indĂ©pendantes de la prĂ©sentation, destinĂ©es Ă  ĂȘtre exploitĂ©es par d’autres applications.

Les API sont Ă©galement une belle prouesse pour l’interopĂ©rabilitĂ© des services. En effet, elles rĂ©pondent Ă  une norme agnostique au langage.

Exemple : si je développe une application pour afficher des prévisions météorologiques, je peux créer mes modÚles de Machine Learning en Python et mes régressions en R. En exposant les résultats via deux API au format identique, mon site Web pourra afficher les résultats de maniÚre cohérente, quel que soit le langage utilisé pour les générer.

Comment fonctionne une API ?

Une API (Interface de Programmation Applicative) permet Ă  diffĂ©rentes applications de communiquer entre elles en exposant des services accessibles via des ressources. Ces services, appelĂ©s endpoints, dĂ©clenchent l’exĂ©cution de fonctions spĂ©cifiques lorsqu’un utilisateur en fait la demande. L’API dĂ©finit donc comment les clients (autres applications ou utilisateurs) peuvent interagir avec elle, en mettant l’accent sur ses fonctionnalitĂ©s tout en cachant ses dĂ©tails internes.

En Python, plusieurs frameworks permettent de créer des API web :

  • Django : Un framework complet, idĂ©al pour les grands projets. Il intĂšgre des outils prĂȘts Ă  l’emploi pour gĂ©rer les bases de donnĂ©es, l’authentification, etc.
  • Flask : Plus lĂ©ger et flexible, il permet de mettre en place rapidement un service HTTP, qu’il soit statique ou une API.
  • FastAPI : Plus rĂ©cent et optimisĂ©, il facilite l’autodocumentation et la gestion des donnĂ©es grĂące Ă  la sĂ©rialisation et dĂ©sĂ©rialisation.

Une API reçoit des requĂȘtes HTTP et y rĂ©pond avec des rĂ©ponses HTTP, qui incluent un code de statut, des en-tĂȘtes (headers) et un corps (body).

Les API utilisent le protocole HTTP et retournent des données dans des formats indépendants des langages de programmation.

Les formats les plus courants sont :

  • JSON (trĂšs utilisĂ© car compatible avec JavaScript, omniprĂ©sent sur le web) :
    {
      "employees": [
        { "firstName": "John", "lastName": "Doe" },
        { "firstName": "Anna", "lastName": "Smith" },
        { "firstName": "Peter", "lastName": "Jones" }
      ]
    }
    
  • XML (plus courant dans les systĂšmes anciens) :
    <?xml version="1.0" standalone="yes"?>
    <employees>
        <employee>
            <firstName>John</firstName>
            <lastName>Doe</lastName>
        </employee>
        <employee>
            <firstName>Anna</firstName>
            <lastName>Smith</lastName>
        </employee>
        <employee>
            <firstName>Peter</firstName>
            <lastName>Jones</lastName>
        </employee>
    </employees>
    

Les requĂȘtes envoyĂ©es Ă  une API sont aussi souvent en JSON ou XML.

Remarque : une fois pythonisés ces formats seront récupérés comme des objets ou des dictionnaires. Mais pour cela il faut parler de sérialisation / désérialisation.

Sérialisation / Désérialisation

Un des enjeux du travail avec des ressources externes d’un programme est de savoir convertir les entrĂ©es d’un programme en des formes connues de notre programme (dĂ©sĂ©rialisation), mais Ă©galement de pouvoir exposer des objets connus de notre programme dans un format utilisable par d’autres programmes (sĂ©rialisation).

En Python, vous ĂȘtes plutĂŽt habituĂ©s Ă  utiliser la sĂ©rialisation avec le module intĂ©grĂ© json.

Sérialisation en JSON

import json

# Objet Python (dictionnaire)
data = {"nom": "Alice", "Ăąge": 25, "ville": "Paris"}

# Conversion en JSON (sérialisation)
json_data = json.dumps(data)  # ChaĂźne JSON
print(json_data)  # {"nom": "Alice", "Ăąge": 25, "ville": "Paris"}

Désérialisation depuis JSON

# Conversion JSON -> objet Python (désérialisation)
data_reconstruit = json.loads(json_data)
print(data_reconstruit["nom"])  # Alice

Cette logique doit ĂȘtre intĂ©grĂ©e dans la conception de vos logiciels : toujours contrĂŽler les entrĂ©es et les convertir en un format connu du systĂšme.

Par exemple, en programmation orientĂ©e objet, il sera attendu qu’entre deux couches de sĂ©paration architecturale, vous introduisiez des objets de type DTO (Data Transfer Object). Ainsi, des DTO seront utilisĂ©s entre la couche service et DAO, mais aussi entre la couche contrĂŽleur et service. Cela permet une conversion et, par consĂ©quent, de pĂ©renniser le modĂšle dans chacune des couches de votre systĂšme.

Tout cela a dĂ©jĂ  Ă©tĂ© expliquĂ© dans cette partie du cours 🔰 Bonnes pratiques du dĂ©veloppement et design patterns.

Pour aller plus loin
Architecture hexagonale => Gérer les entrées/sorties => https://alistair.cockburn.us/hexagonal-architecture/

Synchronicité / Asynchronicité

L’asynchronisme est particuliĂšrement avantageux lorsqu’il s’agit d’effectuer des appels Ă  des services Web, car il permet Ă  votre programme de poursuivre son exĂ©cution pendant l’attente des rĂ©ponses.

Prenons l’exemple d’un programme qui nĂ©cessite plusieurs appels Ă  des services Web pour obtenir des donnĂ©es. Si vous procĂ©dez de maniĂšre synchrone, c’est-Ă -dire en rĂ©alisant les appels un Ă  un dans l’ordre, votre programme devra attendre la rĂ©ponse de chaque appel avant de continuer avec le suivant. Cela peut ĂȘtre chronophage, surtout si les rĂ©ponses mettent du temps Ă  arriver.

En revanche, en adoptant une approche asynchrone, vous pouvez envoyer plusieurs appels simultanĂ©ment sans avoir Ă  attendre la fin de chacun avant de passer au suivant. Ainsi, votre programme peut continuer Ă  s’exĂ©cuter pendant que les requĂȘtes sont traitĂ©es, et il peut gĂ©rer les rĂ©ponses dĂšs leur arrivĂ©e.

Avec Python et la librairie asyncio, dĂšs que vous avez besoin du rĂ©sultat d’une requĂȘte asynchrone, vous pouvez utiliser l’instruction await, qui suspend l’exĂ©cution jusqu’Ă  ce que toutes les fonctions asynchrones prĂ©cĂ©dentes soient complĂštes.

import asyncio
import aiohttp

async def fetch_data(url):
    print(f"DĂ©but de la requĂȘte : {url}")
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            data = await response.json()
            print(f"Données récupérées depuis {url}: {data}")
            return data

async def process_data(data1, data2):
    print("Traitement des données...")
    await asyncio.sleep(1)  # Simule un traitement
    print(f"Données traitées : {data1} & {data2}")

async def main():
    url1 = "https://jsonplaceholder.typicode.com/todos/1"
    url2 = "https://jsonplaceholder.typicode.com/todos/2"

    # ExĂ©cuter les deux requĂȘtes en parallĂšle
    task1 = asyncio.create_task(fetch_data(url1))
    task2 = asyncio.create_task(fetch_data(url2))

    # Attendre que les deux requĂȘtes soient terminĂ©es
    data1 = await task1
    data2 = await task2

    # Traiter les données obtenues
    await process_data(data1, data2)

    print("Processus terminé !")

asyncio.run(main())
Attention : l'introduction de l'asynchronicitĂ© dans des fonctions oĂč elle n'est pas nĂ©cessaire peut alourdir le code.

Pour aller plus loin

Hébergement : ASGI(Asynchronous Server Gateway Interface) vs WSGI(Web Server Gateway Interface) : https://www.youtube.com/watch?v=vKjCkeJGbNk&pp=ygUJYXNnaSB3c2dp

Quelques mots sur FastAPI

FastAPI est un framework web qui simplifie la crĂ©ation d’API en Python, notamment par une gestion efficace de la sĂ©rialisation et de la dĂ©sĂ©rialisation des donnĂ©es.

Il repose sur deux autres frameworks :

  • Starlette pour les fonctionnalitĂ©s liĂ©es au web.
  • Pydantic pour la gestion des donnĂ©es.

FastAPI est open source et est utilisé par de nombreuses entreprises, telles que Netflix, Uber et Microsoft.

FastAPI facilite la gestion des requĂȘtes asynchrones.

Installation

Pour installer FastAPI, utilisez les commandes suivantes :

pip install fastapi
pip install "uvicorn[standard]"

Création de votre application

  1. Créez un fichier nommé main.py avec le contenu suivant :
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()

class Item(BaseModel):
    """
    Représente un article avec ses détails.
    
    - **name**: Le nom de l'article.
    - **description**: Une description optionnelle de l'article.
    - **price**: Le prix de l'article.
    - **tax**: Une taxe optionnelle associée à l'article.
    - **tags**: Une liste de tags associés à l'article.
    """
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []

@app.post("/items/", summary="Créer un article", description="Crée un nouvel article avec les détails fournis.")
async def create_item(item: Item) -> Item:
    """
    CrĂ©e un nouvel article avec les informations fournies dans le corps de la requĂȘte.
    
    - **item**: Détails de l'article à créer.
    
    Renvoie l'article créé.
    """
    return item

@app.get("/items/", summary="Lire les articles", description="RécupÚre une liste d'articles prédéfinis.")
async def read_items() -> list[Item]:
    """
    RécupÚre une liste d'articles prédéfinis.

    Renvoie une liste d'articles avec des noms et des prix.
    """
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]
  1. Lancez le serveur avec la commande :
uvicorn main:app --reload

Votre application sera disponible Ă  l’adresse suivante, que vous pouvez ouvrir dans votre navigateur :
http://127.0.0.1:8000/

Ressources supplémentaires

Quelques exemples pour vous permettre de dĂ©marrer l’implĂ©mentation de vos API :

Ressources officielles: