Rusty Full Stack

El blog para los amantes de Rust, Ionic y Vuejs

Jaime Blandón
Jaime Blandón Desarrollador de software desde 2009, entusiasta de Rust, Vuejs y Ionic!. Fundador de este blog, espero que las publicaciones te sean de utilidad y si tienes comentarios para mejorar, son bienvenidos.

Desarrollo Web Con Rust - Archivos Estáticos Y Recursos

Hola! En la publicación anterior hemos iniciando el aprendizaje sobre el desarrollo web con Rust y actix web, si te gustaría echar un vistazo a la publicación puedes encontrarla en el siguiente enlace:

Desarrollo Web Con Rust - Introducción a Actix Web.

Si ves el ejemplo de la publicación anterior, te darás cuenta que hemos creado un HTML desde el servidor como un String y de esa forma hemos generado una página a la cual le colocamos un formulario con solamente un botón. Esa forma de responder no es la más fácil de mantener a largo plazo y nuestro código se volvería cada vez más complicado.

Para resolver el problema anterior, podemos utilizar algún framework en Rust que nos permita administrar de mejor manera las respuestas de contenido HTML, algunos de estos frameworks son:

  • Yew
  • Leptos
  • Tauri
  • Templates HTML con tera (si vienes de Django este será super familiar para ti)
  • Muchos más! 

Los primeros dos son muy similares a React, por si vienes de ese mundo estoy seguro que te gustarán. 😉

También puedes simplemente crear algún API con actix_web y simplemente integrar con React, Angular, Vuejs o cualquier otro framework javascript de tu preferencia.

Por sencillez, en esta serie, utilizaremos templates HTML con tera, ya que es el camino más fácil de aprender y es HTML, css y javascript de toda la vida, pero no te preocupes que más adelante aprenderemos sobre Yew, Leptos y otros frameworks más parecidos a la forma actual de hacer desarrollo web como React, Vuejs, etc.

Ahora es importante hacernos la siguiente pregunta:

¿Qué tienen en común los frameworks anteriores?

Algo en común que encontrarás en cualquier framework web ya sea hecho con Rust o no, que incluso es común con HTML plano es el uso de recursos "assets" a los cuales también se les conoce como archivos estáticos "static".

¿Qué son los archivos estáticos o recursos?

Los archivos estáticos, como su nombre lo indica, son archivos que no cambian durante nuestro sitio web se muestra al usuario (a menos que quieras cambiarlos totalmente).

Usualmente estos archivos estáticos o recursos son utilizados para hacer nuestro sitio web más agradables para nuestros usuarios, como por ejemplo imágenes, videos, audios, archivos de estilo (css), archivos javascript (o typescript) y más.

En esta serie aprenderemos como utilizar estos recursos pero no vamos a profundizar en CSS ni javascript ya que son demasiado amplios, aunque eso no será un bloqueo para que puedas continuar con la serie, sin embargo, si no has trabajado anteriormente con css y javascript, te recomiendo conocer conceptos básicos, en internet existen muchos recursos con los cuales puedes familiarizarte con ellos.

Debido a que los archivos estáticos son un elemento común y ampliamente utilizado en el desarrollo web, vamos aprender a utilizarlos con Rust y actix web para luego poder dar una mejor vista y experiencia de usuario en nuestra aplicación web.

¿Cómo Manejar Archivos Estáticos Y Recursos Con Rust y Actix Web?

Vamos a realizar un ejemplo mediante el cual accederemos a una ruta o url y mostraremos en el navegador un archivo estático. En el ejemplo vamos a visualizar tres tipos  de archivos: css, javascript e imágenes, pero es prácticamente lo mismo para devolver otros tipos como audios o videos.

En este ejemplo se asume que tienes un conocimiento básico de actix web, como por ejemplo peticiones get y agregar rutas al servidor web, si tienes alguna duda, puedes referirte a la publicación anterior:

Desarrollo Web Con Rust - Introducción a Actix Web.

Comencemos con nuestro ejemplo creando un nuevo proyecto, desde nuestra terminal ejecutemos el siguiente comando:

cargo new archivos-estaticos-actix-web

cd archivos-estaticos-actix-web

Recuerda que puedes encontrar el código completo en el repositorio de github.

Ahora agreguemos actix-web a nuestro archivo Cargo.toml, pero también agregaremos una nueva dependencia llamada actix-files, el cual nos facilita el manejo de archivos estáticos con Rust y actix. Nuestro archivo Cargo.toml debería verse de la siguiente manera:

        
        
            
// Cargo.toml

[package]
name = "archivos-estaticos-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-files = "0.6.5"
actix-web = "4.4.1"

        
        
    

Ahora vamos a crear una estructura de carpetas en la cual buscaremos nuestros archivos estáticos. La carpeta principal la colocaremos en el raíz de nuestro proyecto, esto es debido a que no es buena práctica colocarlo dentro de src/ porque entonces sería parte de nuestro archivo compilado y nos obligaría a compilar nuestro programa cada vez que agreguemos algún archivo, esto sería muy complicado de mantener si queremos poder subir recursos como archivos, imágenes o videos (lo cual es muy probable que lleguemos a implementar tarde o temprano en cualquier proyecto).

