Rusty Full Stack
El blog para los amantes de Rust, Ionic y Vuejs
El blog para los amantes de Rust, Ionic y Vuejs
Hola, ha sido un largo tiempo desde la última publicación en nuestro blog, las disculpas por la ligera pausa que he tenido debido, pero espero este año sea de mucho contenido para el blog y que nos divirtamos mucho aprendiendo sobre Rust 😉
Esta publicación, es la primera de una larga serie que planeo estar escribiendo y subiendo al canal de youtube sobre el desarrollo web con Rust. Personalmente es una serie que me emociona mucho poderles compartir debido a que el desarrollo web ha sido mi día a día desde hace muchos años y descubrir que con Rust podemos desarrollar aplicaciones web con el mismo nivel de profesionalismo que otros lenguajes como javascript, python, etc, fue un descubrimiento bastante agradable 😍
Cuando inicié mi camino de aprendizaje con Rust, uno de los temas que siempre me causó inquietudes era el desarrollo web (APIs, sitios web, apps, etc) debido a que es gran parte de mi experiencia profesional.
Sin embargo, algo que noté es que la documentación y recursos era bastante limitado incluso en inglés y en español era casi inexistente, pero eso no me detuvo y a base de documentación técnica, leer el código fuente de distintos frameworks, etc. logré crear mi primera aplicación web, la cual, no tenía nada que envidiar a aplicaciones web similares que había creado anteriormente con otros lenguajes, esa aplicación fue precisamente el blog que estás leyendo en este momento, este blog fue creado utilizando Rust! 😇
Este blog que estás leyendo, no es ningún blog con contenido HTML estático o con algún servicio pre-construido, fue totalmente construido desde cero como parte de mi forma de verificar si podía hacer lo mismo que en otras tecnologías y hasta el momento ha funcionado.
Como parte de mi objetivo de esta serie es que tu también puedas construir tus propias aplicaciones web desde su inicio, hasta su publicación en el internet, aprovechando todos los beneficios de Rust como por ejemplo su manejo de memoria y velocidad.
Lo primero a tomar en cuenta es que necesitas conocer conceptos básicos del desarrollo web, como por ejemplo conocer sobre el protocolo HTTP, conocer sobre requests GET, POST, hostings, y mucho más.
Aunque en esta serie intentaré explicar de forma muy básica los conceptos que vayamos conociendo, no es parte de la serie el hablar en detalle de cada uno de estos conceptos, por lo que te recomiendo que si no tienes mucha experiencia o es la primera vez que intentarás crear una aplicación puedas leer un poco sobre lo básico del desarrollo web, en internet podrás encontrar muchos recursos, sin embargo, también puedes seguir esta serie sin conocer muchos detalles, pero te invito a profundizar en los conceptos que vayamos aprendiendo.
A parte de los conceptos básicos, un siguiente paso es escoger un framework web para Rust que nos provea de las funcionalidades que necesitemos y que al mismo tiempo nos facilite poder brindar seguridad a nuestra aplicación web.
Hoy en día existen varios frameworks web para Rust, por mencionar algunos de los más populares:
Cada uno de ellos tienen sus puntos fuertes y débiles (como cualquier framework web de cualquier lenguaje). De los listados anteriormente, si vienes del mundo de Django el framework web de python, entonces Rocket, se te hará muy familiar.
Entrar en detalle y hacer comparaciones entre cada framework web sería muy largo y nunca se podría obtener una conclusión definitiva de cuál framework es mejor que cual, porque incluso cada uno de ellos sigue en constante mejora.
Esta serie de centrará en el framework web actix-web, el cual es el framework web en Rust con el cual tengo más experiencia y me ha permitido poder hacer aplicaciones web con las características que he necesitado (deja en la sección de comentarios si te gustaría conocer sobre algún framework web específico 😉).
De acuerdo la definición de su propia documentación, "Actix Web es un marco web potente, pragmático y extremadamente rápido para Rust".
Desde mi propia opinión, elegí actix-web sobre otros frameworks como Rocket porque, en su momento fue el único que encontré que permitía utilizar websockets (ahora no solamente actix-web lo provee), el rendimiento es bastante rápido, su integración sencilla con diferentes mecanismos de seguridad y la forma de codificación, pero, a parte de actix, te invito a conocer lo básico sobre otros frameworks para que tu puedas tomar tu propia decisión, sin embargo, actix-web es un buen punto de inicio.
Como un dato curioso, hace algún tiempo, el creador de actix-web había dejado de dar mantenimiento al repositorio por distintos motivos, sin embargo, hoy en día sigue siendo mantenido por un nuevo equipo y sigue siendo actualizado y mejorado constantemente.
Ahora vamos a empezar creando una aplicación web bastante simple, la cual únicamente tendrá tres páginas:
De acuerdo, no suena como una aplicación que vaya a generar millones de dólares 😋 pero nos permitirá ir incursionando en el uso de Rust para la web y actix-web, el propósito del ejemplo es para aprender aprender lo básico que necesitamos y un poco de rutas y navegación.
Recuerda que puedes ver el código completo del ejemplo en el repositorio de github.
Empecemos creando nuestra aplicación, dentro de algún folder de tu preferencia, ejecuta el siguiente comando:
cargo new introduccion-actix-web
cd introduccion-actix-web
Ahora vamos a instalar actix-web, dentro de nuestro archivo Cargo.toml, agregamos la dependencia, tu archivo Cargo.toml deberá verse de esta forma:
// Cargo.toml
[package]
name = "introduccion-actix-web"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4.4.1"
Ahora que hemos agregado nuestra dependencia, empecemos a crear nuestra primer app web con Rust 😎
Lo primero que vamos a hacer es importar de actix-web lo que necesitaremos. Lo primero que vamos a crear es una página de saludo, únicamente dirá "Hola Mundo!" y vamos a accederlo como nuestra ruta raíz de nuestra aplicación ("/").
Cuando accedamos a la ruta raíz desde nuestro navegador, este hará una petición HTTP mediante el método GET, por lo cual debemos definir que ese será el método de acceso en nuestro código, también otra cosa importante a tomar en cuenta es que luego que nuestro navegador haga la petición GET, nuestro servidor, es decir, nuestra app web con Rust, deberá retornar una respuesta bajo el protocolo HTTP (HttpResponse). Para ello modifiquemos nuestro código en src/main.rs para importar las librerías necesarias, deberá verse de esta forma:
// src/main.rs
use actix_web::{get, HttpResponse, Responder};
fn main() {
println!("Hello, world!");
}
Por el momento no hemos hecho nada más que importar lo que necesitaremos para nuestra "página" de Hola Mundo, como puedes ver hemos importado lo siguiente:
Ahora pongamos todo junto para crear nuestra primera ruta, modifiquemos nuestro archivo src/main.rs para agregar la ruta raíz y que cuando sea accedida mediante un GET, entonces el servidor responda con un "Hola Mundo!"
// src/main.rs
use actix_web::{get, HttpResponse, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola Mundo!")
}
fn main() {
println!("Hello, world!");
}
Como puedes ver en el código anterior, solo hemos agregado una nueva función "hola", pero algunas cosas a tomar en cuenta son las siguientes:
A este punto ya tenemos nuestra primer página, pero todavía no tenemos como accederla, como has leído anteriormente, necesitamos de un servidor web que pueda interpretar el protocolo HTTP (HttpServer), pero algo a tomar en cuenta, es que un servidor web es el cual contiene la configuración de nuestra aplicación que desde ahora llamaremos App. La App, puede tener uno o muchos recursos como todas las rutas, conexiones a bases de datos, Cookies, y mucho más. Veamos como configurar nuestra App y también agregar nuestro servidor web.
En nuestro archivo src/main.rs, vamos agregar las librerias necesarias y actualizar la definición de nuestra función main para hacerla async; pero es importante agregar el macro actix_web::main para poder convertirla en asíncrona, además agregaremos nuestra App y su servidor web:
// src/main.rs
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola Mundo!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new())
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Como puedes ver, hemos convertido a nuestra función main en el punto de ingreso de nuestra aplicación "App" y hemos agregado el servidor web "HttpServer"
en async fn main, algunas cosas importantes a mencionar son:
Antes de ejecutar tu código, si abres tu navegador y en la barra de direcciones escribes:
http://localhost:8080
verás un mensaje de error similar a este (el mensaje puede variar dependiendo del navegador).
Este error quiere decirte que no hay nada ejecutándose bajo localhost y puerto 8080, ahora si vamos a ejecutar nuestra app web con el siguiente comando:
cargo run
Verás que ahora se instalan todas las dependencias y hay un momento en el que nuestra app se queda en un estado que parece estático, pero en realidad es que nuestro servidor se encuentra activo y listo para recibir peticiones desde nuestro cliente o navegador.
Ahora vuelve a ingresar en tu navegador a:
http://localhost:8080
Ahora el mensaje de error es diferente:
Si miras la imagen, esta hace referencia a un "HTTP ERROR 404" esto quiere decir que quien ha contestado al navegador ha sido nuestra aplicación web, es decir, ahora nuestra app puede ser utilizada! El error 404 quiere decir que nuestro servidor no pudo encontrar la página, en este caso, nuestra ruta raíz "/".
Si hemos definido ya la función para accederse desde la ruta raíz, ¿por qué nuestro servidor no la pudo encontrar?, para poder ser detectada, debemos agregar la ruta en la configuración de nuestra aplicación, para ello debemos modificar nuestro archivo src/main.rs y agregarla a App (si tienes corriendo el servidor web y quieres detenerlo, puedes hacerlo con Ctrl + c en tu terminal)
// src/main.rs
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola Mundo!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(hola))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Como puedes ver en el código anterior, todo es bastante parecido con excepción de esta instrucción:
App::new().service(hola)
Con .service estamos agregando la ruta, para ello colocamos el nombre de la función "hola" la cual ya hemos construido y utiliza el macro $[get("/")] por lo que nuestra App ejecutará la función "hola" cuando se haga una petición GET a la ruta raíz, si ejecutamos nuevamente nuestra app con:
cargo run
Y accedemos nuevamente a:
http://localhost:8080
Ahora veremos la página de saludo, nuestra app web con Rust está funcionando 😎!
Algo que podrás observar es que el fondo de pantalla, al menos en mi navegador, es negro, esto es debido a que en realidad, nuestro servidor no está respondiendo HTML, sino mas bien, un texto, en un momento más veremos como agregar HTML, pero por el momento agreguemos una nueva función de despedida, pero está vez, vamos a mostrar esa página cuando se ingrese a la ruta "/despedida".
Agreguemos la nueva función la cual será parecida a "hola", pero agregaremos la ruta de una forma diferente para mostrarte que existe más de una forma de agregar rutas a parte de .service. En nuestro archivo src/main.rs colocamos el siguiente código:
// src/main.rs
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola Mundo!")
}
async fn despedida() -> impl Responder {
HttpResponse::Ok().body("Adios!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hola)
.route("/despedida", web::get().to(despedida))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Como puedes ver hemos creado la función "despedida", pero no le hemos colocado el macro #[get("/despedida")], como la función no tiene le macro, debemos definirlo de forma explícita en nuestra App, si ves la línea:
.route("/despedida", web::get().to(despedida))
Se encarga de agregar la ruta "/despedida" y web::get (la cual hemos importado en el bloque anterior) hacen el amarre con la función "despedida". Esta es otra forma de poner rutas, tu puedes utilizar la que más te guste o convenga.
Si ahora ejecutamos nuestra app con:
cargo run
y visitamos en nuestro navegador la ruta:
http://localhost:8080/despedida
Veremos una nueva página!
Ya tenemos dos rutas en nuestra app 🥳
Si bien ahora podríamos navegar utilizando las direcciones:
http://localhost:8080
http://localhost:8080/despedida
las funciones que generan las respuestas únicamente están respondiendo con un texto en lugar de HTML y además solo hemos visto peticiones GET, pero aveces la navegación entre páginas es un poco más complicada, como por ejemplo si tuviéramos un formulario que cuando es procesado queremos hacer un "redirect" a otra página ¿cómo lo haríamos?.
Empecemos creando una nueva ruta, pero esta vez vamos a devolver un HTML que represente un formulario cuyo único componente sea un botón, este botón realizará una petición a nuestro servidor para procesar el formulario y luego hacer una redirección a nuestra página de despedida.
Primero agreguemos nuestro formulario y respondamos con HTML en lugar de solo un texto, el formulario lo colocaremos dentro de una función de nombre "formulario" y que es accesible mediante una petición GET, de una vez lo agregaremos a las rutas de nuestra App.
// src/main.rs
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola Mundo!")
}
async fn despedida() -> impl Responder {
HttpResponse::Ok().body("Adios!")
}
#[get("/formulario")]
async fn formulario() -> impl Responder {
HttpResponse::Ok().body(
r#"
<!doctype html>
<html lang="es">
<body>
<form method="post" action="/formulario">
<button type="submit">Adios</button>
</form>
</body>
</html>
"#,
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hola)
.route("/despedida", web::get().to(despedida))
.service(formulario)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Si observamos la función "formulario" veremos que ahora respondemos con un HTML, si has utilizado otros frameworks web, notarás que la forma de poner todo el HTML en una sola String no es la más elegante del mundo, la buena noticia es que existen procesadores de templates HTML que se integran muy bien con actix-web como por ejemplo tera, de lo cual hablaremos en otro artículo, pero por el momento lo vamos a dejar así.
Algo que debes notar también es que la definición del formulario indica que realizaremos una petición POST a la misma ruta "/formulario" cuando el usuario de click al botón:
<form method="post" action="/formulario">
Por lo tanto debemos tener algo en nuestro servidor que procese la petición POST, en este caso lo único que hará será una redirección a la despedida, en otro artículo también hablaremos de cómo capturar los inputs de un usuario y hacer procesamientos más complejos, pero apenas son nuestros primeros pasos por lo que haremos algo sencillo. 😉
Agreguemos ese "Redirect" y el método para procesar la petición POST:
// src/main.rs
use actix_web::{get, http::StatusCode, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola Mundo!")
}
async fn despedida() -> impl Responder {
HttpResponse::Ok().body("Adios!")
}
#[get("/formulario")]
async fn formulario() -> impl Responder {
HttpResponse::Ok().body(
r#"
<!doctype html>
<html lang="es">
<body>
<form method="post" action="/formulario">
<button type="submit">Adios</button>
</form>
</body>
</html>
"#,
)
}
#[post("/formulario")]
async fn procesar_formulario() -> impl Responder {
web::Redirect::to("/despedida").using_status_code(StatusCode::FOUND)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hola)
.route("/despedida", web::get().to(despedida))
.service(formulario)
.service(procesar_formulario)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Lo primero a observar del código anterior es que ahora hemos agregado a nuestro use un nuevo macro "post" y el enum StatusCode.
use actix_web::{get, http::StatusCode, post, web, App, HttpResponse, HttpServer, Responder};
Luego hemos creado una nueva función procesar_formulario, pero algo interesante a destacar es que arriba de su definición tiene la misma ruta que otro método get:
#[get("/formulario")]
async fn formulario() -> impl Responder {
HttpResponse::Ok().body(
r#"
<!doctype html>
<html lang="es">
<body>
<form method="post" action="/formulario">
<button type="submit">Adios</button>
</form>
</body>
</html>
"#,
)
}
#[post("/formulario")]
async fn procesar_formulario() -> impl Responder {
web::Redirect::to("/despedida").using_status_code(StatusCode::FOUND)
}
Esto se debe a que en el protocolo HTTP existen varios método: GET, POST, PUT, etc. Esto quiere decir que una misma ruta puede ser accedida por más de un método, pero es IMPORTANTE recordar que el nombre de la función debe ser siempre diferente ("formulario" y "procesar_formulario")
Y por último la línea:
web::Redirect::to("/despedida").using_status_code(StatusCode::FOUND)
Hace un redirect, pero utilizando .using_status_code y pasándole StatusCode::FOUND
Esto quiere decir que se hace un redireccionamiento a despedida, pero, "detrás de cámaras" se coloca el status code 301 del protocolo HTTP, otra forma de hacerlo es colocando asi, la forma en que decidas hacerlo dependerá de tu preferencia porque ambos son equivalentes.
return Ok(HttpResponse::Found().append_header(("Location", "/despedida")).finish());
Sin embargo, para nuestro ejemplo no te funcionará porque necesitarás hacer algunas modificaciones extras que aprenderemos más adelante, pero quería compartirte la información. 😝
En el código anterior ya hemos agregado la ruta en App. Ahora podemos ejecutar el código:
cargo run
Si ingresamos a:
http://localhost:8080/formulario
Observamos nuestro formulario con solo un botón y ahora el fondo es blanco porque estamos retornando contenido HTML!! 🤩
Si ahora damos click al botón, entonces se ejecuta una petición POST desde el formulario y al procesarla hace un redirect a nuestra despedida.
Aunque el ejemplo anterior lo pudimos haber hecho solo con un enlace <a>, hacerlo de esta forma nos ayuda entender un poco la navegación con actix-web.
Felicidades ahora hemos creado nuestra primer app web con Rust! 🥳
Recuerda que puedes ver el código completo del ejemplo en el repositorio de github.
Entiendo que a este punto tengas muchas preguntas o dudas como por ejemplo el manejo de parámetros, formatos JSON, Cookies, seguridad y muuuuuchhiiiiismas dudas más, espero poder ir aclarando todas esas dudas en futuros artículos, pero no vayas a dudar el dejar tus preguntas en la sección de comentarios.
Si esta publicación te ha sido de utilidad, compártela con tus amigos y en las redes sociales. No te olvides de seguirme en X (twitter) ni de suscribirte al canal de youtube.
Si esta información te ha sido de utilidad y te gustaría apoyar a la creación de más artículos y contenidos puedes convertirte en mecenas desde patreon te estaré sumamente agradecido 🦀
println!("Hasta la próxima!");