128 lines
3.8 KiB
TypeScript
128 lines
3.8 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
|
|
export type TCompressionMethod = 'gzip' | 'deflate' | 'br' | 'none';
|
|
export interface ICompressionResult {
|
|
result: Buffer;
|
|
compressionMethod: TCompressionMethod;
|
|
}
|
|
|
|
export class Compressor {
|
|
private _cache: Map<string, Buffer>;
|
|
private MAX_CACHE_SIZE: number = 100 * 1024 * 1024; // 100 MB
|
|
|
|
constructor() {
|
|
this._cache = new Map<string, Buffer>();
|
|
}
|
|
|
|
private _addToCache(key: string, value: Buffer) {
|
|
this._cache.set(key, value);
|
|
this._manageCacheSize();
|
|
}
|
|
|
|
private _manageCacheSize() {
|
|
let currentSize = Array.from(this._cache.values()).reduce((acc, buffer) => acc + buffer.length, 0);
|
|
|
|
while (currentSize > this.MAX_CACHE_SIZE) {
|
|
const firstKey = this._cache.keys().next().value;
|
|
const firstValue = this._cache.get(firstKey)!;
|
|
currentSize -= firstValue.length;
|
|
this._cache.delete(firstKey);
|
|
}
|
|
}
|
|
|
|
public async compressContent(
|
|
content: Buffer,
|
|
method: 'gzip' | 'deflate' | 'br' | 'none'
|
|
): Promise<Buffer> {
|
|
const cacheKey = content.toString('base64') + method;
|
|
const cachedResult = this._cache.get(cacheKey);
|
|
|
|
if (cachedResult) {
|
|
return cachedResult;
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const callback = (err: Error | null, result: Buffer) => {
|
|
if (err) reject(err);
|
|
else {
|
|
this._addToCache(cacheKey, result);
|
|
resolve(result);
|
|
}
|
|
};
|
|
|
|
switch (method) {
|
|
case 'gzip':
|
|
plugins.zlib.gzip(content, {
|
|
level: 1,
|
|
},callback,);
|
|
break;
|
|
case 'br':
|
|
plugins.zlib.brotliCompress(content, {}, callback);
|
|
break;
|
|
case 'deflate':
|
|
plugins.zlib.deflate(content, callback);
|
|
break;
|
|
default:
|
|
this._addToCache(cacheKey, content);
|
|
resolve(content);
|
|
}
|
|
});
|
|
}
|
|
|
|
public determineCompression(acceptEncoding: string | string[], preferredCompressionMethodsArg: TCompressionMethod[] = []) {
|
|
// Ensure acceptEncoding is a single string
|
|
const encodingString = Array.isArray(acceptEncoding)
|
|
? acceptEncoding.join(', ')
|
|
: acceptEncoding;
|
|
|
|
let compressionMethod: TCompressionMethod = 'none';
|
|
|
|
// Prioritize preferred compression methods if provided
|
|
for (const preferredMethod of preferredCompressionMethodsArg) {
|
|
if (new RegExp(`\\b${preferredMethod}\\b`).test(encodingString)) {
|
|
return preferredMethod;
|
|
}
|
|
}
|
|
|
|
// Fallback to default prioritization if no preferred method matches
|
|
if (/\bbr\b/.test(encodingString)) {
|
|
compressionMethod = 'br';
|
|
} else if (/\bgzip\b/.test(encodingString)) {
|
|
compressionMethod = 'gzip';
|
|
} else if (/\bdeflate\b/.test(encodingString)) {
|
|
compressionMethod = 'deflate';
|
|
}
|
|
|
|
return compressionMethod;
|
|
}
|
|
|
|
public async maybeCompress(requestHeaders: plugins.http.IncomingHttpHeaders, content: Buffer, preferredCompressionMethodsArg?: TCompressionMethod[]): Promise<ICompressionResult> {
|
|
const acceptEncoding = requestHeaders['accept-encoding'];
|
|
const compressionMethod = this.determineCompression(acceptEncoding, preferredCompressionMethodsArg);
|
|
const result = await this.compressContent(content, compressionMethod);
|
|
return {
|
|
result,
|
|
compressionMethod,
|
|
};
|
|
}
|
|
|
|
public createCompressionStream(method: 'gzip' | 'deflate' | 'br' | 'none') {
|
|
let compressionStream: any;
|
|
switch (method) {
|
|
case 'gzip':
|
|
compressionStream = plugins.zlib.createGzip();
|
|
case 'br':
|
|
compressionStream = plugins.zlib.createBrotliCompress({
|
|
chunkSize: 16 * 1024,
|
|
params: {
|
|
|
|
},
|
|
});
|
|
case 'deflate':
|
|
compressionStream = plugins.zlib.createDeflate();
|
|
default:
|
|
compressionStream = plugins.smartstream.createPassThrough();
|
|
}
|
|
}
|
|
}
|