use std::sync::Arc; use anyhow::{Context, Result}; use axum::extract::Request; use axum::http::{Uri, header}; use axum::response::IntoResponse; use axum::routing::get; use axum::{Extension, Router, ServiceExt, middleware}; use reqwest::StatusCode; use rust_embed_for_web::{EmbedableFile, RustEmbed}; use sea_orm::DatabaseConnection; use crate::api::auth; use crate::api::handler::api_router; use crate::bilibili::BiliClient; use crate::config::VersionedConfig; #[derive(RustEmbed)] #[preserve_source = false] #[gzip = false] #[folder = "../../web/build"] struct Asset; pub async fn http_server(database_connection: Arc, bili_client: Arc) -> Result<()> { let app = Router::new() .merge(api_router()) .fallback_service(get(frontend_files)) .layer(Extension(database_connection)) .layer(Extension(bili_client)) .layer(middleware::from_fn(auth::auth)); let config = VersionedConfig::get().load_full(); let listener = tokio::net::TcpListener::bind(&config.bind_address) .await .context("bind address failed")?; info!("开始运行管理页: http://{}", config.bind_address); Ok(axum::serve(listener, ServiceExt::::into_make_service(app)).await?) } async fn frontend_files(uri: Uri) -> impl IntoResponse { let mut path = uri.path().trim_start_matches('/'); if path.is_empty() || Asset::get(path).is_none() { path = "index.html"; } let Some(content) = Asset::get(path) else { return (StatusCode::NOT_FOUND, "404 Not Found").into_response(); }; let mime_type = content.mime_type(); let content_type = mime_type.as_deref().unwrap_or("application/octet-stream"); if cfg!(debug_assertions) { ( [(header::CONTENT_TYPE, content_type)], // safety: `RustEmbed` returns uncompressed files directly from the filesystem in debug mode content.data().unwrap(), ) .into_response() } else { ( [(header::CONTENT_TYPE, content_type), (header::CONTENT_ENCODING, "br")], // safety: `RustEmbed` will always generate br-compressed files if the feature is enabled content.data_br().unwrap(), ) .into_response() } }