miércoles, 24 de junio de 2020

Creando aplicaciones web usando Rust y Rocket (Parte 2)

Continuaremos con el ejemplo que generamos en la entrada anterior para entender más el funcionamiento de Rocket. Podemos cambiar el valor "/" del atributo "#[get("/")]" para cambiar el URL / URI que usa nuestro servidor para apuntar a nuestra función. A esto Rocket le llama las rutas y el enrutamiento ("routing"). Este valor es similar a la "/" que se encuentra en la línea del archivo main.rs: "rocket::ignite().mount("/", routes![index]).launch();" ya que este valor también determina la ruta que nos permite acceder a la página.

En el ejemplo de la entrada anterior usamos "#[get("/")]" y "rocket::ignite().mount("/", routes![index]).launch();". El resultado de esto es que directamente al abrir http://localhost:8000 se activa nuestra función. Esta función Rocket la llama "handler" que es nuestra función encargada de manejar toda solicitud que sea enrutada a ella.

¿Cómo podemos hacer para que nuestra función solo se ejecute con el URL / URI http://localhost:8000/hello ? Esto es muy simple sólo cambiamos nuestro valor de "#[get("/")]" a "#[get("/hello")]":

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;

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

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

Probaremos este cambio ejecutando el comando "cargo new". El resultado es que ahora cuando intentamos abrir http://localhost:8000 directamente no podemos y nos regresa un error 404:
Intentando abrir http://localhost:8000 directamente con "#[get("/hello")]" hace que regrese un error
Esto es porque al poner el valor a "#[get("/hello")]" hemos cambiado el enrutamiento. Es decir nuestra funcionalidad ahora está en otra ruta. Por lo anterior si intentamos abrir la ruta correcta que es http://localhost:8000/hello entonces obtenemos el comportamiento de la entrada anterior:
Abriendo http://localhost:8000/hello resulta en el comportamiento que esperamos
Ahora cambiemos también lo que está en el main(). De forma equivalente a lo que acabamos de ver podemos cambiar el valor del main() de "rocket::ignite().mount("/", routes![index]).launch();" a "rocket::ignite().mount("/api", routes![index]).launch();". Como es de esperarse, el resultado es un error si intentamos abrir http://localhost:8000 o http://localhost:8000/hello:

Intentamos abrir http://localhost:8000 o http://localhost:8000/hello 
También tenemos un error si intentamos abrir http://localhost:8000/api :
Intentamos abrir http://localhost:8000/api pero nos da el mismo mensaje de error
Es decir, ahora si queremos que se ejecute index() tenemos que usar exactamente http://localhost:8000/api/hello :
Abrimos http://localhost:8000/api/hello y funciona como esperamos
Con esto Rocket puede limitar el acceso a las funcionalidades y cuidar que solo se ejecuten las funciones encargadas de manejar las operaciones que requerimos.

Una variación que no mostré aquí es dejando "/" en el get, pero cambiando "rocket::ignite().mount("/", routes![index]).launch();" a "rocket::ignite().mount("/api", routes![index]).launch();". En este punto después de ver todo lo anterior es fácil predecir lo que va a pasar. Si realizamos este cambio el comportamiento será que todas las direcciones "http://localhost:8000", "http://localhost:8000/hello" y "http://localhost:8000/api/hello" serán inválidas, y la correcta ahora será http://localhost:8000/api :
Si realizamos el cambio descrito anteriormente ahora nuestra funcionalidad será accesible en http://localhost:8000/api
De las estructuras anteriores la más extensible es dejar que sea accesible usando http://localhost:8000/api/hello para dejar abierta la posibilidad de extender nuestra API agregando nuevas funciones, por ejemplo podemos agregar una función que imprima un mensaje de despedida usando: http://localhost:8000/api/goodbye .

Fuentes:

lunes, 22 de junio de 2020

Creando aplicaciones web usando Rust y Rocket (Parte 1)

Los resultados de la encuesta de Rust de 2019 reflejan que la mayoría de los que trabajan con Rust en tiempo completo son programadores / ingenieros en software que trabajan en "backend web applications" que son simplemente aplicaciones web para servicios de fondo. Estos servicios de fondo se encargan de procesar entradas de datos y a partir de esto regresar un resultado o realizar una actualización en una base de datos. Usualmente el usuario no interactúa con estos servicios directamente.

Rust cuenta ya con algunos "crates" o librerías que ayudan crear estos servicios de fondo. Información de estos se puede encontrar en la página "Are we web yet?". He decidido seguir la guía  de uno de estos frameworks web para servicios web y clientes web para probar las capacidades de Rust en esta área.

