Maîtriser cet Art

Pourquoi Construire ce Framework

En tant que débutant, j'ai eu du mal à saisir les frameworks existants comme actix-web et Rocket. Lorsque j'ai tenté de réécrire un service web Go précédent en Rust, chaque framework semblait plus complexe que ceux disponibles en Go. Étant donné la courbe d'apprentissage déjà abrupte de Rust, pourquoi compliquer davantage les frameworks web ?

Lorsque Tokio a introduit le framework Axum, j'ai été ravi, pensant que je pourrais enfin arrêter de maintenir mon propre framework web. Cependant, j'ai vite réalisé que bien qu'Axum paraisse simple, il nécessitait de nombreuses acrobaties de types et de définitions génériques. Créer même un middleware de base exigeait une expertise approfondie en Rust et l'écriture d'une quantité importante de code boilerplate obscur.

Ainsi, j'ai décidé de continuer à maintenir mon framework web unique—un framework intuitif, riche en fonctionnalités et convivial pour les débutants.

Salvo est-il Fait pour Vous ?

Salvo est simple, complet et puissant, probablement le plus fort de l'écosystème Rust. Malgré ses capacités, il reste facile à apprendre et à utiliser, vous épargnant toute douleur d'« auto-castration ».

  • Idéal pour les débutants en Rust : Les opérations CRUD sont courantes, et avec Salvo, ces tâches semblent aussi simples qu'avec les frameworks d'autres langages (par exemple, Express, Koa, Gin, Flask). À certains égards, Salvo est même plus abstrait et concis.

  • Adapté à la production : Si vous visez à déployer Rust en production pour des serveurs robustes et rapides, Salvo convient parfaitement. Bien qu'il ne soit pas encore à la version 1.0, ses fonctionnalités principales ont subi des années d'itération, garantissant stabilité et résolution rapide des problèmes.

  • Parfait pour vous, surtout si vos cheveux s'éclaircissent et tombent quotidiennement.

Atteindre la Simplicité

Hyper gère de nombreuses implémentations de bas niveau, en faisant une base fiable pour les besoins généraux. Salvo suit cette approche, offrant un système de routage puissant et flexible ainsi que des fonctionnalités essentielles comme Acme, OpenAPI et l'authentification JWT.

Salvo unifie les Handlers et les Middlewares : un Middleware est un Handler. Les deux sont attachés au Router via hoop. Essentiellement, ils traitent la Request et peuvent écrire des données dans la Response. Un Handler reçoit trois paramètres : Request, Depot (pour les données temporaires pendant le traitement de la requête) et Response.

Pour plus de commodité, vous pouvez omettre les paramètres inutiles ou ignorer leur ordre.

use salvo::prelude::*;

#[handler]
async fn hello_world(_req: &mut Request, _depot: &mut Depot, res: &mut Response) {
    res.render("Hello world");
}
#[handler]
async fn hello_world(res: &mut Response) {
    res.render("Hello world");
}

L'API de routage est exceptionnellement simple mais puissante. Pour les cas d'utilisation typiques, vous n'avez besoin de vous concentrer que sur le type Router.

De plus, si une structure implémente les traits pertinents, Salvo peut générer automatiquement la documentation OpenAPI, extraire les paramètres, gérer les erreurs avec élégance et renvoyer des messages conviviaux. Cela rend l'écriture des handlers aussi intuitive que l'écriture de fonctions ordinaires. Nous explorerons ces fonctionnalités en détail plus tard. Voici un exemple :

#[endpoint(tags("message_logs"))]
pub async fn create_message_log_handler(
    input: JsonBody<CreateOrUpdateMessageLog>,
    depot: &mut Depot,
) -> APPResult<Json<MessageLog>> {
    let db = utils::get_db(depot)?;
    let log = create_message_log(&input, db).await?;
    Ok(Json(log))
}

Dans cet exemple, JsonBody<CreateOrUpdateMessageLog> analyse automatiquement le JSON du corps de la requête dans le type CreateOrUpdateMessageLog (prenant en charge plusieurs sources de données et types imbriqués). La macro #[endpoint] génère la documentation OpenAPI pour ce endpoint, simplifiant l'extraction des paramètres et la gestion des erreurs.

Système de Routage

Je crois que le système de routage de Salvo se distingue. Les routeurs peuvent être plats ou arborescents, distinguant les arbres de logique métier et les arbres de répertoire d'accès. L'arbre de logique métier organise les routeurs en fonction des besoins métier, qui peuvent ne pas correspondre à l'arbre de répertoire d'accès.

Typiquement, les routes sont écrites ainsi :

Router::new().path("articles").get(list_articles).post(create_article);
Router::new()
    .path("articles/{id}")
    .get(show_article)
    .patch(edit_article)
    .delete(delete_article);

Souvent, consulter et lister les articles ne nécessitent pas de connexion utilisateur, mais créer, modifier ou supprimer des articles si. Le système de routage imbriqué de Salvo gère cela avec élégance. Nous pouvons regrouper les routes publiques ensemble :

Router::new()
    .path("articles")
    .get(list_articles)
    .push(Router::new().path("{id}").get(show_article));

Ensuite, regrouper les routes protégées avec un middleware pour l'authentification :

Router::new()
    .path("articles")
    .hoop(auth_check)
    .post(create_article)
    .push(Router::new().path("{id}").patch(edit_article).delete(delete_article));

Même si les deux routeurs partagent le même path("articles"), ils peuvent être ajoutés au même routeur parent, donnant :

Router::new()
    .push(
        Router::new()
            .path("articles")
            .get(list_articles)
            .push(Router::new().path("{id}").get(show_article)),
    )
    .push(
        Router::new()
            .path("articles")
            .hoop(auth_check)
            .post(create_article)
            .push(Router::new().path("{id}").patch(edit_article).delete(delete_article)),
    );

{id} correspond à un segment de chemin. Typiquement, un id d'article est numérique, donc nous pouvons restreindre la correspondance avec une regex : r"{id:/\d+/}".