El autor del material, cuya traducción publicamos hoy, dice que su último experimento en el campo de la arquitectura de proyectos de software fue la creación de una aplicación web que funciona utilizando solo el lenguaje Rust y con el mínimo uso posible de código de plantilla. En este artículo, quiere compartir con los lectores lo que descubrió al desarrollar una aplicación y responder a la
pregunta de si Rust está listo para usarlo en varias áreas del desarrollo web.

Resumen del proyecto
El código para el proyecto que se discutirá aquí se puede encontrar en
GitHub . Las partes cliente y servidor de la aplicación se encuentran en el mismo repositorio, esto se hace para simplificar el mantenimiento del proyecto. Cabe señalar que Cargo necesitará compilar las aplicaciones frontend y backend con diferentes dependencias.
Aquí puede ver una aplicación que funciona.
Nuestro proyecto es una simple demostración del mecanismo de autenticación. Le permite iniciar sesión con el nombre de usuario y la contraseña seleccionados (deben ser los mismos).
Si el nombre de usuario y la contraseña son diferentes, la autenticación fallará. Después de una autenticación exitosa, el token
JWT (JSON Web Token) se almacena en el lado del cliente y del servidor. Por lo general, no es necesario almacenar el token en el servidor en tales aplicaciones, pero lo hice solo con fines de demostración. Esto, por ejemplo, se puede utilizar para averiguar cuántos usuarios están conectados. La aplicación completa se puede configurar usando un solo archivo
Config.toml , por ejemplo, especificando credenciales para acceder a la base de datos, o la dirección y el número de puerto del servidor. Así es como se ve el código estándar para este archivo para nuestra aplicación.
[server] ip = "127.0.0.1" port = "30080" tls = false [log] actix_web = "debug" webapp = "trace" [postgres] host = "127.0.0.1" username = "username" password = "password" database = "database"
Desarrollo de clientes de aplicaciones
Para desarrollar el lado del cliente de la aplicación, decidí usar
tejo . Este es un marco moderno Rust inspirado en Elm, Angular y React. Está diseñado para crear porciones de clientes de aplicaciones web multiproceso utilizando
WebAssembly (Wasm). Actualmente, este proyecto está en desarrollo activo, mientras que no hay muchas versiones estables.
El marco de trabajo de
yew
basa en la herramienta
web de carga , que está diseñada para compilar código en Wasm.
La herramienta
web de carga es una dependencia directa del
yew
que simplifica la compilación cruzada del código Rust en Wasm. Aquí hay tres objetivos principales para compilar Wasm que están disponibles a través de esta herramienta:
asmjs-unknown-emscripten
: utiliza asm.js a través de Emscripten.wasm32-unknown-emscripten
: utiliza WebAssembly a través de Emscriptenwasm32-unknown-unknown
: utiliza WebAssembly con el backend nativo de Rust para WebAssembly
Montaje webDecidí usar la última opción, que requiere el uso del ensamblaje "nocturno" del compilador Rust, pero en el mejor de los casos demuestra las capacidades nativas de Wasm de Rust.
Si hablamos de WebAssembly, hablar de Rust hoy es el tema más candente. Se está realizando una gran cantidad de trabajo en la compilación cruzada de Rust in Wasm y su integración en el ecosistema Node.js (utilizando paquetes npm). Decidí implementar el proyecto sin ninguna dependencia de JavaScript.
Al iniciar el frontend de una aplicación web (en mi proyecto esto se hace con el comando
make frontend
),
cargo-web
compila de forma cruzada la aplicación en Wasm y la empaqueta, agregando algunos materiales estáticos.
cargo-web
lanza un servidor web local, que le permite interactuar con la aplicación con fines de desarrollo. Esto es lo que sucede en la consola cuando ejecuta el comando anterior:
> make frontend Compiling webapp v0.3.0 (file:///home/sascha/webapp.rs) Finished release [optimized] target(s) in 11.86s Garbage collecting "app.wasm"... Processing "app.wasm"... Finished processing of "app.wasm"! If you need to serve any extra files put them in the 'static' directory in the root of your crate; they will be served alongside your application. You can also put a 'static' directory in your 'src' directory. Your application is being served at '/app.js'. It will be automatically rebuilt if you make any changes in your code. You can access the web server at `http://0.0.0.0:8000`.
El marco de
yew
tiene algunas características muy interesantes. Entre ellos se encuentra el soporte para arquitecturas de componentes reutilizables. Esta característica ha simplificado el desglose de mi aplicación en tres componentes principales:
RootComponent . Este componente está montado directamente en la etiqueta
<body>
del sitio web. Él decide qué componente secundario se debe cargar a continuación. Si, en la primera entrada de la página, se encuentra un token JWT, intenta actualizar este token poniéndose en contacto con el servidor de la aplicación. Si esto falla, se realiza la transición al componente
LoginComponent
.
LoginComponent . Este componente es un descendiente del componente
RootComponent
; contiene un formulario con campos para ingresar credenciales. Además, interactúa con el back-end de la aplicación para organizar un esquema de autenticación simple basado en la verificación del nombre de usuario y la contraseña y, en caso de autenticación exitosa, guarda el JWT en una cookie. Además, si el usuario pudo autenticarse, realiza la transición al componente
ContentComponent
.
Apariencia del Componente LoginComponentContentComponent Este componente es otro descendiente del componente
RootComponent
. Contiene lo que se muestra en la página principal de la aplicación (en este momento es solo un título y un botón para salir del sistema). El acceso al mismo se puede obtener a través de
RootComponent
(si la aplicación, al inicio, logró encontrar un token de sesión válido), o a través de
LoginComponent
(en caso de autenticación exitosa). Este componente intercambia datos con el back-end cuando el usuario hace clic en el botón de cerrar sesión.
Componente de componente de contenidoRouterComponent Este componente almacena todas las rutas posibles entre componentes que contienen contenido. Además, contiene el estado inicial de la
loading
de la aplicación y el
error
. Está directamente conectado al
RootComponent
.
Uno de los siguientes conceptos clave de
yew
que discutiremos ahora son los servicios. Le permiten reutilizar la misma lógica en diferentes componentes. Digamos que estos pueden ser interfaces o herramientas de registro para admitir el uso de
cookies . Los servicios no almacenan un estado global; se crean cuando se inicializan los componentes. Además de los servicios,
yew
apoya el concepto de agentes. Se pueden usar para organizar el intercambio de datos entre varios componentes, para mantener el estado general de la aplicación, como el necesario para el agente responsable del enrutamiento. Para organizar el sistema de enrutamiento de nuestra aplicación, que cubre todos los componentes,
nuestro propio agente y servicio de enrutamiento se implementaron aquí. No hay un enrutador estándar en
yew
, pero en el repositorio de framework puede encontrar
un ejemplo de implementación de enrutador que admite una variedad de operaciones de URL.
Me complace observar que
yew
usa
la API de Web Workers para ejecutar agentes en varios subprocesos y usa un programador local adjunto al hilo para resolver tareas paralelas. Esto hace posible desarrollar aplicaciones de navegador con un alto grado de subprocesamiento múltiple en Rust.
Cada componente implementa su propio
rasgo Renderable , que nos permite incluir código HTML directamente en el código fuente de Rust usando la
macro html! {} .
Esta es una gran característica y, por supuesto, el compilador controla su uso adecuado. Aquí está el
Renderable
implementación
Renderable
en el componente
LoginComponent
.
impl Renderable<LoginComponent> for LoginComponent { fn view(&self) -> Html<Self> { html! { <div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center",> <form onsubmit="return false",> <fieldset class="uk-fieldset",> <legend class="uk-legend",>{"Authentication"}</legend> <div class="uk-margin",> <input class="uk-input", placeholder="Username", value=&self.username, oninput=|e| Message::UpdateUsername(e.value), /> </div> <div class="uk-margin",> <input class="uk-input", type="password", placeholder="Password", value=&self.password, oninput=|e| Message::UpdatePassword(e.value), /> </div> <button class="uk-button uk-button-default", type="submit", disabled=self.button_disabled, onclick=|_| Message::LoginRequest,>{"Login"}</button> <span class="uk-margin-small-left uk-text-warning uk-text-right",> {&self.error} </span> </fieldset> </form> </div> } } }
La conexión entre el frontend y el backend se basa en las conexiones
WebSocket utilizadas por cada cliente. La fortaleza de la tecnología WebSocket es el hecho de que es adecuada para transmitir mensajes binarios, así como el hecho de que el servidor, si es necesario, puede enviar notificaciones push a los clientes.
yew
tiene un servicio estándar de WebSocket, pero decidí crear su
propia versión con fines de demostración, principalmente debido a la inicialización "perezosa" de las conexiones directamente dentro del servicio. Si el servicio WebSocket se creara durante la inicialización del componente, tendría que monitorear muchas conexiones.
Protocolo Cap'n ProtoDecidí usar el protocolo
Cap'n Proto (en lugar de algo como
JSON ,
MessagePack o
CBOR ) como una capa para transmitir datos de la aplicación por razones de velocidad y compacidad. Vale la pena señalar que no
utilicé la interfaz de protocolo
RPC que tiene Cap'n Proto, ya que su implementación de Rust no se compila para WebAssembly (debido a las
dependencias de toxio-rs Unix). Esto complica un poco la selección de solicitudes y respuestas de los tipos correctos, pero este problema se puede resolver utilizando una
API bien estructurada . Aquí está la declaración del protocolo Cap'n Proto para la aplicación.
@0x998efb67a0d7453f; struct Request { union { login :union { credentials :group { username @0 :Text; password @1 :Text; } token @2 :Text; } logout @3 :Text; # The session token } } struct Response { union { login :union { token @0 :Text; error @1 :Text; } logout: union { success @2 :Void; error @3 :Text; } } }
Puede ver que aquí tenemos dos versiones diferentes de la solicitud de inicio de sesión.
Uno es para
LoginComponent
(aquí, para obtener un token, se usa un nombre y una contraseña), y otro es para
RootComponent
(se usa para actualizar un token existente). Todo lo que se necesita para que el protocolo funcione está empaquetado en el servicio de
protocolo , gracias al cual es conveniente reutilizar las capacidades correspondientes en varias partes de la interfaz.
UIkit: un marco frontal compacto y modular para desarrollar interfaces web rápidas y potentesLa interfaz de usuario de la parte cliente de la aplicación se basa en el marco
UIkit , su versión 3.0.0 se lanzará en un futuro próximo. Un script
build.rs especialmente preparado descarga automáticamente todas las dependencias UIkit necesarias y compila la hoja de estilo resultante. Esto significa que puede agregar sus propios estilos a un solo archivo
style.scss , que puede aplicarse en toda la aplicación. Es muy conveniente.
▍ Interfaz de prueba
Creo que hay algunos problemas al probar nuestra solución. El hecho es que es muy simple probar servicios individuales, pero
yew
no proporciona al desarrollador una forma conveniente de probar componentes y agentes. Ahora, en el marco de Rust puro, la integración y las pruebas de extremo a extremo de la interfaz no están disponibles. Aquí podría usar proyectos como
Cypress o
Protractor , pero con este enfoque, tendría que incluir una gran cantidad de código JavaScript de plantilla / TypeScript en el proyecto, por lo que decidí abandonar la implementación de tales pruebas.
Por cierto, aquí hay una idea para un nuevo proyecto: un marco para pruebas de extremo a extremo escrito en Rust.
Desarrollo del lado del servidor de aplicaciones
Para implementar el lado del servidor de la aplicación, elegí el marco
actix-web . Este es un marco de
modelo de actor compacto, práctico y muy rápido basado en Rust. Es compatible con todas las tecnologías necesarias, como WebSockets, TLS y
HTTP / 2.0 . Este marco admite varios manejadores y recursos, pero en nuestra aplicación solo se usaron un par de rutas principales:
/ws
es el recurso principal para las comunicaciones WebSocket./
- el controlador principal que da acceso a la aplicación frontal estática.
De forma predeterminada,
actix-web
inicia los flujos de trabajo en la cantidad correspondiente al número de núcleos de procesador disponibles en la computadora local. Esto significa que si la aplicación tiene un estado, deberá compartirse de manera segura entre todos los subprocesos, pero gracias a las confiables plantillas de cómputo paralelo Rust, esto no es un problema. Sea como fuere, el backend debería ser un sistema sin estado, ya que muchas copias de este se pueden implementar en paralelo en un entorno de nube (como
Kubernetes ). Como resultado, los datos que forman el estado de la aplicación deben estar separados del backend. Por ejemplo, pueden estar dentro de una instancia separada de un contenedor
Docker .
PostgreSQL DBMS y Proyecto DieselComo el almacén de datos principal, decidí usar el DBMS
PostgreSQL . Por qué Esta elección determinó la existencia de un maravilloso proyecto
Diesel que ya es compatible con PostgreSQL y ofrece un sistema ORM seguro y extensible y una herramienta de creación de consultas para él. Todo esto corresponde perfectamente a las necesidades de nuestro proyecto, ya que
actix-web
ya es compatible con Diesel. Como resultado, aquí, para realizar operaciones CRUD con información sobre sesiones en la base de datos, puede usar un lenguaje especial que tenga en cuenta los detalles de Rust. Aquí hay un ejemplo de controlador
UpdateSession
para
actix-web
basado en Diesel.rs.
impl Handler<UpdateSession> for DatabaseExecutor { type Result = Result<Session, Error>; fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result {
El proyecto
r2d2 se utiliza para establecer una conexión entre
actix-web
y Diesel. Esto significa que tenemos (además de la aplicación con sus flujos de trabajo) el estado compartido de la aplicación, que admite muchas conexiones de bases de datos como un único grupo de conexiones. Esto simplifica enormemente la escala seria del backend, haciendo que esta solución sea flexible.
Aquí puede encontrar el código responsable de crear la instancia del servidor.
▍ Prueba de backend
Las
pruebas de integración de back-end en nuestro proyecto se realizan iniciando una instancia de servidor de prueba y conectándose a una base de datos que ya se está ejecutando. Luego puede usar el cliente estándar de WebSocket (usé
tungstenite ) para enviar datos generados usando el protocolo Cap'n Proto al servidor y comparar los resultados con los esperados. Esta configuración de prueba ha demostrado ser excelente. No
utilicé servidores de prueba especiales
actix-web , ya que no se requiere mucho más trabajo para configurar y ejecutar un servidor real. Las pruebas de la unidad de back-end resultaron, como se esperaba, una tarea bastante simple; realizar tales pruebas no causa ningún problema.
Despliegue del proyecto
La aplicación es muy fácil de implementar utilizando la imagen Docker.
DockerCon el
make deploy
puede crear una imagen llamada
webapp
que contenga ejecutables backend estáticamente vinculados, el archivo
Config.toml
actual, certificados TLS y contenido de interfaz estático. El ensamblaje de ejecutables totalmente vinculados estáticamente en Rust se implementa utilizando una versión modificada de la imagen Docker
rust-musl-builder . Una aplicación web terminada se puede probar con el comando
make run
, que inicia un contenedor habilitado para la red. El contenedor PostgreSQL debe ejecutarse en paralelo con el contenedor de la aplicación para garantizar que el sistema funcione. En general, el proceso de implementación de nuestro sistema es bastante simple, además, gracias a las tecnologías utilizadas aquí, podemos hablar de su suficiente flexibilidad, simplificando su posible adaptación a las necesidades de una aplicación en desarrollo.
Tecnologías utilizadas en el desarrollo de proyectos.
Aquí está el diagrama de dependencia de la aplicación.
Tecnologías utilizadas para desarrollar una aplicación web en RustEl único componente que utilizan el frontend y el backend es la versión Rust de Cap'n Proto, que requiere el compilador Cap'n Proto instalado localmente para crear.
Los resultados ¿Está listo el óxido para la producción web?
Esta es una gran pregunta. Esto es lo que puedo responder. Desde el punto de vista de los servidores, me inclino a responder "sí", ya que el ecosistema Rust, además de
actix-web
, tiene una
pila HTTP muy madura y muchos
marcos diferentes para el rápido desarrollo de servicios y API de servidores.
Si hablamos del front-end, entonces, gracias a la atención general a WebAssembly, se está trabajando mucho ahora. Sin embargo, los proyectos creados en esta área deben alcanzar la misma madurez que los proyectos de servidor. Esto es especialmente cierto para la estabilidad de API y las capacidades de prueba. Así que ahora digo "no" al uso de Rust en el extremo frontal, pero no puedo evitar notar que se está moviendo en la dirección correcta.
Estimados lectores! ¿Usas Rust en el desarrollo web?