import { resolve, join, sep, extname } from "node:path"; import { promises as fsp } from "node:fs"; import { decodePath, withLeadingSlash, withoutTrailingSlash, parseURL, } from "ufo"; import type { H3Event } from "h3"; import mime from "mime"; const METHODS = new Set(["HEAD", "GET"]); const SAFE_BASE_DIR = resolve("public"); // 缓存配置 const CACHE_CONTROL = "public, max-age=31536000, immutable"; const NOT_MODIFIED = 304; const FORBIDDEN = 403; const NOT_FOUND = 404; const SERVER_ERROR = 500; export default eventHandler(async (event: H3Event) => { if (!event.path.startsWith("/public")) return; const { req, res } = event.node; const method = req.method; if (method && !METHODS.has(method)) return; try { // 安全解析路径 const url = event.path.replace(/^\/public/, ""); const pathname = decodePath( withLeadingSlash(withoutTrailingSlash(parseURL(url).pathname)) ); const targetPath = join(SAFE_BASE_DIR, pathname); const resolvedPath = resolve(targetPath); // 安全校验 if (!resolvedPath.startsWith(SAFE_BASE_DIR + sep)) { res.statusCode = FORBIDDEN; return "Forbidden"; } const stat = await fsp.stat(resolvedPath); if (!stat.isFile()) { res.statusCode = NOT_FOUND; return "Not Found"; } // ====================== 缓存逻辑 ====================== const mtime = stat.mtime.toUTCString(); const etag = `"${stat.mtime.getTime().toString(16)}-${stat.size.toString(16)}"`; const contentType = mime.getType(resolvedPath) || "application/octet-stream"; // 设置缓存头 res.setHeader("Cache-Control", CACHE_CONTROL); res.setHeader("ETag", etag); res.setHeader("Last-Modified", mtime); res.setHeader("Content-Type", contentType); res.setHeader("Content-Length", stat.size); // 禁用 keep-alive res.setHeader("Connection", "close"); // 304 协商缓存 const ifNoneMatch = req.headers["if-none-match"]; const ifModifiedSince = req.headers["if-modified-since"]; if (ifNoneMatch === etag || (ifModifiedSince && ifModifiedSince === mtime)) { res.statusCode = NOT_MODIFIED; return ""; } // ====================================================== if (method === "HEAD") return ""; return fsp.readFile(resolvedPath); } catch (err: any) { if (err.code === "ENOENT") { res.statusCode = NOT_FOUND; return "Not Found"; } res.statusCode = SERVER_ERROR; return "Server Error"; } });