Tower Rust : guide complet avec exemples concrets

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Qu'est-ce que Tower et pourquoi l'utiliser ?

Tower est une bibliothèque fondamentale de l'écosystème Rust qui fournit des abstractions pour construire des services réseau asynchrones modulaires et composables, ainsi que des intergiciels (middleware). Ce n'est pas un framework HTTP prêt à l'emploi (comme Actix-web ou Axum), mais plutôt un "matériau de construction" pour ceux-ci. Axum, par exemple, est construit sur Tower.

L'idée centrale de Tower est le trait Service, qui représente une fonction asynchrone transformant une requête (Request) en réponse (Response). Un middleware (ou "Layer") dans Tower permet d'encapsuler un service dans un autre, en y ajoutant un comportement supplémentaire (journalisation, authentification, délais d'attente, équilibrage de charge, etc.) sans modifier le code source du service d'origine.

Pourquoi utiliser Tower ?

  • Modularité : Vous pouvez décomposer une logique complexe de traitement de requêtes en petits composants indépendants et réutilisables (services et middleware).
  • Composabilité : Les middlewares se combinent facilement en les superposant comme des couches (layers).
  • Asynchronisme : Tower est conçue pour fonctionner avec le Rust asynchrone (tokio, async-std) et prend en charge les futures.
  • Testabilité : Chaque composant peut être testé de manière isolée.

Installation de Tower

Tower est une bibliothèque Rust qui s'installe via le gestionnaire de paquets Cargo. Ajoutez la dépendance dans votre fichier Cargo.toml :

[dependencies]
tower = { version = "0.4", features = ["full"] } # "full" active toutes les fonctionnalités
tokio = { version = "1", features = ["full"] } # Pour l'exécution asynchrone

Ensuite, exécutez la commande dans le terminal :

cargo build

La bibliothèque sera téléchargée et compilée avec votre projet.

Premiers pas : Votre premier service et middleware

Créons un service simple qui reçoit une chaîne de caractères et la retourne en majuscules. Ensuite, nous l'encapsulerons dans un middleware qui journalise chaque requête.

use std::convert::Infallible;
use std::task::{Context, Poll};
use tower::{Service, Layer, ServiceBuilder};
use tower::layer::util::Identity;
use std::future::{Ready, ready};

// 1. Définition de notre service
#[derive(Clone)]
struct MyService;

impl<Request> Service<Request> for MyService {
    type Response = String;
    type Error = Infallible;
    type Future = Ready<Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: Request) -> Self::Future {
        // Retourne simplement une chaîne
        ready(Ok(format!("Bonjour depuis le service ! Requête : {:?}", std::any::type_name::<Request>())))
    }
}

// 2. Définition du middleware de journalisation
#[derive(Clone)]
struct LogMiddleware;

impl<S> Layer<S> for LogMiddleware {
    type Service = LogService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        LogService { inner }
    }
}

#[derive(Clone)]
struct LogService<S> {
    inner: S,
}

impl<S, Request> Service<Request> for LogService<S>
where
    S: Service<Request>,
    S::Future: std::marker::Send,
    Request: std::fmt::Debug,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request) -> Self::Future {
        println!("[LOG] Requête reçue : {:?}", req);
        self.inner.call(req)
    }
}

#[tokio::m                    

Recommendations