Ao longo da última década, surgiram uma série de estruturas web Rust, cada uma construída com usuários e necessidades de recursos ligeiramente diferentes em mente. Todos eles se beneficiam da segurança de tipo, segurança de memória, velocidade e correção do Rust.

Este artigo é uma rápida olhada em cinco dos frameworks web Rust mais populares: Actix Web, Rocket, Warp, Axum e Poem. Todos eles fornecem elementos comuns para serviços web: roteamento, tratamento de solicitações, vários tipos de resposta e middleware. Observe que essas estruturas não fornecem modelos, que normalmente são manipulados por caixas separadas.

Actix Web

Actix Web é facilmente o framework web mais popular para Rust. Ele satisfaz praticamente todas as principais necessidades: é de alto desempenho, suporta uma ampla gama de recursos de servidor e requer pouca cerimônia para montar um site básico.

O nome “Actix Web” originalmente se referia à dependência do framework do actix estrutura de ator, mas a estrutura praticamente eliminou essa dependência há algum tempo. Todos os recursos do Actix Web estão disponíveis no branch estável Rust.

Aqui está um aplicativo básico “olá mundo” no Actix Web:


use actix_web::{get, App, HttpResponse, HttpServer, Responder};

#(get("https://www.infoworld.com/"))
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#(actix_web::main)
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(hello))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

O get() atributo no hello() função indica qual rota ela deve atender, mas não está ativa até que seja adicionada ao App objeto com o .service() método. O Actix Web também oferece suporte à construção de rotas mais avançadas – por exemplo, você pode capturar variáveis ​​posicionais do URL e usá-las para rotear solicitações para funções que não usam get().

O desempenho é um grande atrativo para o Actix Web. Todas as solicitações e respostas são tratadas como tipos distintos. O servidor usa um pool de threads para lidar com solicitações, sem nada compartilhado entre os threads para maximizar o desempenho. Você pode compartilhar o estado manualmente, se necessário, usando um Arc<>, mas os mantenedores do Actix Web recomendam não fazer qualquer coisa que bloqueie threads de trabalho e, assim, sabotar o desempenho. Para trabalhos de longa duração sem CPU, use futuros ou assíncronos.

Actix Web também fornece manipuladores baseados em tipo para códigos de erro e usa um sistema de middleware integrado (que você também pode usar) para implementar o log. A estrutura também inclui um sistema de gerenciamento de sessão de usuário de uso geral com cookies como tipo de armazenamento padrão, embora você possa adicionar outros, se desejar. Arquivos e diretórios estáticos também podem ser servidos com seus próprios manipuladores dedicados.

Muitas funções comuns de serviços da web vêm junto com o Actix Web, junto com algumas que são menos comuns. Isso inclui manipulação de corpos codificados em URL para formulários, promoção automática para HTTPS/2, descompactação de Brotli, gzip, deflatee zstddados compactados e manipulação de codificação em partes. Para WebSockets, o Actix Web requer o actix-web-actors crate, que é sua principal dependência. Da mesma forma, para fluxos multipartes, você precisa do actix-multipart caixa. (Para converter de e para JSON, o Actix Web usa serde e serde_jsonque deve ser familiar aos usuários do Rust em geral.)

Actix Web chamou a atenção em 2020, quando seu mantenedor original saiu do projeto, supostamente por causa de críticas sobre o uso de unsafe código. No entanto, outros mantenedores líderes continuaram a desenvolver a estrutura, e ela continuou a crescer nos anos seguintes. Muito dos unsafe o código foi removido.

Foguete

A grande diferença do Rocket entre os frameworks web Rust é que ele permite obter o máximo de resultados com o mínimo de código. Escrever uma aplicação web básica no Rocket requer relativamente poucas linhas e pouca cerimônia. Rocket consegue isso usando o sistema de tipos do Rust para descrever muitos comportamentos, para que possam ser aplicados e codificados em tempo de compilação.

Aqui está um aplicativo básico de “olá mundo” no Rocket:


#(macro_use) extern crate rocket;

#(get("https://www.infoworld.com/"))
fn hello_world() -> &'static str {
    "Hello, world!"
}

#(launch)
fn rocket() -> _ {
    rocket::build().mount("https://www.infoworld.com/", routes!(hello_world))
}

