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, post}; use axum::{Extension, Router, ServiceExt, middleware}; use reqwest::StatusCode; use rust_embed::Embed; use sea_orm::DatabaseConnection; use utoipa::OpenApi; use utoipa_swagger_ui::{Config, SwaggerUi}; use crate::api::auth; use crate::api::handler::{ApiDoc, get_video, get_video_sources, get_videos, reset_video}; use crate::config::CONFIG; #[derive(Embed)] #[folder = "../../web/build"] struct Asset; pub async fn http_server(database_connection: Arc) -> Result<()> { let app = Router::new() .route("/api/video-sources", get(get_video_sources)) .route("/api/videos", get(get_videos)) .route("/api/videos/{id}", get(get_video)) .route("/api/videos/{id}/reset", post(reset_video)) .merge( SwaggerUi::new("/swagger-ui/") .url("/api-docs/openapi.json", ApiDoc::openapi()) .config( Config::default() .try_it_out_enabled(true) .persist_authorization(true) .validator_url("none"), ), ) .fallback_service(get(frontend_files)) .layer(Extension(database_connection)) .layer(middleware::from_fn(auth::auth)); let listener = tokio::net::TcpListener::bind(&CONFIG.bind_address) .await .context("bind address failed")?; info!("开始监听 http 服务: 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() { path = "index.html"; } match Asset::get(path) { Some(content) => { let mime = mime_guess::from_path(path).first_or_octet_stream(); ([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response() } None => (StatusCode::NOT_FOUND, "404 Not Found").into_response(), } }