下载非内核
This commit is contained in:
@@ -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 }))),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user