Rocket funciona de forma concisa por meio do uso de atributos. As rotas são decoradas com atributos para os métodos e padrões de URL que utilizam. Como você vê neste exemplo, o #(launch) O atributo indica a função usada para montar as rotas e configurar o aplicativo para escutar solicitações.

Embora as rotas no exemplo “hello world” sejam síncronas, as rotas podem ser assíncronas no Rocket, e geralmente deveriam ser quando possível. Por padrão, o Rocket usa o tokio runtime para lidar com coisas como converter operações de sincronização em assíncronas.

Rocket fornece muitos dos recursos usuais para lidar com solicitações – extrair variáveis ​​de elementos de URL, por exemplo. Um recurso exclusivo é o “request guards”, onde você usa tipos Rust, implementando Rocket's FromRequest trait, para descrever uma política de validação para uma rota.

Por exemplo, você poderia criar um tipo personalizado para impedir que uma rota fosse disparada, a menos que determinadas informações estivessem presentes nos cabeçalhos da solicitação e pudessem ser validadas, como um cookie com uma determinada permissão associada a ele. Isso permite que você crie coisas como permissões na segurança do tipo em tempo de compilação do Rust.

Outro recurso útil e distinto do Rocket é carenagens, a versão do middleware do Rocket. Tipos que implementam o Fairing trait pode ser usado para adicionar retornos de chamada a eventos, como solicitações ou respostas. Mas as carenagens não podem alterar ou interromper solicitações (embora possam acessar cópias dos dados da solicitação).

Para esse fim, as carenagens são melhores para coisas que têm comportamento global – registro, coleta de métricas de desempenho ou políticas gerais de segurança. Para ações como autenticação, use um request guard.

Urdidura

A grande diferença do Warp em relação a outros frameworks web Rust é a maneira como ele usa componentes combináveis ​​– “filtros”, no jargão do Warp – que podem ser encadeados para criar serviços.

Um “olá mundo” básico no Warp não demonstra esse recurso particularmente bem, mas vale a pena mostrar o quão conciso o framework pode ser:


use warp::Filter;

#(tokio::main)
async fn main() {
    let hello = warp::path!().map(|| "Hello world");
    warp::serve(hello).run(((127, 0, 0, 1), 8080)).await;
}

Os filtros implementam o Filter trait, cada filtro capaz de passar a saída para outro filtro para modificar comportamentos. Neste exemplo, warp::path é um arquivador que pode ser encadeado em outras operações, como .map() para aplicar uma função.

Outro exemplo da documentação do Warp mostra o sistema de filtros com mais detalhes:


use warp::Filter;

let hi = warp::path("hello")
    .and(warp::path::param())
    .and(warp::header("user-agent"))
    .map(|param: String, agent: String| {
        format!("Hello {}, whose agent is {}", param, agent)
    });

Aqui, vários filtros são encadeados para criar o seguinte comportamento, nesta ordem:

  1. Configure um endpoint com o caminho hello.
  2. Adicione um parâmetro ao final do caminho, para que o caminho esteja no formato /hello/. (O .and() método é uma das maneiras pelas quais a composição funciona no Warp.)
  3. Adicione um analisador para o user-agent cabeçalho, para que qualquer solicitação recebida sem user-agent cabeçalho não é processado.
  4. Aplicar o format! macro com os parâmetros param (o parâmetro coletado) e agent (o user-agent string) para uma string e retorne-a ao cliente.

Os desenvolvedores que gostam da abordagem composicional vão gostar de como o Warp complementa sua forma de trabalhar.

Uma consequência da abordagem composicional é que você pode fazer a mesma coisa de diversas maneiras diferentes, nem todas intuitivas. Vale a pena dar uma olhada nos exemplos no repositório do Warp para ver as diferentes maneiras de resolver cenários comuns de programação usando o Warp.

Outra consequência vem da forma como os filtros funcionam em tempo de compilação. Compor muitas rotas a partir de muitos filtros diferentes pode tornar o tempo de compilação mais longo, embora as rotas sejam rápidas em tempo de execução. Outra opção é usar o envio dinâmico para sites com muitas rotas, com um pequeno custo para o desempenho do tempo de execução. Um exemplo mostra como fazer isso com um BoxedFilter tipo.

Aksum

A estrutura Axum se baseia no tower ecossistema de caixas para aplicativos cliente/servidor de todos os tipos, bem como tokio para assíncrono. Isso torna mais fácil usar o Axum se você já tiver experiência com tower ou use-o em projetos aliados.