Viendo la sección de frameworks https://www.arewewebyet.org/topics/frameworks/ resaltan algunos por su número de descargas:
  • 1,200,000 descargas para actix-web. Actualmente parece ser el framework más completo, complejo y paralelo. El problema es que puede ser muy complicado para un principiante de Rust. Puede usarse en la actual versión estable de Rust y usa funcionalidades async de Rust. 
  • 734,000 descargas para iron. Es un framework que actualmente no ha sido mantenido considerablemente pero dadas las descargas parece ser que era importante.
  • 515,000 descargas para rocket. En la versión actual de rocket la versión actual estable de Rust no es suficiente para que compile. Rocket requiere algunas funcionalidades "experimentales" que se están estabilizando y/o se planean remover de Rocket para que Rocket pueda compilar en la versión estable de Rust. Pero por ahora para usar rocket necesitamos la versión "nightly" de Rust. Eventualmente esta planeado que pueda usarse con la versión estable de Rust. A pesar de esto hay compañías que ya están usando Rocket en producción según lo mencionado en la plática "Rocket: Securing the Web at Compile-time" que puede encontrarse en Youtube. Rocket también actualmente no parece usar las funcionalidades de async de Rust así que parece ser una buena opción para aplicaciones pequeñas que no se enfrentan a una gran demanda. Para aplicaciones más demandantes actix-web podría ser una mejor opción.
  • 402,000 descargas para wrap. Este framework parece ser un poco más nuevo que Rocket. A diferencia de Rocket este puede usarse en la versión estable actual de Rust y usa funcionalidades async de Rust. 
Para esta prueba quiero un framework simple que me permita hacer un API REST que reciba y regrese JSON. Por lo que me decidí por Rocket ya que parece más amigable para principiantes de Rust. A continuación seguiré el proceso de ejemplo descrito en la página oficial de Rocket: https://rocket.rs/v0.4/guide/getting-started/

El primer paso es crear un proyecto usando "cargo new" y instalando Rust Nightly usando "rustup default nightly":

Creando el proyecto y instalando rust nightly usando "rustup default nightly"
Para agregar Rocket a nuestro proyecto solo es cuestión de agregar rocket al archivo "cargo.toml". 

[package]
name = "rocket-test"
version = "0.1.0"
authors = ["MiUsuario <micorreo@email.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rocket = "0.4.5"

Como se puede ver la versión de rocket al momento de esta publicación es 0.4.5. El siguiente paso es copiar y pegar el código de ejemplo en el archivo "main.rs", en este caso cambié el mensaje a "Hola, mundo! Estoy usando Rocket":


#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;

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

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

Como podemos ver Rocket hace uso de atributos de Rust. Los atributos son las líneas que empiezan con "#" y afectan lo que sigue inmediatamente después de estos. Cuando un atributo afecta todo un ejecutable o toda una librería o todo un módulo se usa "#!" en lugar de sólo "#".

Como podemos ver por el uso de "#![feature(...)]" Rocket usa ciertas características/features de Rust que se activan con la línea "#![feature(proc_macro_hygiene, decl_macro)]". Luego tenemos que activar el uso de macros de Rocket (macros externos) usando la línea "#[macro_use] extern crate rocket;".

Usando el atributo "#[get("/")]" parece ser la forma en la que etiquetamos una función para que Rocket la maneje. Supongo que estos serán análogos a las funciones GET, PUT, DELETE y POST que se usan en HTTP/REST. En este caso la función "index" regresa un "str" que vive durante toda la vida del ejecutable, es decir que tiene un tiempo de vida (lifetime) del tipo "'static". Como este "str" vive durante toda la ejecución del programa entonces podemos regresar una referencia "&'static" sin problema de que deje de vivir, ya que vivirá durante todo el tiempo de ejecución del programa.

Finalmente tenemos la línea en el main que es "rocket::ignite().mount("/", routes![index]).launch();" donde estamos llamando varios métodos de la librería de Rocket. Por el uso del signo de exclamación "!" puedo ver que "routes!" se trata de una macro. Y le estamos pasando nuestra función "index".

Siguiendo la guía todavía no se nos explica a detalle que es todo esto, pero podemos correrlo ya usando el comando "cargo run".

Como esta es la primera vez que se ejecuta se tienen que descargar y compilar Rocket y sus dependencias de la página web cartes.io:

El resultado de ejecutar "cargo run" por primera vez. Se descargan las dependencias y después de descargar se compilan.
Una vez que esperamos a que se descarguen y se compilen las dependencias podremos ejecutar "cargo run" y ver Rocket corriendo:
El resultado de correr nuestro programa usando "cargo run" después de que ya tenemos las dependencias descargadas y compiladas
Mientras esta esto corriendo en la consola podemos abrir un navegador de internet como Google Chrome y ir a la página "http://localhost:8000" para ver el resultado de este pequeño ejemplo:

Google Chrome con la página http://localhost:8000
Como podemos ver la página nos muestra el texto que escribimos en la función "index". Con esto tenemos una pequeña aplicación web en Rust usando Rocket con lo que fueron pasos muy simples:

  1. "rustup default nightly"
  2. "cargo new rocket-test" y "cd rocket-test" 
  3. Agregar "rocket = "0.4.5"" como dependencia en Cargo.toml
  4. Las 12 líneas de código en main.rs que vienen en el ejemplo inicial de Rocket
  5. "cargo run"
  6. Esperar que se descarguen y compilen las dependencias
No olvidemos que podemos usar "cargo run --release" en lugar de "cargo run" para compilar con optimizaciones y menos información de debug.


Fuentes: