♻️ (pagetop): Migra de actix-web a Axum
Sustituye el módulo `service` por `web` y adapta toda la API al modelo de Axum: router inmutable, extractores via `FromRequestParts` y servicios Tower para archivos estáticos. - `HttpRequest` pasa a ser un tipo propio, mínimo y clonable. - `configure_services` pasa a `configure_routes`. - `EmbeddedFilesService` pasa a `ServeEmbedded`. - Elimina `session_lifetime` de `Server` (va a `pagetop-auth`). - Actualiza tests y ejemplos a la nueva API.
This commit is contained in:
parent
026448e511
commit
9c58d5e1d6
19 changed files with 612 additions and 390 deletions
147
src/app.rs
147
src/app.rs
|
|
@ -6,24 +6,20 @@ use crate::core::{extension, extension::ExtensionRef};
|
|||
use crate::html::Markup;
|
||||
use crate::locale::Locale;
|
||||
use crate::response::page::{ErrorPage, ResultPage};
|
||||
use crate::service::HttpRequest;
|
||||
use crate::{global, service, trace, PAGETOP_VERSION};
|
||||
|
||||
use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle};
|
||||
use actix_session::storage::CookieSessionStore;
|
||||
use actix_session::SessionMiddleware;
|
||||
|
||||
use substring::Substring;
|
||||
use crate::web::{HttpRequest, Router};
|
||||
use crate::{PAGETOP_VERSION, global, trace};
|
||||
|
||||
use std::future::Future;
|
||||
use std::io::Error;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// Punto de entrada de una aplicación PageTop.
|
||||
///
|
||||
/// No almacena datos, **encapsula** el inicio completo de configuración y puesta en marcha. Para
|
||||
/// instanciarla se puede usar [`new()`](Application::new) o [`prepare()`](Application::prepare).
|
||||
/// Después sólo hay que llamar a [`run()`](Application::run) para ejecutar la aplicación (o a
|
||||
/// [`test()`](Application::test) si se está preparando un entorno de pruebas).
|
||||
/// No almacena datos, **encapsula** el inicio completo de la configuración y puesta en marcha de la
|
||||
/// aplicación. Para instanciarla se puede usar [`new()`](Application::new) o
|
||||
/// [`prepare()`](Application::prepare). Después sólo hay que llamar a [`run()`](Application::run)
|
||||
/// para ejecutar la aplicación (o a [`test()`](Application::test) si se está preparando un entorno
|
||||
/// de pruebas).
|
||||
pub struct Application;
|
||||
|
||||
impl Default for Application {
|
||||
|
|
@ -33,24 +29,24 @@ impl Default for Application {
|
|||
}
|
||||
|
||||
impl Application {
|
||||
/// Crea una instancia de la aplicación.
|
||||
/// Crea una instancia mínima de la aplicación, sin extensión raíz.
|
||||
///
|
||||
/// Útil para verificar que el servidor arranca correctamente. Para una aplicación real, usa
|
||||
/// [`prepare()`](Application::prepare) con una extensión raíz.
|
||||
pub fn new() -> Self {
|
||||
Self::internal_prepare(None)
|
||||
}
|
||||
|
||||
/// Prepara una instancia de la aplicación a partir de una extensión raíz.
|
||||
///
|
||||
/// Esa extensión suele declarar:
|
||||
///
|
||||
/// - Sus propias dependencias (que se habilitarán automáticamente).
|
||||
/// - Una lista de extensiones que deben deshabilitarse si estuvieran activadas.
|
||||
///
|
||||
/// Esto simplifica el arranque en escenarios complejos.
|
||||
/// Las dependencias se habilitan en orden: primero las que no dependen de ninguna otra, luego
|
||||
/// las que dependen de extensiones ya habilitadas, y así sucesivamente hasta dejar habilitada
|
||||
/// la extensión raíz.
|
||||
pub fn prepare(root_extension: ExtensionRef) -> Self {
|
||||
Self::internal_prepare(Some(root_extension))
|
||||
}
|
||||
|
||||
/// Método interno para preparar la aplicación, opcionalmente con una extensión.
|
||||
// Secuencia de arranque común a new() y prepare().
|
||||
fn internal_prepare(root_extension: Option<ExtensionRef>) -> Self {
|
||||
// Al arrancar muestra una cabecera para la aplicación.
|
||||
Self::show_banner();
|
||||
|
|
@ -73,10 +69,10 @@ impl Application {
|
|||
Self
|
||||
}
|
||||
|
||||
/// Muestra una cabecera para la aplicación basada en la configuración.
|
||||
// Muestra la cabecera de arranque si está habilitada en la configuración.
|
||||
fn show_banner() {
|
||||
use colored::Colorize;
|
||||
use terminal_size::{terminal_size, Width};
|
||||
use terminal_size::{Width, terminal_size};
|
||||
|
||||
if global::SETTINGS.app.startup_banner != global::StartupBanner::Off {
|
||||
// Nombre de la aplicación, ajustado al ancho del terminal si es necesario.
|
||||
|
|
@ -85,8 +81,8 @@ impl Application {
|
|||
if let Some((Width(term_width), _)) = terminal_size() {
|
||||
if term_width >= 80 {
|
||||
let maxlen: usize = ((term_width / 10) - 2).into();
|
||||
let mut app = app_name.substring(0, maxlen).to_string();
|
||||
if app_name.len() > maxlen {
|
||||
let mut app: String = app_name.chars().take(maxlen).collect();
|
||||
if app_name.chars().count() > maxlen {
|
||||
app = format!("{app}...");
|
||||
}
|
||||
if let Some(ff) = figfont::FIGFONT.convert(&app) {
|
||||
|
|
@ -103,7 +99,7 @@ impl Application {
|
|||
// Descripción de la aplicación.
|
||||
if !global::SETTINGS.app.description.is_empty() {
|
||||
println!("{}", global::SETTINGS.app.description.cyan());
|
||||
};
|
||||
}
|
||||
|
||||
// Versión de PageTop.
|
||||
println!(
|
||||
|
|
@ -114,72 +110,55 @@ impl Application {
|
|||
}
|
||||
}
|
||||
|
||||
// Construye el router con las rutas de todas las extensiones habilitadas.
|
||||
fn build_router() -> Router {
|
||||
let router = extension::all::configure_routes(Router::new());
|
||||
router.fallback(route_not_found)
|
||||
}
|
||||
|
||||
/// Arranca el servidor web de la aplicación.
|
||||
///
|
||||
/// Devuelve [`std::io::Error`] si el *socket* no puede enlazarse (por puerto en uso, permisos,
|
||||
/// etc.).
|
||||
pub fn run(self) -> Result<service::Server, Error> {
|
||||
// Genera clave secreta para firmar y verificar cookies.
|
||||
let secret_key = service::cookie::Key::generate();
|
||||
|
||||
// Prepara el servidor web.
|
||||
Ok(service::HttpServer::new(move || {
|
||||
Self::service_app()
|
||||
.wrap(tracing_actix_web::TracingLogger::default())
|
||||
.wrap(
|
||||
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
|
||||
.session_lifecycle(match global::SETTINGS.server.session_lifetime {
|
||||
0 => SessionLifecycle::BrowserSession(BrowserSession::default()),
|
||||
_ => SessionLifecycle::PersistentSession(
|
||||
PersistentSession::default().session_ttl(
|
||||
service::cookie::time::Duration::seconds(
|
||||
global::SETTINGS.server.session_lifetime,
|
||||
),
|
||||
),
|
||||
),
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
})
|
||||
.bind(format!(
|
||||
/// Enlaza el puerto del servidor web de forma síncrona (puede fallar con [`std::io::Error`] si
|
||||
/// el puerto ya está en uso o el proceso carece de permisos) y devuelve un [`Future`] que
|
||||
/// ejecuta el bucle de atención de peticiones. El patrón habitual es:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// struct MyApp;
|
||||
///
|
||||
/// impl Extension for MyApp {}
|
||||
///
|
||||
/// #[pagetop::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// Application::prepare(&MyApp).run()?.await
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run(self) -> Result<impl Future<Output = Result<(), Error>>, Error> {
|
||||
let addr = format!(
|
||||
"{}:{}",
|
||||
&global::SETTINGS.server.bind_address,
|
||||
&global::SETTINGS.server.bind_port
|
||||
))?
|
||||
.run())
|
||||
global::SETTINGS.server.bind_address,
|
||||
global::SETTINGS.server.bind_port
|
||||
);
|
||||
|
||||
// Enlaza el puerto de forma síncrona para detectar errores antes del *await*.
|
||||
let std_listener = std::net::TcpListener::bind(&addr)?;
|
||||
std_listener.set_nonblocking(true)?;
|
||||
|
||||
let router = Self::build_router();
|
||||
|
||||
Ok(async move {
|
||||
let listener = tokio::net::TcpListener::from_std(std_listener)?;
|
||||
axum::serve(listener, router).await
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepara el servidor web de la aplicación para pruebas.
|
||||
pub fn test(
|
||||
self,
|
||||
) -> service::App<
|
||||
impl service::Factory<
|
||||
service::Request,
|
||||
Config = (),
|
||||
Response = service::Response<service::BoxBody>,
|
||||
Error = service::Error,
|
||||
InitError = (),
|
||||
>,
|
||||
> {
|
||||
Self::service_app()
|
||||
}
|
||||
|
||||
/// Configura el servicio web de la aplicación.
|
||||
fn service_app() -> service::App<
|
||||
impl service::Factory<
|
||||
service::Request,
|
||||
Config = (),
|
||||
Response = service::Response<service::BoxBody>,
|
||||
Error = service::Error,
|
||||
InitError = (),
|
||||
>,
|
||||
> {
|
||||
service::App::new()
|
||||
.configure(extension::all::configure_services)
|
||||
.default_service(service::web::route().to(service_not_found))
|
||||
/// Devuelve el servidor web configurado para usarlo en pruebas de integración.
|
||||
pub fn test(self) -> Router {
|
||||
Self::build_router()
|
||||
}
|
||||
}
|
||||
|
||||
async fn service_not_found(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
||||
async fn route_not_found(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
||||
Err(ErrorPage::NotFound(request))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue