下载非内核

This commit is contained in:
zhongluofeng
2026-07-22 18:26:26 +08:00
parent 1e31ee8da9
commit f7d3c13f35
24 changed files with 2609 additions and 2532 deletions
+139
View File
@@ -0,0 +1,139 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use axum::{
extract::{Path, State},
http::{HeaderMap, StatusCode},
response::Json,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use super::engine::DownloadEngine;
use super::task::DownloadTask;
/// 扩展 HTTP API 服务器
pub struct ExtensionServer;
#[derive(Serialize)]
struct HealthResponse {
ok: bool,
version: &'static str,
}
#[derive(Deserialize)]
struct CreateDownloadRequest {
url: String,
#[serde(default)]
filename: Option<String>,
#[serde(default)]
dir: Option<String>,
#[serde(default)]
headers: HashMap<String, String>,
}
#[derive(Serialize)]
struct CreateDownloadResponse {
id: String,
}
#[derive(Serialize)]
struct ErrorResponse {
error: String,
}
impl ExtensionServer {
/// 启动 HTTP API 服务器(绑定到 127.0.0.1:port
pub async fn start(engine: DownloadEngine, port: u16, secret: String) {
let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().expect("无效端口");
let app = Router::new()
.route("/health", get(health))
.route("/api/downloads", post(create_download).get(list_downloads))
.route("/api/downloads/:id", axum::routing::delete(remove_download))
.with_state(AppState { engine, secret });
let listener = match tokio::net::TcpListener::bind(&addr).await {
Ok(l) => l,
Err(e) => {
eprintln!("[download_engine] 扩展 HTTP 服务启动失败 ({}): {}", addr, e);
return;
}
};
eprintln!("[download_engine] 扩展 HTTP 服务已启动: http://{}", addr);
if let Err(e) = axum::serve(listener, app).await {
eprintln!("[download_engine] 扩展 HTTP 服务异常: {}", e);
}
}
}
#[derive(Clone)]
struct AppState {
engine: DownloadEngine,
secret: String,
}
/// 鉴权检查:如果配置了 secret,校验 Bearer token
fn check_auth(headers: &HeaderMap, secret: &str) -> bool {
if secret.is_empty() {
return true;
}
if let Some(auth) = headers.get("authorization") {
if let Ok(s) = auth.to_str() {
if let Some(token) = s.strip_prefix("Bearer ") {
return token == secret;
}
}
}
false
}
async fn health(State(state): State<AppState>) -> Json<HealthResponse> {
let _ = state; // 不需要鉴权
Json(HealthResponse {
ok: true,
version: "thing-download-engine/1.0",
})
}
async fn create_download(
State(state): State<AppState>,
headers: HeaderMap,
Json(req): Json<CreateDownloadRequest>,
) -> Result<Json<CreateDownloadResponse>, (StatusCode, Json<ErrorResponse>)> {
if !check_auth(&headers, &state.secret) {
return Err((StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "未授权".into() })));
}
match state.engine.add_task(req.url, req.filename, req.dir, req.headers).await {
Ok(id) => Ok(Json(CreateDownloadResponse { id })),
Err(e) => Err((StatusCode::BAD_REQUEST, Json(ErrorResponse { error: e }))),
}
}
async fn list_downloads(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<Vec<DownloadTask>>, (StatusCode, Json<ErrorResponse>)> {
if !check_auth(&headers, &state.secret) {
return Err((StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "未授权".into() })));
}
Ok(Json(state.engine.get_tasks()))
}
async fn remove_download(
State(state): State<AppState>,
headers: HeaderMap,
Path(id): Path<String>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
if !check_auth(&headers, &state.secret) {
return Err((StatusCode::UNAUTHORIZED, Json(ErrorResponse { error: "未授权".into() })));
}
match state.engine.remove_task(&id, false) {
Ok(()) => Ok(StatusCode::NO_CONTENT),
Err(e) => Err((StatusCode::NOT_FOUND, Json(ErrorResponse { error: e }))),
}
}