Aqui está um aplicativo básico do Axum “hello world” encontrado na documentação do Axum. Você notará que não parece muito diferente de modelos como Actix:


use axum::{
    routing::get,
    Router,
};

#(tokio::main)
async fn main() {
    let app = Router::new().route("https://www.infoworld.com/", get(|| async { "Hello, World!" }));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Axum usa muitos dos mesmos padrões do Actix para como as rotas e manipuladores funcionam. Funções de manipulador de rota são adicionadas a um Router objeto com o .route() método, e o axum::extract módulo contém tipos para extrair componentes de URL ou POST cargas úteis. As respostas implementam o IntoResponse características e erros são tratados por meio de towerpróprio tower::Service Error tipo.

Este último comportamento, baseado em tower para os principais componentes do Axum, também inclui como o Axum lida com o middleware. Roteadores, métodos e manipuladores individuais podem ter middleware aplicado por meio de diferentes .layer métodos em tower objetos. Também se pode usar tower::ServiceBuilder para criar agregados de múltiplas camadas e aplicá-las juntas.

Axum fornece ferramentas próprias para outros padrões comuns em serviços da web. O compartilhamento de estado entre manipuladores, por exemplo, pode ser feito de maneira segura com um State tipo. Maneiras de implementar cenários típicos, como desligamentos normais ou configuração de conectividade de banco de dados, podem ser encontradas no diretório de exemplos do Axum.

Poema

A maioria das linguagens tem pelo menos um framework web “maximalista” completo (por exemplo, Django em Python) e um framework web pequeno, conciso e “minimalista” (por exemplo, Bottle, novamente em Python). O Poem está no extremo mínimo do espectro do Rust, oferecendo recursos suficientes por padrão para sustentar um serviço web básico.

Aqui está um exemplo de “olá mundo” ecoando o nome de usuário quando incluído no URL:


use poem::{get, handler, listener::TcpListener, web::Path, Route, Server};

#(handler)
fn hello(Path(name): Path) -> String {
    format!("hello: {}", name)
}

#(tokio::main)
async fn main() -> Result<(), std::io::Error> {
    let app = Route::new().at("/hello/:name", get(hello));
    Server::new(TcpListener::bind("0.0.0.0:3000"))
        .run(app)
        .await
}

Muitos dos recursos deste aplicativo devem ser familiares a partir de outras estruturas e exemplos que você viu até agora: configuração de rotas, vinculação de URLs e manipuladores a elas, extração de elementos da solicitação e assim por diante.

Para reduzir o tempo de compilação, o Poem por padrão não instala suporte para determinados recursos. Cookies, projeção CSRF, HTTP sobre TLS, WebSockets, internacionalização e compactação e descompactação de solicitação/resposta (para citar alguns), todos precisam ser habilitados manualmente.

Apesar de toda a sua simplicidade, o Poem ainda vem com muita utilidade. Ele inclui uma série de peças de middleware úteis e comuns, e você também pode implementar facilmente as suas próprias. Uma conveniência atenciosa é NormalizePath, um mecanismo para tornar os caminhos de solicitação consistentes. Isso inclui um manipulador universal para lidar com barras finais em um URL. Com o manipulador, você pode implementar seu formato preferido de uma vez e de forma consistente em todo o aplicativo.

O diretório de exemplos do Poem é menor do que algumas das outras estruturas que você viu aqui, mas se concentra principalmente em exemplos que exigem documentação detalhada, como usar o Poem com AWS Lambda ou gerar APIs em conformidade com a especificação OpenAPI.

Qual estrutura Rust é melhor para você?

O Actix Web funciona como uma solução boa e equilibrada em geral, especialmente se o desempenho for um objetivo. O Rocket permite que você mantenha seu código curto, mas expressivo, e seu sistema de “carenagens” fornece uma metáfora poderosa para implementar o comportamento do middleware.

Programadores que gostam de trabalhar com elementos combináveis ​​vão querer experimentar o Warp, pois ele permite construir rotas e fluxos de trabalho de forma programática e com grande expressividade. Axum atrairá mais diretamente os usuários do Rust que já estão familiarizados com o tower ecossistema, mas é útil o suficiente para não se limitar a esse público. Poem é simples por padrão e ótimo assim se tudo que você precisa é o roteamento mais básico e o tratamento de solicitações. Você também pode instalar recursos adicionais se precisar deles.