mirror of
https://github.com/certd/certd.git
synced 2026-10-13 07:38:56 +08:00
perf(server): response优化为gzip压缩
添加了响应内容压缩中间件,支持对可压缩类型的响应进行gzip压缩,减少传输体积提升性能,同时添加了对应的测试用例
This commit is contained in:
@@ -22,6 +22,7 @@ import * as upload from "@midwayjs/upload";
|
||||
import { setLogger } from "@certd/acme-client";
|
||||
import { HiddenMiddleware } from "./middleware/hidden.js";
|
||||
import { shouldSetDefaultNoCache } from "./configuration-cache.js";
|
||||
import { compressResponse } from "./middleware/compression.js";
|
||||
// import * as swagger from '@midwayjs/swagger';
|
||||
//@ts-ignore
|
||||
// process.env.UV_THREADPOOL_SIZE = 2
|
||||
@@ -90,6 +91,7 @@ export class MainConfiguration {
|
||||
origin: "*",
|
||||
})
|
||||
);
|
||||
this.app.use(compressResponse);
|
||||
//
|
||||
// this.app.use(async (ctx, next) => {
|
||||
// // 只在返回 'index.html' 的时候设置 maxAge
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { shouldCompress } from "./compression.js";
|
||||
|
||||
function createContext(overrides: Record<string, unknown> = {}) {
|
||||
const headers: Record<string, string> = { "Accept-Encoding": "gzip" };
|
||||
return {
|
||||
method: "GET",
|
||||
status: 200,
|
||||
body: { data: "response" },
|
||||
request: { get: (name: string) => headers[name] },
|
||||
response: { get: (name: string) => headers[name] },
|
||||
...overrides,
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("compression middleware", () => {
|
||||
it("compresses gzip-capable API responses", () => {
|
||||
assert.equal(shouldCompress(createContext()), true);
|
||||
});
|
||||
|
||||
it("skips responses without gzip support or downloads", () => {
|
||||
assert.equal(shouldCompress(createContext({ request: { get: () => "br" } })), false);
|
||||
assert.equal(shouldCompress(createContext({ response: { get: (name: string) => (name === "Content-Disposition" ? "attachment" : undefined) } })), false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { gzip } from "node:zlib";
|
||||
import { promisify } from "node:util";
|
||||
import { IMidwayKoaContext, NextFunction } from "@midwayjs/koa";
|
||||
|
||||
const gzipAsync = promisify(gzip);
|
||||
const MIN_COMPRESS_SIZE = 1024;
|
||||
|
||||
function acceptsGzip(header: string | undefined): boolean {
|
||||
if (!header) {
|
||||
return false;
|
||||
}
|
||||
const encodings = header.split(",").map(value => value.trim().split(";"));
|
||||
const gzipEncoding = encodings.find(([encoding]) => encoding === "gzip") ?? encodings.find(([encoding]) => encoding === "*");
|
||||
if (!gzipEncoding) {
|
||||
return false;
|
||||
}
|
||||
const quality = gzipEncoding[1]?.trim().replace(/^q=/, "");
|
||||
return quality !== "0";
|
||||
}
|
||||
|
||||
function isCompressibleContentType(contentType: string | undefined): boolean {
|
||||
if (!contentType) {
|
||||
return true;
|
||||
}
|
||||
const type = contentType.split(";", 1)[0].trim().toLowerCase();
|
||||
return type.startsWith("text/") || type.includes("json") || type.includes("javascript") || type.includes("xml") || type.includes("svg");
|
||||
}
|
||||
|
||||
export function shouldCompress(ctx: Pick<IMidwayKoaContext, "method" | "status" | "response" | "request" | "body">): boolean {
|
||||
if (ctx.method === "HEAD" || ctx.status === 204 || ctx.status === 304) {
|
||||
return false;
|
||||
}
|
||||
if (ctx.response.get("Content-Encoding") || ctx.response.get("Content-Disposition")) {
|
||||
return false;
|
||||
}
|
||||
if (!acceptsGzip(ctx.request.get("Accept-Encoding"))) {
|
||||
return false;
|
||||
}
|
||||
if (!isCompressibleContentType(ctx.response.get("Content-Type"))) {
|
||||
return false;
|
||||
}
|
||||
if (Buffer.isBuffer(ctx.body) && !ctx.response.get("Content-Type")) {
|
||||
return false;
|
||||
}
|
||||
return typeof ctx.body === "string" || Buffer.isBuffer(ctx.body) || (ctx.body !== null && typeof ctx.body === "object");
|
||||
}
|
||||
|
||||
export async function compressResponse(ctx: IMidwayKoaContext, next: NextFunction): Promise<void> {
|
||||
await next();
|
||||
if (!shouldCompress(ctx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = ctx.response.get("Content-Type");
|
||||
const body = Buffer.isBuffer(ctx.body) ? ctx.body : Buffer.from(typeof ctx.body === "string" ? ctx.body : JSON.stringify(ctx.body));
|
||||
if (body.length < MIN_COMPRESS_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const compressed = await gzipAsync(body);
|
||||
ctx.body = compressed;
|
||||
if (contentType) {
|
||||
ctx.response.set("Content-Type", contentType);
|
||||
}
|
||||
ctx.response.set("Content-Encoding", "gzip");
|
||||
ctx.response.set("Vary", "Accept-Encoding");
|
||||
ctx.response.remove("Content-Length");
|
||||
}
|
||||
Reference in New Issue
Block a user