feat(static): 新增静态资源服务
这个提交包含在:
@@ -1,92 +1,117 @@
|
||||
import * as path from "@std/path";
|
||||
|
||||
import fastify from 'fastify';
|
||||
import fastifyView from "@fastify/view"
|
||||
import fastify from "fastify";
|
||||
import fastifyView from "@fastify/view";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import handlebars from "handlebars";
|
||||
import mime from 'mime/lite';
|
||||
import mime from "mime/lite";
|
||||
|
||||
const server = fastify();
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
const __dirname = new URL(".", import.meta.url).pathname;
|
||||
|
||||
server.register(fastifyView, { engine: { handlebars }, root: path.resolve(__dirname, 'views') });
|
||||
// 处理视图资源
|
||||
server.register(fastifyView, {
|
||||
engine: { handlebars },
|
||||
root: path.resolve(__dirname, "views"),
|
||||
});
|
||||
|
||||
// 处理静态资源 maxAge 30天
|
||||
server.register(fastifyStatic, {
|
||||
prefix: "/static/",
|
||||
root: path.resolve(__dirname, "static"),
|
||||
cacheControl: true,
|
||||
maxAge: 2592000,
|
||||
});
|
||||
|
||||
enum Headers {
|
||||
FormatHeader = 'x-format',
|
||||
CodeHeader = 'x-code',
|
||||
OriginalURI = 'x-original-uri',
|
||||
ContentType = 'content-type',
|
||||
Namespace = "x-namespace",
|
||||
IngressName = "x-ingress-name",
|
||||
ServiceName = "x-service-name",
|
||||
ServicePort = "x-service-port",
|
||||
RequestId = "x-request-id",
|
||||
FormatHeader = "x-format",
|
||||
CodeHeader = "x-code",
|
||||
OriginalURI = "x-original-uri",
|
||||
ContentType = "content-type",
|
||||
Namespace = "x-namespace",
|
||||
IngressName = "x-ingress-name",
|
||||
ServiceName = "x-service-name",
|
||||
ServicePort = "x-service-port",
|
||||
RequestId = "x-request-id",
|
||||
}
|
||||
|
||||
const CodeMessageMap: Record<number, string> = {
|
||||
403: '禁止访问此内容: 抱歉,由于安全问题您被禁止访问此内容。如果您认为这是个错误,请联系我们获取帮助。',
|
||||
404: '内容未找到: 您正在寻找的内容可能已被删除、名称已更改或暂时不可用。',
|
||||
500: '服务器错误: 请稍后再试,或者返回主页。如果问题持续存在,请联系我们,我们会尽快修复。'
|
||||
403: "禁止访问此内容: 抱歉,由于安全问题您被禁止访问此内容。如果您认为这是个错误,请联系我们获取帮助。",
|
||||
404: "内容未找到: 您正在寻找的内容可能已被删除、名称已更改或暂时不可用。",
|
||||
500: "服务器错误: 请稍后再试,或者返回主页。如果问题持续存在,请联系我们,我们会尽快修复。",
|
||||
};
|
||||
|
||||
// 默认处理函数
|
||||
server.all('/', (request, reply) => {
|
||||
const headers = request.headers;
|
||||
server.all("/", (request, reply) => {
|
||||
const headers = request.headers;
|
||||
|
||||
// 获取OriginalURI的文件后缀
|
||||
const originalURI: string = headers[Headers.OriginalURI] || '';
|
||||
const urlExtWithDot = path.extname(originalURI);
|
||||
// 获取OriginalURI的文件后缀
|
||||
const originalURI: string = headers[Headers.OriginalURI] || "";
|
||||
const urlExtWithDot = path.extname(originalURI);
|
||||
|
||||
// 获取FormatHeader对应的文件后缀
|
||||
const format: string = headers[Headers.FormatHeader] || '';
|
||||
// 排除format存在多个的情况 目前只支持一个
|
||||
const firstFormat = format.split(',')[0];
|
||||
const formatExt = mime.getExtension(firstFormat);
|
||||
const formatExtWithDot = formatExt ? `.${formatExt}` : '';
|
||||
// 获取FormatHeader对应的文件后缀
|
||||
const format: string = headers[Headers.FormatHeader] || "";
|
||||
// 排除format存在多个的情况 目前只支持一个
|
||||
const firstFormat = format.split(",")[0];
|
||||
const formatExt = mime.getExtension(firstFormat);
|
||||
const formatExtWithDot = formatExt ? `.${formatExt}` : "";
|
||||
|
||||
const ext = formatExtWithDot || urlExtWithDot || '.html';
|
||||
const ext = formatExtWithDot || urlExtWithDot || ".html";
|
||||
|
||||
// 获取header中的其他内容
|
||||
const requestId: string = headers[Headers.RequestId] || '';
|
||||
// 获取header中的其他内容
|
||||
const requestId: string = headers[Headers.RequestId] || "";
|
||||
|
||||
// 获取CodeHeader对应的状态码
|
||||
const code: number = Number(headers[Headers.CodeHeader] || 404);
|
||||
// 获取CodeHeader对应的状态码
|
||||
const code: number = Number(headers[Headers.CodeHeader] || 404);
|
||||
|
||||
// 获取兼容code 比如没有403的code,就使用404 没有502的code,就使用500 取第一位相同的但是最小的code
|
||||
let compatibleCode = code;
|
||||
// 获取兼容code 比如没有403的code,就使用404 没有502的code,就使用500 取第一位相同的但是最小的code
|
||||
let compatibleCode = code;
|
||||
|
||||
const isCodeExist = Reflect.has(CodeMessageMap, code);
|
||||
const isCodeExist = Reflect.has(CodeMessageMap, code);
|
||||
|
||||
if (!isCodeExist) {
|
||||
const codeList = Object.keys(CodeMessageMap).map(Number).sort((a, b) => a - b);
|
||||
compatibleCode = Number(codeList.find((c) => String(c).startsWith(String(code).charAt(0))) || 404)
|
||||
}
|
||||
if (!isCodeExist) {
|
||||
const codeList = Object.keys(CodeMessageMap).map(Number).sort((a, b) =>
|
||||
a - b
|
||||
);
|
||||
compatibleCode = Number(
|
||||
codeList.find((c) => String(c).startsWith(String(code).charAt(0))) || 404,
|
||||
);
|
||||
}
|
||||
|
||||
const message = CodeMessageMap[compatibleCode];
|
||||
const message = CodeMessageMap[compatibleCode];
|
||||
|
||||
const data = { code, requestId, message };
|
||||
const data = { code, requestId, message };
|
||||
|
||||
console.log(`[${new Date().toISOString()}] code:${code} url:'${originalURI}' ext:'${ext}' requestId:'${requestId}'`);
|
||||
console.log(
|
||||
`[${
|
||||
new Date().toISOString()
|
||||
}] code:${code} url:'${originalURI}' ext:'${ext}' requestId:'${requestId}'`,
|
||||
);
|
||||
|
||||
// 根据后缀渲染不同的内容
|
||||
if (ext === '.html' || ext === '.htm') {
|
||||
const [title, content] = message.split(':');
|
||||
// 根据后缀渲染不同的内容
|
||||
if (ext === ".html" || ext === ".htm") {
|
||||
const [title, content] = message.split(":");
|
||||
|
||||
const htmlContent = content.trim().replace(/。(?=.+)/, '。<br>');
|
||||
const htmlContent = content.trim().replace(/。(?=.+)/, "。<br>");
|
||||
|
||||
reply.code(code).view('error.hbs', { ...data, title, content: htmlContent });
|
||||
} else if (ext === '.json') {
|
||||
reply.code(code).send(data);
|
||||
} else {
|
||||
reply.code(code).send(message);
|
||||
}
|
||||
reply.code(code).view("error.hbs", {
|
||||
...data,
|
||||
title,
|
||||
content: htmlContent,
|
||||
});
|
||||
} else if (ext === ".json") {
|
||||
reply.code(code).send(data);
|
||||
} else {
|
||||
reply.code(code).send(message);
|
||||
}
|
||||
});
|
||||
|
||||
// 提供健康检查接口
|
||||
server.get('/healthz', (_request, reply) => reply.send('OK'));
|
||||
server.get("/healthz", (_request, reply) => reply.send("OK"));
|
||||
|
||||
server.listen({ port: 8080, host: '0.0.0.0'}, (_err, address) => {
|
||||
if (_err) throw _err
|
||||
server.listen({ port: 8080, host: "0.0.0.0" }, (_err, address) => {
|
||||
if (_err) throw _err;
|
||||
|
||||
console.log(`Server listening at ${address}`);
|
||||
console.log(`Server listening at ${address}`);
|
||||
});
|
||||
|
||||
在新议题中引用
屏蔽一个用户