Web Apps con Rust + Rocket
Tags: rust
Published: 2019-01-27 16:49:13

De acuerdo a su sitio, **[Rocket](https://rocket.rs/)** es un framework web para Rust que hace que sea sencillo escribir aplicaciones web rápidas y seguras, sin sacrificar flexibilidad, usabilidad y el tipado fuerte.

La última versión disponible es la 0.4 publicada el 6 de Diciembre, el día que se lanzó Rust 2018, lo que garantiza su compatibilidad con la última edición del lenguaje. Rocket funciona con Rust Nightly.

## Configuración

- Instalar Rust Nightly con **rustup** si no se tiene instalado.

```bash
$ rustup install nightly
```

- Crear un nuevo proyecto con **Cargo**.

```bash
$ cargo new hello_rocket
```

- Asignar Nightly al proyecto creado.

```bash
$ cd hello_rocket
$ rustup override set nightly
```

## Estructura de directorios

La estructura del directorio de un proyecto desarrollo con Rocket es la siguiente:

- src
- static
- templates
- Cargo.toml

**static** es el directorio en el que se guardan las imágenes, archivos de **JavaScript** y hojas de estilo (**CSS**).

En el directorio **templates** se guardan las plantillas HTML.

Estos directorios deben crearse.


## Templates
Rocket soporta dos sistemas de plantillas, **[Tera](https://github.com/Keats/tera)** que está inspirado en **[Jinja2](http://jinja.pocoo.org/)** y el **[lenguaje de plantillas de Django](https://docs.djangoproject.com/en/1.9/topics/templates/)**, y **[Handlebars](https://github.com/sunng87/handlebars-rust)**.

## ¡Hola, mundo!

¡Es momento de crear el primer ejemplo con Rocket!

### Cargo.toml

Agregar Rocket como dependencia al archivo **Cargo.toml**.

```toml
[package]
name = "hello_rocket"
version = "0.1.0"
authors = ["mattdark"]
edition = '2018'

[dependencies]
rocket = "0.4"
```

### src/main.rs

Editar el archivo **main.rs** en **src** y colocar el código que mostrará el texto "**¡Hola, mundo!**" en el navegador.

```rust
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "¡Hola, mundo!"
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}
```

Con la línea `#![feature]` se le indica a Rust que se usarán funcionalidades del compilador disponibles en Nightly.

Se importa el crate de Rocket y todas sus macros al espacio de nombres mediante `#[macro_use] extern crate rocket;`.

Se declara la función `index` que mostrará el texto en el navegador y en la función principal `main` se inicia el servidor de la aplicación.

### Iniciando la aplicación

Se ejecuta el siguiente comando para iniciar el servidor de la aplicación.

```bash
$ cargo run
```

El comando anterior se encarga de descargar las dependencias del proyecto, de acuerdo a la información del archivo **Cargo.toml**, realiza la compilación, genera el binario correspondiente e inicia el servidor.

En caso de no haber error alguno, en la terminal aparece lo siguiente.

```bash
🔧 Configured for development.
    => address: localhost
    => port: 8000
    => log: normal
    => workers: 4
    => secret key: generated
    => limits: forms = 32KiB
    => keep-alive: 5s
    => tls: disabled
🛰  Mounting /:
    => GET / (index)
🚀 Rocket has launched from http://localhost:8000
```

Desde la barra de direcciones del navegador se accede a http://localhost:8000 y debe mostrar lo siguiente.
![Hola mundo](https://thepracticaldev.s3.amazonaws.com/i/1rsyolclpvihu1gxub5m.png)

## Routing
```rust
#[get("/world")] {            // <- route attribute
fn world() -> &'static str {  // <- request handler
    "¡Hola, mundo!"
}
```

La función `world`, declarada en el código anterior, corresponde al manejador de peticiones y está asociada a la ruta `/world`, la cual recibirá peticiones de tipo `GET`.


## Mounting
Después de declarar una ruta, indicar el tipo de peticiones que recibirá y declarar la función que se encargará de manejar las peticiones, esta deberá montarse como se muestra a continuación.

```rust
fn main() {
    rocket::ignite.mount("/hello", routes![world]).launch();
}
```

Lo anterior crea un nueva instancia de Rocket con la función `ignite` y monta la ruta `world` en la dirección `/hello`. Las peticiones `GET` a `/hello/world` se redigirán a la función `world`. El servidor de la aplicación se inicia con la función `launch`.

Una vez iniciado el servidor, desde la barra de direcciones acceder a http://localhost:8000/hello/world y mostrará el siguiente texto.
![Hola mundo](https://thepracticaldev.s3.amazonaws.com/i/1rsyolclpvihu1gxub5m.png)

Las rutas `hello` e `index` no están declaradas por lo que si se intenta acceder a http://localhost:8000 y http://localhost:8000/hello mostrará lo siguiente.
![404](https://thepracticaldev.s3.amazonaws.com/i/r4njclu34u8di2eysekr.png)

Estas rutas y su manejador correspondiente deberán declararse en el código en caso de que se necesite, y montarlas usando la macro `routes`.

## Métodos
Los métodos soportados por Rocket son los siguientes:
- get
- put
- post
- delete
- head
- patch
- options

## Direcciones dinámicas

```rust
...
use rocket::http::RawStr;

#[get("/hello/<name>")]
fn hello(name: &RawStr) -> String {
    format("¡Hola, {}!", name.as_str())
}
```

Una ruta puede recibir parámetros, como se observa en el código anterior, en el que la ruta `hello` recibe el parámetro `<name>`, que en el manejador de peticiones correspondiente, `hello`, guarda en la variable `name`, para luego imprimir como parte del texto que mostrará en el navegador.

&RawStr es un tipo de dato de Rocket, por lo que hay que decirle a Rust que se usará con la línea `use rocket::http::RawStr`.

Al visitar `/hello/Mario` en el navegador aparecerá lo siguiente.
![Hola Mario](https://thepracticaldev.s3.amazonaws.com/i/k8y1b9sqh8dn5kgui68i.png)

## Archivos estáticos
En la estructura de directorios de Rocket, el directorio **src** es donde se guardan las imágenes, archivos de **JavaScript** y hojas de estilo (**CSS**).

```rust
...
use std::path::{Path, PathBuf};
use rocket::response::NamedFile;
...
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
     NamedFile::open(Path::new("static/").join(file)).ok()
}

fn main() {
    rocket::ignite().mount("/", routes![index, files]).launch();
}
```

Toda petición `GET` para abrir un archivo enviada a `/` será recibida en el parámetro `<file..>` y manejada por la función `files`.

La línea `NamedFile::open(Path::new("static/").join(file)).ok()` se usa para decirle a Rust cual es el directorio donde se encuentran los archivos estáticos.

Con una nueva ruta declarada, esta debe agregarse a la macro `routes` para que pueda montarse correctamente. 

## Plantillas
Rocket soporta **Tera** y **Handlebars** como sistemas de plantillas.

### Cargo.toml

En el archivo **Cargo.toml** se agrega lo siguiente para decirle que se usará **Handlebars**. Si se usa **Tera** solo se reemplaza `handlebars` por `tera`.

```toml
...
handlebars = "1.1.0"

[dependencies.rocket_contrib]
version = "0.4"
features = ["handlebars_templates"]
```

### src/main.rs
Una vez que se indica cuales son las dependencias necesarias para usar cualquiera de los sistemas de plantillas, se modifica el archivo **main.rs** en **src**.

```rust
...
extern crate rocket_contrib;
use rocket_contrib::templates::{template, handlebars};

#[get("/")]
fn index() -> Template {
    Template::render("index")
}
...
fn main() {
    rocket::ignite().mount("/", routes![index, files])
    .attach(Template::fairing())
    .launch();
}
```
En primer lugar se importa el crate **rocket_contrib** y todas sus macros con la línea `extern crate rocket_contrib`. En seguida se indica el sistema de plantillas que se usará con la línea `use rocket_contrib::templates::{template, handlebars};`.

Para importar una plantilla se usa la función `render` y se especifica el tipo de dato que la función debe retornar, en este caso es `Template`.

Para asegurar que el middleware (fairing, como se conoce en Rocket) de Template se adjunte a la aplicación, en la función principal se agrega la línea `.attach(Template::fairing())`.

Las plantillas deberán colocarse en el directorio **templates** y el nombre debe corresponder con el indicado en `Template::render("index")`. Del código anterior se infiere que el nombre de la plantilla es `index.html.hbs`.

En este artículo he descrito de manera general como se crea una aplicación web con Rocket. Espero que la información sea útil. En un próximo artículo mostraré un ejemplo de aplicación desarrollada con este framework.