Router
¿Qué es el Enrutamiento?
Router define qué middleware y Handler procesarán una solicitud HTTP. Esta es la funcionalidad más fundamental y central en Salvo.
Internamente, un Router está esencialmente compuesto por una serie de filtros. Cuando llega una solicitud, el router prueba a sí mismo y a sus descendientes en orden, de arriba a abajo, para ver si pueden coincidir con la solicitud. Si una coincidencia es exitosa, el middleware en toda la cadena formada por el router y sus routers descendientes se ejecuta secuencialmente. Si durante el procesamiento, el estado de la Response se establece en un error (4XX, 5XX) o una redirección (3XX), el middleware y Handler posteriores se omitirán. También puedes llamar manualmente a ctrl.skip_rest() para omitir el middleware y Handler restantes.
Durante el proceso de coincidencia, existe un objeto de información de ruta URL, que puede considerarse como un objeto que debe ser completamente consumido por los filtros durante la coincidencia. Si todos los filtros en un Router determinado coinciden con éxito, y esta información de ruta URL ha sido completamente consumida, se considera una "coincidencia exitosa".
Por ejemplo:
Es en realidad equivalente a:
Si se accede a GET /articles/, se considera una coincidencia exitosa y se ejecuta list_articles. Sin embargo, si se accede a GET /articles/123, la coincidencia de ruta falla y devuelve un error 404 porque Router::with_path("articles") solo consume la parte /articles de la información de ruta URL, dejando la parte /123 sin consumir, por lo tanto, la coincidencia se considera fallida. Para lograr una coincidencia exitosa, la ruta se puede cambiar a:
Aquí, {**} coincide con cualquier ruta restante, por lo que puede coincidir con GET /articles/123 y ejecutar list_articles.
Definición Plana
Podemos definir rutas en un estilo plano:
Definición en Forma de Árbol
También podemos definir rutas en una estructura de árbol, que es el enfoque recomendado:
Esta forma de definición hace que las definiciones de Router sean jerárquicas, claras y simples para proyectos complejos.
Muchos métodos en Router devuelven Self después de ser llamados, facilitando la escritura de código encadenado. A veces, necesitas decidir cómo enrutar basándote en ciertas condiciones. El sistema de enrutamiento también proporciona la función then, que es fácil de usar:
Este ejemplo significa que las rutas para crear, editar y eliminar artículos solo se agregan cuando el servidor está en admin_mode.
Recuperando Parámetros de las Rutas
En el código anterior, {id} define un parámetro. Podemos recuperar su valor a través de la instancia Request:
{id} coincide con un segmento en la ruta. Normalmente, el id de un artículo es solo un número. En este caso, podemos usar una expresión regular para restringir la regla de coincidencia para id, como r"{id|\d+}".
Para tipos numéricos, hay un método aún más simple usando <id:num>. Las notaciones específicas son:
{id:num}coincide con cualquier número de caracteres de dígitos.{id:num[10]}coincide exactamente con un número específico de caracteres de dígitos; aquí, 10 significa que coincide exactamente con 10 dígitos.{id:num(..10)}coincide con 1 a 9 caracteres de dígitos.{id:num(3..10)}coincide con 3 a 9 caracteres de dígitos.{id:num(..=10)}coincide con 1 a 10 caracteres de dígitos.{id:num(3..=10)}coincide con 3 a 10 caracteres de dígitos.{id:num(10..)}coincide con al menos 10 caracteres de dígitos.
También puedes coincidir con todos los segmentos de ruta restantes usando {**}, {*+}, o {*?}. Para una mejor legibilidad del código, puedes agregar nombres apropiados para hacer la semántica de la ruta más clara, por ejemplo, {**file_path}.
{**}: Representa una coincidencia comodín donde la parte coincidente puede ser una cadena vacía. Por ejemplo, la ruta/files/{**rest_path}coincide con/files,/files/abc.txt,/files/dir/abc.txt.{*+}: Representa una coincidencia comodín donde la parte coincidente debe existir y no puede ser una cadena vacía. Por ejemplo, la ruta/files/{*+rest_path}NO coincide con/filespero coincide con/files/abc.txt,/files/dir/abc.txt.{*?}: Representa una coincidencia comodín donde la parte coincidente puede ser una cadena vacía pero solo puede contener un segmento de ruta. Por ejemplo, la ruta/files/{*?rest_path}NO coincide con/files/dir/abc.txtpero coincide con/files,/files/abc.txt.
Múltiples expresiones pueden combinarse para coincidir con el mismo segmento de ruta, por ejemplo, /articles/article_{id:num}/, /images/{name}.{ext}.
Agregando Middleware
El middleware se puede agregar a través de la función hoop en un router:
En este ejemplo, el router raíz usa check_authed para verificar si el usuario actual ha iniciado sesión. Todos los routers descendientes se ven afectados por este middleware.
Si los usuarios solo están navegando por la información de writer y artículos, podríamos preferir que puedan navegar sin iniciar sesión. Podemos definir las rutas así:
Aunque dos routers tienen la misma definición de ruta path("articles"), aún pueden agregarse al mismo router padre.
Filtros
Router internamente usa filtros para determinar si una ruta coincide. Los filtros admiten operaciones lógicas básicas usando or o and. Un router puede contener múltiples filtros; cuando todos los filtros coinciden con éxito, la ruta coincide con éxito.
La información de ruta del sitio web es una estructura de árbol, pero esta estructura de árbol no es equivalente a la estructura de árbol que organiza los routers. Una ruta de sitio web puede corresponder a múltiples nodos de router. Por ejemplo, algunos contenidos bajo la ruta articles/ pueden requerir inicio de sesión para ver, mientras que otros no. Podemos organizar las subrutas que requieren verificación de inicio de sesión bajo un router que contenga middleware de verificación de inicio de sesión, y aquellas que no requieren verificación bajo otro router sin ella:
Los routers usan filtros para filtrar solicitudes y enviarlas al middleware y Handler correspondientes para su procesamiento.
path y method son dos de los filtros más comúnmente usados. path se usa para coincidir con información de ruta; method se usa para coincidir con el Método de solicitud, por ejemplo, GET, POST, PATCH, etc.
Podemos conectar filtros de router usando and, or:
Filtro de Ruta
Los filtros basados en rutas de solicitud son los más frecuentemente usados. Los filtros de ruta pueden definir parámetros, por ejemplo:
En un Handler, se pueden recuperar a través de la función get_param del objeto Request:
Filtro de Método
Filtra solicitudes basándose en el Method de la solicitud HTTP, por ejemplo:
Aquí, get, patch, delete son todos filtros de Método. Esto es en realidad equivalente a:
Wisp Personalizado
Para ciertas expresiones de coincidencia que ocurren con frecuencia, podemos asignar un nombre corto a través de PathFilter::register_wisp_regex o PathFilter::register_wisp_builder. Por ejemplo, el formato GUID a menudo aparece en rutas. La forma normal es escribirlo así cada vez que se necesita coincidencia:
Escribir esta expresión regular compleja cada vez es propenso a errores y hace que el código sea menos legible. Puedes hacer esto en su lugar:
Solo necesitas registrarlo una vez. Posteriormente, puedes usar directamente notación simple como {id:guid} para coincidir con GUIDs, simplificando la escritura del código.
Viniendo de un Framework Web Basado en Controladores, ¿Cómo Entender Router?
Las principales diferencias entre los frameworks web diseñados con enrutamiento (como Salvo) y los frameworks tradicionales MVC o diseñados con Controladores son:
-
Flexibilidad: El diseño de enrutamiento permite una definición más flexible de los flujos de procesamiento de solicitudes, permitiendo un control preciso sobre la lógica para cada ruta. Por ejemplo, en Salvo puedes definir directamente funciones manejadoras para rutas específicas:
En el diseño de Controlador, normalmente necesitas definir primero una clase controladora, luego definir múltiples métodos dentro de la clase para manejar diferentes solicitudes:
-
Integración de Middleware: Los frameworks de enrutamiento generalmente proporcionan una forma más concisa de integrar middleware, permitiendo que el middleware se aplique a rutas específicas. El middleware de Salvo se puede aplicar con precisión a rutas particulares:
-
Organización del Código: El diseño de enrutamiento tiende a organizar el código basándose en la funcionalidad o los endpoints de la API, en lugar de la capa Modelo-Vista-Controlador de MVC. El diseño de enrutamiento fomenta organizar el código según la funcionalidad del endpoint de la API:
-
Ligereza: El diseño de enrutamiento es típicamente más ligero, reduciendo conceptos y restricciones impuestos por el framework. Puedes introducir solo los componentes que necesitas sin seguir una estructura estricta del framework.
El diseño de enrutamiento hace