DEV Community

Steadylearner
Steadylearner

Posted on • Updated on • Originally published at steadylearner.com

Cómo utilizar Rust web framework Warp

En este post, aprenderemos a utilizar Rust Warp. Empezando por el ejemplo oficial actual en GitHub. Luego, vamos a aprender a modulizarlo.

You can find the English version of it here.

Si desea probar Warp más después de leer esta publicación, puede consultar esto.

Si ya estás familiarizado con Rust y quieres ahorrarte tiempo, por favor, simplemente clona el Rust Full Stack repository y consulta Warp y el microservicio ejemplos allí.

$git clone https://github.com/steadylearner/Rust-Full-Stack.git
Enter fullscreen mode Exit fullscreen mode

Entonces, una vez dentro de la carpeta de warp, utiliza esto:

$cd hello_world
$cargo run --release
Enter fullscreen mode Exit fullscreen mode

Para construir una aplicación full stack con Rust, consulte How to use Rust Yew para la parte de Rust frontend. Puedes utilizar su Fetch API y solicitar datos de los servidores de Rust para renderizar sus páginas.

[Requisito previo]

  1. How to install Rust
  2. Warp documentation and Warp official examples
  3. The article to help you find Futures, Services and Filters better
  4. FP in Rust
  5. Reading Rust function signatures
  6. Real world Tacit programming part 1 and Real world Tacit programming part 2
  7. Rust closures are hard
  8. Rust Future crate, Future by Example and Rust future and_then

En primer lugar, debes instalar Rust, si aún no lo tienes. La entrada del blog Cómo instalar Rust te ayudará.

Si es necesario, visita el sitio web de Rust para obtener más información.

Asumo que ya estás familiarizado con Rust. Si no lo estás, por favor, aprende a usarlo primero.

Antes de leer este post, espero que leas completamente la documentación de Warp y los ejemplos oficiales de Warp.
Después de instalar la dependencia, utilice este comando:

$cargo doc -p warp --open
Enter fullscreen mode Exit fullscreen mode

Será útil pasar más tiempo leyendo el ejemplo de enrutamiento del autor.

Fíjate que el autor utiliza clausuras como API principal del framework. He puesto a tu disposición varias documentaciones para ayudarte a entender mejor su funcionamiento.

Encuentra más información sobre and_then.

Para saber más sobre la programación asíncrona con Rust, consulta esto.

Espero que los leas todos antes de terminar este post.

Índice de contenidos

  1. Comience con el ejemplo oficial
  2. Refactorizarlo
  3. Hacer Rutas para filtrar las peticiones de los usuarios
  4. Construir Handlers para responder a ellas
  5. Enlazarlos para terminar la API con una macro
  6. Probar su funcionamiento con CURL y Rust
  7. Conclusion

La estructura de los proyectos de este post es opinable.

Encontré que los proyectos Warp pueden ser organizados de manera similar a los Express. Asumiendo que ya estás familiarizado con los sistemas de módulos de Rust, esto no será difícil de hacer.

Espero que también dediques tiempo a leer la documentación de Express.

1. Comience con el ejemplo oficial

Haremos nuestra primera aplicación Warp usando el ejemplo oficial dado por su autor. Utilizaremos varios comandos y crearemos algunos archivos.

En primer lugar, utilice $cargo new hello_world para hacer un mínimo de Rust boilerplate. El cargo debe haber hecho el archivo src/main.rs y Cargo.toml.

Verifique que Rust está funcionando bien en su máquina usando $cargo c, si es nuevo en Rust.

A continuación, añade las dependencias a tu proyecto Rust pegándolas desde el autor a Cargo.toml. (Si estás familiarizado con JavaScript y NPM, puedes imaginar que esto es similar a package.json).

tokio = { version = "0.2", features = ["macros"] }
warp = "0.2.1"
Enter fullscreen mode Exit fullscreen mode

Además, puedes considerar los comandos de edición de carga en su lugar.
Ahora, copia y pega esto en main.rs.

use warp::Filter; // 1.

#[tokio::main] // 2.
async fn main() {
    let hello = warp::path!("hello" / String) // 3.
        .map(|name| format!("Hello, {}!", name)); // 4.

    warp::serve(hello) // 5.
        .run(([0, 0, 0, 0], 8000)) // 6.
        .await;
}
Enter fullscreen mode Exit fullscreen mode

Todo está listo. Usa $cargo run --release. Lee este post en lugar de esperar a que termine.