El nombre que le pongas a tu carpeta de archivos estáticos depende de tu preferencia, los más comunes son "static" y "assets", pero eso queda a tu gusto. En este ejemplo le llamaremos "static".

En la carpeta raíz puedes crear una carpeta "static" y dentro de estatic crearemos 3 carpetas adicionales "css" para los archivos css, "js" para los archivos javascript y "img" para las imágenes. Nuestra estructura de carpetas se debe ver como la siguiente (observa que "static" está al mismo nivel que "src":

Con nuestra estructura de carpetas creada, ahora vamos a colocarle algunos archivos estáticos para que luego podamos acceder a ellos desde nuestro servidor web hecho con Rust y actix web. Por el momento no es importante entender el código del archivo css ni el de javascript, lo único que queremos comprobar es que podamos ver su contenido con actix web.

Dentro de static/css coloca el contenido del siguiente archivo y nómbralo bootstrap.min.css:

bootstrap.min.css

En static/js descarga el contenido el siguiente archivo y nómbralo como bootstrap.min.js:

bootstrap.min.js

Finalmente descarga las siguientes imágenes y colócalas dentro de static/img, nómbralas como image1.jpeg y la otra como image2.jpeg

image1.jpeg

image2.jpeg

Tu estructura de archivos estáticos se debería de ver de la siguiente manera:

Lo siguiente que haremos es crear una función en nuestro archivo src/main.rs la cual nos permitirá acceder mediante una petición GET a nuestros archivos estáticos.

Algo a tomar en cuenta será la ruta de nuestros archivos estáticos, si nos fijamos bien, todos nuestros archivos tienen como padre la carpeta "static" por lo cual esa carpeta será fija en las rutas para encontrar nuestros archivos.

Otra cosa a tomar en cuenta es que nuestros archivos estáticos pueden tener diferentes extensiones como por ejemplo .css, .js, .jpeg, por lo que debemos permitir que en las rutas o url, nuestro cliente web pueda colocar esas extensiones. También otra cosa a tomar en cuenta, es que vamos a acceder a ellos utilizando su nombre propio, con actix-files podemos utilizar el trait NamedFile para dicho propósito.

En el archivo src/main.rs coloca el siguiente código:

        
        
            
// src/main.rs

use actix_files::NamedFile;
use actix_web::{get, Error, HttpRequest, Result};
use std::path::PathBuf;

// la ruta base sera /static/ luego el filename es un parametro de tipo url
// el parametro "filename" puede contener cualquier string como por ejemplo js/, css/, img/ etc
// agregarle el .* quiere decir que el parametro permitira cualquier tipo de extension como .css,
// .js, .html, etc. Se puede restringir el tipo de archivo que se quira leer, pero para nuestro
// caso de uso, .* es suficiente.
#[get("/static/{filename:.*}")]
async fn archivo_estatico(req: HttpRequest) -> Result<NamedFile, Error> {
    // Obteniendo un la ruta o pah del archivo.
    let ruta: PathBuf = req.match_info().query("filename").parse().unwrap();
    // Con el path del archivo, sera necesario agregarle el folder base de los archivos estaticos
    // para ello debemos pasar el path a un String al cual podamos agregarle el folder base.
    let mut ruta_string = ruta.into_os_string().into_string().unwrap();
    // Agregando el folder base, que le hemos llamado "static", es importando agregar "./" porque
    // la ruta base esta en el root del proyecto.
    ruta_string = format!("./static/{}", ruta_string);
    // Ahora con la ruta vamos a abrir el archivo en modo solo lectura con el metodo "open"

    // Notar el "?" al final de la linea, esto permitira regresar un Error
    // si no se puede abrir o encontrar el archivo entonces se traduce en un error
    // con status HTTP 404 (archivo no encontrado)
    let archivo = NamedFile::open(ruta_string)?;

    // Si el archivo es encontrado, entonces devolvemos la ultima version
    Ok(archivo.use_last_modified(true))
}

fn main() {
    println!("Hello, world!");
}

        
        
    

Vamos a explicar la función para leer los archivos estáticos línea por línea, lo primero a notar es que el decorador get, ahora tiene una particularidad:

#[get("/static/{filename:.*}")]

La sección {filename:.*} se considera en actix web como un parámetro mediante URL, es decir, que actix reconocerá que en la ruta web, después de "static/" puede llegar cualquier string como por ejemplo:

/static/css/bootstrap.min.css

/static/js/bootstrap.min.js

Hablaremos más profundamente sobre parámetros de url en otra publicación, pero por el momento basta con saber que ahora podremos invocar una url con diferentes archivos estáticos.

Luego en la definición de la función:

        
        
            

async fn archivo_estatico(req: HttpRequest) -> Result<NamedFile, Error> {
        
        
    

Se recibe como parámetro un HttpRequest, Es decir que actix web nos permite poder recibir todos los valores del protocolo HTTP, una parte de esos valores son los parámetros de url, por lo que podremos obtener el nombre del archivo desde ahí. De igual forma se especifica que se retornará un resultado con el archivo (NamedFile) o un error en caso se produzca.

La línea:

        
        
            

let ruta: PathBuf = req.match_info().query("filename").parse().unwrap();
        
        
    

Permite obtener una ruta (PathBuf) la cual se obtiene del parámetro filename (que debe ser el mismo nombre definido en el macro #[get], es decir que esa "ruta" puede ser por ejemplo:

css/bootstrap.min.css 

sin colocarle "./static/" a la ruta, es decir, la misma carpeta "static" de la raíz de nuestro proyecto, por lo cual nos tocará ponerlo a nosotros, ya que sino, no se podría abrir el archivo. Ese es el motivo de estas líneas:

        
        
            

let mut ruta_string = ruta.into_os_string().into_string().unwrap();
ruta_string = format!("./static/{}", ruta_string);
        
        
    

Luego de ello podemos abrir el archivo (NamedFile) para luego la función retornarlo

        
        
            

let archivo = NamedFile::open(ruta_string)?;
        
        
    

Ahora al momento de devolverlo, indicamos que utilizaremos la última versión del archivo:

        
        
            

Ok(archivo.use_last_modified(true))
        
        
    

Para nuestro caso de uso eso sería suficiente, pero por si necesitas que el archivo se descargado como un adjunto, puedes modificar el retorno de la función de esta forma, aunque para nuestro ejemplo no te recomiendo hacerlo ya que solo queremos leerlo desde el navegador:

        
        
            

Ok(archivo
        .use_last_modified(true)
        .set_content_disposition(ContentDisposition {
            disposition: DispositionType::Attachment,
            parameters: vec![],
        }))
        
        
    

Lo último que nos queda es agregar nuestro servidor Http y nuestra App con la ruta para ingresar a los archivos estáticos, como agregamos rutas en el artículo anterior, simplemente completaremos el código, tu archivo src/main.rs debe verse de la siguiente manera:

        
        
            
// src/main.rs

use actix_files::NamedFile;
use actix_web::{get, App, Error, HttpRequest, HttpServer, Result};
use std::path::PathBuf;

// la ruta base sera /static/ luego el filename es un parametro de tipo url
// el parametro "filename" puede contener cualquier string como por ejemplo js/, css/, img/ etc
// agregarle el .* quiere decir que el parametro permitira cualquier tipo de extension como .css,
// .js, .html, etc. Se puede restringir el tipo de archivo que se quira leer, pero para nuestro
// caso de uso, .* es suficiente.
#[get("/static/{filename:.*}")]
async fn archivo_estatico(req: HttpRequest) -> Result<NamedFile, Error> {
    // Obteniendo un la ruta o pah del archivo.
    let ruta: PathBuf = req.match_info().query("filename").parse().unwrap();
    // Con el path del archivo, sera necesario agregarle el folder base de los archivos estaticos
    // para ello debemos pasar el path a un String al cual podamos agregarle el folder base.
    let mut ruta_string = ruta.into_os_string().into_string().unwrap();
    // Agregando el folder base, que le hemos llamado "static", es importando agregar "./" porque
    // la ruta base esta en el root del proyecto.
    ruta_string = format!("./static/{}", ruta_string);
    // Ahora con la ruta vamos a abrir el archivo en modo solo lectura con el metodo "open"

    // Notar el "?" al final de la linea, esto permitira regresar un Error
    // si no se puede abrir o encontrar el archivo entonces se traduce en un error
    // con status HTTP 404 (archivo no encontrado)
    let archivo = NamedFile::open(ruta_string)?;

    // Si el archivo es encontrado, entonces devolvemos la ultima version
    Ok(archivo.use_last_modified(true))
}

// Es importante notar que main devuelve un Result pero de std, no the actic_web
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(archivo_estatico))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

        
        
    

Ahora podemos iniciar nuestro servidor Http con el comando:

cargo run

Con nuestro servidor activo:

Ahora deberíamos poder ingresar a nuestros archivos estáticos, para ello, en tu navegador intenta ingresar al siguiente enlace:

http://localhost:8080/static/img/image1.jpeg

Si todo ha salido bien, en tu navegador debería de verse la siguiente imagen:

Si logras ver la imagen, entonces ya estás logrando regresar archivos estáticos 🥳!! 

Ahora intentemos ver nuestro archivo css o javascript.

Por ejemplo para ver el archivo css puedes ingresar a:

http://localhost:8080/static/css/bootstrap.min.css

O si quieres ver el archivo javascript:

http://localhost:8080/static/js/bootstrap.min.js

Perfecto, ahora podemos acceder incluso a archivos css y javascript, con lo cual podemos intuir que ahora podríamos colocar en un HTML las referencias a esos archivos.

Recuerda que puedes encontrar el código completo en el repositorio de github.

En el siguiente artículo, aprenderemos como poder utilizar estos archivos estáticos con templates HTML 😊

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!");


 Utilizamos cookies propias y de terceros para mejorar tu experiencia, mostrar publicidad y análisis de navegación, puedes encontrar el detalle en nuestra Política de Cookies