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 Aussi dans la bibliothèque
Travailler avec Kafka en TypeScript : la bibliothèque KafkaJS pour Node.js
Travailler avec XML en Swift (iOS/macOS) : Foundation et SwiftXML
Client Redis pour C++ : connexion, installation et exemples d'utilisation
Internationalisation (i18n) en TypeScript : guide complet