como comentario el shortcut de unwrap es "?"
.bind(("0.0.0.0", 9900))?.run()
Configurar Actix Web en Rust te permite levantar un servidor HTTP en minutos y conectarlo a una base de datos PostgreSQL usando un pool de conexiones. Aquí aprendes a estructurar el main.rs, definir rutas con macros y entender por qué Rust exige la palabra move al pasar datos entre hilos. Es una guía pensada para quien ya conoce Diesel y quiere dar el salto a la capa web.
¿Qué dependencia necesito para usar Actix Web en Rust?
Todo empieza en el Cargo.toml. Ahí declaras la versión estable de Actix Web, que al momento de esta clase es la 4.0 [01:00].
toml [dependencies] actix-web = "4.0"
Dentro del archivo main.rs importas los elementos que vas a usar a lo largo del servidor: get, post, web, App, HttpResponse, HttpServer y Responder.
rust use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
El trait Responder es el que permite que una función devuelva una respuesta HTTP válida sin que tengas que construirla manualmente cada vez.
¿Cómo se estructura el main asíncrono de Actix?
Actix exige que tu aplicación sea asíncrona, así que la función main se marca con async y con el macro #[actix_web::main]. La salida es un std::io::Result<()> porque el servidor puede fallar al enlazar el puerto [01:45].
rust #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(hello_world) }) .bind(("0.0.0.0", 9900))? .run() .await }
Fíjate en tres detalles que suelen romper a quienes empiezan:
bind devuelve un Result, así que necesitas ? o .unwrap()..await porque estás devolviendo el valor.¿Qué hace el macro
#[actix_web::main]? Convierte tu funciónmainsíncrona en un runtime asíncrono compatible con Actix. Sin él, no puedes usarasync/awaitenmain.
¿Cómo creo una ruta y devuelvo Hola Platzi en el navegador?
La función que responde en la raíz es muy directa. Le pones el macro #[get("/")] para asociarla a una URL y devuelves un HttpResponse::Ok con el cuerpo que quieras [03:30].
rust #[get("/")] async fn hello_world() -> impl Responder { HttpResponse::Ok().body("Hola, Platzi") }
Un error típico aquí es dejar el punto y coma después de HttpResponse::Ok().body(...). Si lo haces, la función devuelve () en vez de un Responder y el compilador se queja. Esa es la azúcar sintáctica de Rust: la última expresión sin punto y coma es el valor de retorno.
Corre el proyecto con cargo run, abre el navegador en 0.0.0.0:9900 y vas a ver el mensaje en pantalla. Cualquier cambio en el código requiere detener el servidor con Ctrl + C y volver a ejecutar.
¿Cómo conecto Actix Web con PostgreSQL usando r2d2?
Una sola conexión a base de datos no escala. Por eso Diesel ofrece r2d2, una librería que crea un pool de conexiones reutilizables [05:30]. Necesitas dos imports clave:
rust use diesel::r2d2::Pool; use diesel::r2d2::{self, ConnectionManager};
En el Cargo.toml activa la feature r2d2 de Diesel para que el compilador encuentre estos módulos.
Dentro de main, después de cargar las variables del .env, construyes el manager y el pool:
rust let connection = ConnectionManager::<PgConnection>::new(db_url); let pool = Pool::builder() .build(connection) .expect("No se pudo construir la pool");
El expect te da un mensaje claro si la conexión falla al arrancar, lo cual ahorra horas de debugging.
¿Cómo paso el pool a las rutas con web::Data?
Actix usa web::Data para compartir estado entre handlers. Después de registrar el servicio, agregas el pool clonado al App:
rust HttpServer::new(move || { App::new() .service(hello_world) .app_data(web::Data::new(pool.clone())) })
Desde cualquier ruta, ahora puedes pedir el pool como parámetro y ejecutar queries de Diesel sin abrir conexiones nuevas a mano.
¿Qué es un pool de conexiones? Es un grupo de conexiones a la base de datos que se mantienen abiertas y se reparten entre las peticiones. Evita el costo de abrir y cerrar conexiones en cada request.
¿Por qué Actix obliga a escribir move antes del closure?
La palabra move aparece justo antes de las llaves del HttpServer::new y resuelve un tema profundo de Rust: el ownership [08:00]. Cuando el servidor arranca cada worker en un hilo distinto, necesita ser dueño de los datos que va a usar, en este caso el pool.
Sin move, el closure intentaría tomar prestado el pool del main, pero el main ya no existirá cuando los hilos estén corriendo. Con move, transfieres la propiedad del valor desde el hilo principal hacia cada hilo del servidor.
move traslada la propiedad de las variables capturadas al closure.pool.clone() crea una referencia contable nueva para que cada worker tenga la suya.Es uno de los superpoderes de Rust: te obliga a pensar en quién posee qué dato antes de ejecutar, y eso elimina toda una familia de errores de concurrencia en tiempo de compilación.
¿Ya levantaste tu servidor en el puerto 9900? Cuéntame en los comentarios qué mensaje pusiste en tu primer body y si el compilador te marcó algún typo en Connection o ConnectionManager.
Configurar Actix Web en Rust te permite levantar un servidor HTTP en minutos y conectarlo a una base de datos PostgreSQL usando un pool de conexiones. Aquí aprendes a estructurar el main.rs, definir rutas con macros y entender por qué Rust exige la palabra move al pasar datos entre hilos. Es una guía pensada para quien ya conoce Diesel y quiere dar el salto a la capa web.
Todo empieza en el Cargo.toml. Ahí declaras la versión estable de Actix Web, que al momento de esta clase es la 4.0 [01:00].
toml [dependencies] actix-web = "4.0"
Dentro del archivo main.rs importas los elementos que vas a usar a lo largo del servidor: get, post, web, App, HttpResponse, HttpServer y Responder.
rust use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
El trait Responder es el que permite que una función devuelva una respuesta HTTP válida sin que tengas que construirla manualmente cada vez.
Actix exige que tu aplicación sea asíncrona, así que la función main se marca con async y con el macro #[actix_web::main]. La salida es un std::io::Result<()> porque el servidor puede fallar al enlazar el puerto [01:45].
rust #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(hello_world) }) .bind(("0.0.0.0", 9900))? .run() .await }
Fíjate en tres detalles que suelen romper a quienes empiezan:
bind devuelve un Result, así que necesitas ? o .unwrap()..await porque estás devolviendo el valor.¿Qué hace el macro
#[actix_web::main]? Convierte tu funciónmainsíncrona en un runtime asíncrono compatible con Actix. Sin él, no puedes usarasync/awaitenmain.
La función que responde en la raíz es muy directa. Le pones el macro #[get("/")] para asociarla a una URL y devuelves un HttpResponse::Ok con el cuerpo que quieras [03:30].
rust #[get("/")] async fn hello_world() -> impl Responder { HttpResponse::Ok().body("Hola, Platzi") }
Un error típico aquí es dejar el punto y coma después de HttpResponse::Ok().body(...). Si lo haces, la función devuelve () en vez de un Responder y el compilador se queja. Esa es la azúcar sintáctica de Rust: la última expresión sin punto y coma es el valor de retorno.
Corre el proyecto con cargo run, abre el navegador en 0.0.0.0:9900 y vas a ver el mensaje en pantalla. Cualquier cambio en el código requiere detener el servidor con Ctrl + C y volver a ejecutar.
Una sola conexión a base de datos no escala. Por eso Diesel ofrece r2d2, una librería que crea un pool de conexiones reutilizables [05:30]. Necesitas dos imports clave:
rust use diesel::r2d2::Pool; use diesel::r2d2::{self, ConnectionManager};
En el Cargo.toml activa la feature r2d2 de Diesel para que el compilador encuentre estos módulos.
Dentro de main, después de cargar las variables del .env, construyes el manager y el pool:
rust let connection = ConnectionManager::<PgConnection>::new(db_url); let pool = Pool::builder() .build(connection) .expect("No se pudo construir la pool");
El expect te da un mensaje claro si la conexión falla al arrancar, lo cual ahorra horas de debugging.
Actix usa web::Data para compartir estado entre handlers. Después de registrar el servicio, agregas el pool clonado al App:
rust HttpServer::new(move || { App::new() .service(hello_world) .app_data(web::Data::new(pool.clone())) })
Desde cualquier ruta, ahora puedes pedir el pool como parámetro y ejecutar queries de Diesel sin abrir conexiones nuevas a mano.
¿Qué es un pool de conexiones? Es un grupo de conexiones a la base de datos que se mantienen abiertas y se reparten entre las peticiones. Evita el costo de abrir y cerrar conexiones en cada request.
La palabra move aparece justo antes de las llaves del HttpServer::new y resuelve un tema profundo de Rust: el ownership [08:00]. Cuando el servidor arranca cada worker en un hilo distinto, necesita ser dueño de los datos que va a usar, en este caso el pool.
Sin move, el closure intentaría tomar prestado el pool del main, pero el main ya no existirá cuando los hilos estén corriendo. Con move, transfieres la propiedad del valor desde el hilo principal hacia cada hilo del servidor.
move traslada la propiedad de las variables capturadas al closure.pool.clone() crea una referencia contable nueva para que cada worker tenga la suya.Es uno de los superpoderes de Rust: te obliga a pensar en quién posee qué dato antes de ejecutar, y eso elimina toda una familia de errores de concurrencia en tiempo de compilación.
¿Ya levantaste tu servidor en el puerto 9900? Cuéntame en los comentarios qué mensaje pusiste en tu primer body y si el compilador te marcó algún typo en Connection o ConnectionManager.
como comentario el shortcut de unwrap es "?"
.bind(("0.0.0.0", 9900))?.run()
no tengo problema con que haya errores al codear pero desde el primer curso de rust que vi contigo me incomoda que no planees las clases o que digas que explicaras algo después y luego no explicarlo.
Hubo una actualización de Diesel, para la version 2 (he tenido muchos problemas con respecto a las versiones) y ahora fue con Diesel, estuve como 3 horas revisando y revisando (soy totalmente nuevo) logre armar una solución completa para esta clase, pero actualizado.
extern crate diesel;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use dotenvy::dotenv;
use std::env;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::Pool;
use diesel::r2d2::{self, ConnectionManager};
pub mod models;
pub mod schema;
#[get("/")]
async fn hello_world() -> impl Responder {
return format!("Hello world!");
}
#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DB url variable doesn't found");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder()
.build(manager)
.expect("No se pudo construir la pool");
HttpServer::new(move || App::new().service(hello_world).app_data(pool.clone()))
.bind(("localhost", 8080))?
.run()
.await
}
Para el archivo cargo se debe de agregar r2d2 como feature en Diesel ya que no viene activo por defecto.
// Cargo.toml
[package]
name = "blog-platzi"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = { version = "2.0.0", features = ["postgres", "r2d2"] }
dotenvy = "0.15"
actix-web = "4"
Como pueden observar, se debe de agregar de manera manual r2d2 y con esto funciona correctamente la importación del paquete.
Espero pueda apoyar a alguno de ustedes que este con el mismo problema
En lugar de 0.0.0.0, conviene siempre utilizar localhost o 127.0.0.1.
Actualmente .data() aparece como deprecado, una alternativa sugerido por Rust es cambiarlo por app_data()
En principio, no debería de cambiar nada relacionado con el curso
Diesel ha cambiado mucho, al dia de hoy 25/07/2023 pude hacer funcionar el programa. Resulta que uno debe descargar el archivo gettext0.21-iconv1.16-shared-64 (buscar en Internet), renombrarlo y ponerlo en la carpeta src para que se pueda ejecutar el programa.
Como recomendacion, si algo no funciona vayan a la documentacion porque lo mas seguro es que han actualizado algo, tambien es bueno revisar las respuestas de
Si no comprendí mal, todo en Rust tiene un "dueño". El concepto de Ownership indica que, en este ejemplo, el Pool de Conexión pertenece a main() y si queremos utilizarlo en otra función debemos compartirlo con la palabra reservada move para mover el hilo de ejecución principal a esa otra función, ya que Rust es mono-hilo. Similar a como funciona NodeJS que también lo es y necesita de la asincronisidad para procesar múltiples solicitudes a la vez.
Hector dice que: "ya sabemos que significa cada cosa", frase que me acaba de generar bastante incomodidad porque habla como si lo hubiese explicado todo, cuando en realidad no ha sido así. Muchos huecos conceptuales, parase ser que presuntamente el curso es improvisado, como si estuviera tratando de explicar el ejemplo de inicio de alguna documentación. Creo que sale mas eficaz leer personalmente la documentación y generar apoyo con chatGPT.
Si alguien se siente perdido hasta este punto, pare, no continúe. Tome aire, respire profundo, auto regule la frustración que siente hasta el momento, evita sentir enojo y claro que te entiendo, el curso básico de Rust no tiene nada que ver con este curso. Por esa razón te sugiero que ve a leer los siguiente libros y en el proceso que comprendas un par de cosas mas, regresa y continua si ese es tu deseo. Quizá y aprendas un par de cosas mas viendo este tutorial, y no, no todos los cursos de Platzi son así, asi que no te rindas. Si, es verdad, te va a tomar un poquito mas de tiempo leer los libros pero créeme, valdrá el esfuerzo, recuerda que es importante construir bases solidas.
++Libros sugeridos:++
Para entender mejor la "move" keyword
Closures Rust book
O el link completo: https://doc.rust-lang.org/book/ch13-01-closures.html?highlight=closure#moving-captured-values-out-of-closures-and-the-fn-traits
let mut x = String::from("Hello");
let func = move || { // <--- Con la move keyword, la closure toma ownership de la variable 'x'
// Move es como decir: "hey pasa la variable x a esta function como valor y no como referencia"
x.push_str(" World!");
};
println!("{}", x); // Y al tomar ownership de 'x' no se puede usar aquí
Al crear la App (9:21), al usar data sería app_data verdad?
Como debería de usar pool, como stado o estado mutable?
actix docs