Cuando termine, puede usar el comando CURL que aparece a continuación para comprobar el punto final /hello/String.

$curl 0.0.0.0:8000/hello/www.steadylearner.com // return "Hello, www.steadylearner.com"
Enter fullscreen mode Exit fullscreen mode

Espero que tu primera aplicación Warp haya funcionado. El código utilizado aquí es muy sencillo. Pero, una pequeña ayuda para saber cómo funcionan será muy útil.

1. Si has leído su documentación, el autor dice lo siguiente.

El bloque de construcción fundamental de la urdimbre es el filtro
que pueden combinarse y componerse para expresar requisitos ricos en peticiones.
Enter fullscreen mode Exit fullscreen mode

Por lo tanto, se utiliza para este ejemplo mínimo para demostrar cómo funciona y lo verás más adelante también.

2. Warp utiliza tokio como su ejecutor de tareas asíncronas entre bastidores. No tendrás que preocuparte demasiado por ello para que el ejemplo funcione, salvo que "su alternativa es async std y no son tan compatibles actualmente".

Para aprender más sobre la programación asíncrona con Rust, por favor, lee toda la documentación en la carpeta asíncrona en Rust Full Stack.

3. Si usted es nuevo en el marco de Warp, puede ser difícil de averiguar lo que hace. Puede pensar en "hola" como una ruta estática y en String como una parte para mostrar su intención de recibir parámetros dinámicos. Debe ser de tipo String solamente.

Si está familiarizado con otro framework web como Express, compárelo con la parte req(requisitos). La diferencia es que usted expresa sólo lo que quiere permitir. A continuación, utilizar con la API proporcionada por Warp. Compárelo con routes/hello_route.rs más adelante.

4. Aquí se utiliza el clausura Rust. (Si está familiarizado con Express, compárelo con la parte res(respond).

Vea que podría usar la variable nombre aquí dentro de || para usar el parámetro String que permitimos antes. Después, podrías expresar lo que quieres hacer con esto:

format!("Hello, {}!", name)
Enter fullscreen mode Exit fullscreen mode

Compárelo con handlers/hello_handler.rs más adelante.

Si eres nuevo en Rust, puede que te preguntes cómo funciona el clausura sin tener que escribir aquí la variable, los params, el valor de retorno y la firma de la función, etc.

Esto se debe a que el compilador de Rust infiere los tipos para ellos en lugar de usted. Si has leído la documentación sobre esto, será fácil averiguar cómo funcionan.

Con la ayuda de esto, la API de Warp se vuelve muy fácil con los clausuras y será similar al uso de funciones de flecha en JavaScript.

Pero, a diferencia de JavaScript, no es fácil hacer que los clausuras sean reutilizables en todo el proyecto. Por lo tanto, usaremos funciones y macros en su lugar.

5. Antes, hicimos un hello API y lo guardamos en la variable hello y aprendimos cómo se hacían. Deja que el Warp lo sirva con esto:

6. Prefiero usar 0.0.0.0 en lugar de localhost y su alias 127.0.0.1 para acoplar aplicaciones fácilmente para usar Docker después facilmenete.

Espero que esta breve explicación le haya servido de ayuda. Por favor, lea toda la documentación proporcionada como requisito previo.

2. Refactorizarlo

Anteriormente, logramos hacer funcionar el ejemplo oficial de Warp y aprendimos los detalles de cada parte.

Vamos a refactorizar el ejemplo usando funciones en lugar del clausura que se usa allí. Al final del proceso, todo el proyecto se verá así:

├── Cargo.toml
├── src
│   ├── api
│   │   ├── hello.rs
│   │   └── mod.rs
│   ├── handlers
│   │   ├── hello_handler.rs
│   │   └── mod.rs
│   ├── main.rs
│   ├── routes
│   │   ├── hello_route.rs
│   │   └── mod.rs
│   └── tests
│       ├── hello_test.rs
│       └── mod.rs
Enter fullscreen mode Exit fullscreen mode

Puedes ver que haremos varias carpetas como routes/, handlers/, api/, tests/ etc y archivos dentro de ellas.

Puedes ver un montón de mod.rs allí. Pero, el contenido de esto será muy simple con pub mod hello_route etc. Piensa en ellos sólo como ayudantes para exportar otros archivos dentro de cada carpeta.

Si me dejas explicarlo con más información a continuación, será

1. Rust quiere que seas específico con todo, exportando e importando los módulos también. Si encuentras su sistema de módulos difícil, primero investiga qué los representa. Será mod.rs en cada carpeta y main.rs o lib.rs en el directorio de nivel superior de tu proyecto Rust.

2. crate representará a main.rs o lib.rs y funcionará como si se publicaran crate o algo similar a package en JavaScript. self se utilizará para representar a main.rs o lib.rs dentro de ellos mismos y utilizar los módulos importados en ellos.

3. Es posible que quiera utilizar la palabra clave crate pero no funcionará porque ya se utiliza dentro de la sintaxis extern crate somecrate.

Si quieres más información sobre esto, por favor, consulta Cómo modular tu Rust Frontend o Rust Yew frontend de ejemplo o lee la documentación oficial de Rust para ello.

Así que, sin esos archivos mod.rs, lo que necesitamos será sólo esto:

├── src
│   ├── api
│   │   ├── hello.rs
│   ├── handlers
│   │   ├── hello_handler.rs
│   ├── main.rs
│   ├── routes
│   │   ├── hello_route.rs
│   └── tests
│       ├── hello_test.rs
Enter fullscreen mode Exit fullscreen mode

Primero, empezaremos con main.rs. Esto le ayudará a encontrar mejor la estructura de todo el proyecto.

use warp::{self, Filter};

use console::Style;

mod routes; // 1.
mod handlers; // 2.
mod api; // 3.

use self::{
    routes::{
        hello_route,
    },
    handlers::{
        hello_handler
    },
};

// It will only work with $cargo test
// For example, $cargo test hello -- --nocapture
#[cfg(test)] mod tests;

#[tokio::main]
async fn main() {
    let target: String = "0.0.0.0:8000".parse().unwrap();
    let blue = Style::new()
        .blue();

    // 4.
    let end = hello!().with(warp::log("hello"));

    println!("\nRust Warp Server ready at {}", blue.apply_to(&target));
    println!("Use $curl 0.0.0.0:8000/hello/www.steadylearner.com to test the end point.");

    warp::serve(end).run(([0, 0, 0, 0], 8000)).await;
}
Enter fullscreen mode Exit fullscreen mode

Puedes ver que es muy sencillo porque todas las partes ya están moduladas.

Primero, incluimos la carpeta (submódulos) que haremos con la palabra clave mod. Usted puede reemplazar en su mente con esto.

Carpeta de importación y los archivos de Rust de ella a lo largo en el mismo directorio. Ya debería haber hecho **mod.rs** para representar la carpeta y escribir **pub mod filename** para exportar los archivos de Rust allí, para satisfacer el compilador de Rust.
Enter fullscreen mode Exit fullscreen mode

1. routes/ incluirá lo que quieres que el servidor acepte de la petición del usuario. Ya sabemos que sólo aceptaremos la ruta "/hello/String". Puedes compararlo con la parte req de Express.

2. handlers/ incluirá lo que quieres que el servidor haga con él. Ya sabemos que devolveremos el html (texto) con format!("Hello, {}!", nombre) y la parte del nombre será String filtrado por hello_route que haremos.

Esta será la carga útil de su proyecto.

Más adelante, pasarás la mayor parte del tiempo con las bases de datos y los models/ para utilizarlos. Consulte el ejemplo de la base de datos Warp si lo desea.

3. La macro hello! que vamos a hacer será utilizable en main.rs y tests/hello_test.rs importando la carpeta api con eso.

Esto es tal vez opcional. Pero, le ayudará a no escribir el mismo código de abajo en tests/ folder que vamos a hacer.

let hello = hello_route::hello()
    .and_then(hello_handler::hello)
Enter fullscreen mode Exit fullscreen mode

Te preguntarás "¿Por qué usar macro aquí, cuando se pueden usar funciones, etc.?"

Esta fue la última solución, y quizás la más sencilla, que se me ocurrió. No fue fácil insertar el código porque algunos tipos utilizados son privados, inestables y no permitidos por el compilador.

Consulte la carpeta de comparación antes de encontrar la mejor manera.

4. El nombre de la variable end se utiliza aquí porque se utilizará antes de servirla finalmente con warp::serve.

Puede referirse a esto desde el ejemplo de la base de datos Warp.

let post_api = list_posts!()
    .or(get_post!())
    .or(create_post!())
    .or(update_post!())
    .or(delete_post!());

let end = post_api.with(warp::log("post_api"));
Enter fullscreen mode Exit fullscreen mode

Otras partes son sólo para ayudarte a depurar mejor la aplicación.

3. Rutas para filtrar las solicitudes de los usuarios

Anteriormente, definimos rutas mod para importar archivos Rust (hello_route.rs) en routes en el main.rs. Pero, aún no lo hemos hecho.

Así que lo escribiremos con alguna explicación. Será similar a esto.

// It is equal to use
// let hello = path!("hello" / String);
// in main.rs file.

use warp::{
    filters::BoxedFilter,
    Filter,
};

// 1. "hello"
fn path_prefix() -> BoxedFilter<()> {
    warp::path("hello")
        .boxed()
}

// 2. / String
pub fn hello() -> BoxedFilter<(String, )> {
    warp::get() // 3.
        .and(path_prefix()) // 4.
        .and(warp::path::param::<String>()) // 5.
        .boxed()
}
Enter fullscreen mode Exit fullscreen mode

1. En primer lugar, extraemos la parte del prefijo hello para hacerla reutilizable.

Esto no es tan significativo aquí. Pero, será útil cuando quieras usar prefijos para REST api como api/post/v1, api/user/v1 etc.

Puedes pensar en lo que son BoxedFilter<()> y .boxed() aquí. Piensa en ellos como un pegamento que te ayudará a encadenar otros métodos cuando se usen dentro and la API de Warp más adelante en 2..

2. Esta será la carga útil de este archivo. Aquí se define lo que se quiere filtrar de las peticiones de los usuarios.

3. Sólo recibe (filtrar) las peticiones GET.
4. Estas comienzan con el prefijo /hello que hicimos antes.

5. "Warp, acepta parametro sólo con tipo String".

Puedes ver que será el argumento de la función que haremos en route_hanlder más adelante.

También puede utilizar tipos personalizados como Post, NewPost.

El código utilizado aquí se explica por sí mismo. Pero, espero que estas explicaciones te hayan ayudado.

4. Construir Manejadores para responder a ellos

En la sección anterior, definimos la ruta para filtrar las peticiones de los usuarios. Vamos a definir cómo manejarlas creando hello_handler.rs.

Será similar a esto:

use warp;

pub async fn hello(name: String) -> Result<impl warp::Reply, warp::Rejection> {
    let reply = format!("Hello, {}!", name);
    // println!("{}", &reply);
    Ok(warp::reply::html(reply))
}
Enter fullscreen mode Exit fullscreen mode

El código utilizado aquí es muy sencillo.

En primer lugar, el tipo de retorno de las funciones manejadoras casi siempre será este:

Result<impl warp::Reply, warp::Rejection>
Enter fullscreen mode Exit fullscreen mode

Por lo tanto, sólo tiene que copiar y pegar después de leer la documentación de Respuesta y Rechazo.

Debe tener cuidado de cómo manejar las partes de Rechazo. Pero, no es necesario para este sencillo ejemplo.

También puede ver que el parámetro String que filtramos en hello_route.rs se ha convertido en el parámetro de la función aquí.

Compárelo con lo que usamos antes en la primera parte

.map(|name| format!("Hello, {}!", name));
Enter fullscreen mode Exit fullscreen mode

Es cierto que los Rust clausuras son mucho más sencillos de usar y prototipar tu aplicación Warp. Utilízalos primero en main.rs. Cuando quieras modularla, sepáralos en routes y handlers.

5. Enlazarlos para finalizar la API con la macro

Con routes/ y handlers/, la lógica de tu aplicación ya está completa. Establece la macro hello! dentro de api/hello.rs de forma similar a esta para enlazarlos:

#[macro_export]
macro_rules! hello {
    () => {
        hello_route::hello()
        .and_then(hello_handler::hello)
    }
}
Enter fullscreen mode Exit fullscreen mode

Con #[macro_export], las macros así definidas pasan a estar disponibles globalmente a nivel de crate.

Sólo funcionarán cuando el módulo que incluye esté incluido en main.rs o lib.rs que representan tu proyecto Rust con mod api.

Puedes usar las macros de api dentro de main.rs o lib.rs sin usar esto.

use crate::{
    hello,
};
Enter fullscreen mode Exit fullscreen mode

Si quieres usarlo en otras partes de tu proyecto como vemos en tests/hello_test.rs, debes incluir el código anterior para que funcione.

Todos los archivos para compilar tus primeros proyectos Warp están listos.

Primero, usa $cargo c para verificar si compila o no. Luego, usa cargo run --release para producción o cargo run para compilar rápido.

¡Espero que lo consigas!

6. Pruebe su funcionamiento con CURL y Rust

Si has seguido bien las partes anteriores, tu consola ya debería haber mostrado esto tras la compilación de tu proyecto.

Rust Warp Server ready at 0.0.0.0:8000
Use $curl 0.0.0.0:8000/hello/www.steadylearner.com to test the end point.
Enter fullscreen mode Exit fullscreen mode

Primero, utilice $curl 0.0.0.0:8000/hello/www.steadylearner.com. Se mostrará el mismo resultado que puede ver en la primera parte.

CURL es muy útil para probar puntos finales, pero hay problemas.

1. En primer lugar, tenemos que tener el servidor Warp listo todo el tiempo. El compilador de Rust le quitará tiempo.
2. Luego, debes averiguar qué comandos CURL debes utilizar. Esto puede ser complicado.

Por lo tanto, haremos funciones de prueba de Rust para simplificar y automatizar el proceso. Construiremos tests/hello_test.rs equivalente al comando CURL que usamos.

use warp::Filter;

use crate::{
    handlers::hello_handler,
    routes::hello_route,
    hello,
};

// $cargo test -- --nocapture if you want to use println! etc.

// or test just one function each time.
// For example, $cargo test hello and it passes.

#[cfg(test)]
mod tests {
    use super::*;

    // 1.
    #[tokio::test]
    async fn hello() {
        let res = warp::test::request() // 2.
            .method("GET")
            .path("/hello/www.steadylearner.com")
            .reply(&hello!()) // 3.
            .await;

        // 4.
        assert_eq!(res.status(), 200, "Should return 200 OK.");
        // 5.
        println!("{:#?}", res.body());
    }
}
Enter fullscreen mode Exit fullscreen mode

Los puntos importantes son estos:

1. Ya hemos aprendido que Warp utiliza tokio para manejar los cálculos asíncronos. Por lo tanto, el corredor de pruebas será también #[tokio:test].

2. Construir una solicitud de cliente específica para probar antes de .reply. No será difícil averiguar lo que hacen porque los mismos pensamientos se utilizan de nuevo.

3. Definir cómo va a responder a la solicitud aquí. ¡Ya hemos hecho el hello! API para esto.

4. Ver que el servidor devuelve OK con ello.

5. Pruebe la función con $cargo test hello.

También puedes usar la bandera -- --nocapture para mostrar las partes de stdout. Por ejemplo, pruébalo de nuevo con $cargo test hello -- --nocapture.

Con CURL y un test con Rust, puedes verificar tu API fácilmente, siempre que modifiques los archivos en routes/ y handlers/.

Si tienes curiosidad, también puedes probar el rendimiento con loadtest..

No será lento y su uso de memoria es muy bajo (4,62 Mb en mi máquina
Linux Ubuntu 18.04).

Prueba por tu cuenta con este comando en Linux mientras tu servidor Warp está listo en otra consola.

1. Para ver todos los usos de memoria en su sistema, utilice esto:

$ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | cut -d "" -f2 | cut -d "-" -f1
Enter fullscreen mode Exit fullscreen mode

2. También puede incluir grep y Linux pipe | para ver sólo la cantidad de memoria que necesita Warp.

$ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | cut -d "" -f2 | cut -d "-" -f1 | grep "hello_world"
Enter fullscreen mode Exit fullscreen mode

Consúltalo para compararlo con otros frameworks web que utilices..

7. Conclusión

Espero que todo haya funcionado y que hayas conseguido aprender a usar Warp con este post. Si quieres aprender más sobre él, puedes encontrar más ejemplos de Warp.

Puedes Dockerizarlo, usarlo con una app React de una sola página, base de datos, desplegarlos en AWS, etc.

El post era sobre Warp, pero espero que los lectores también hayan aprendido cómo construir una aplicación web con Rust.

El desarrollo web con Rust no es todavía la corriente principal. De hecho, no fue fácil invertir tiempo en él y escribir para ayudar a otros, aunque es mucho más rápido que la mayoría de otros frameworks web.

Mantente al tanto de los últimos contenidos de Steadylearner: sígueme en Twitter y GitHub.

Comparte con otros para ayudarles a encontrar mejor el repositorio y empezar a programar Rust fácilmente.

Top comments (0)