Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions lib/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,31 @@ const status = (code) => {
return file;
};

const COMPRESSORS = {
gzip: node.zlib.createGzip,
deflate: node.zlib.createDeflate,
br: node.zlib.createBrotliCompress,
zstd: node.zlib.createZstdCompress,
};

class Static extends Place {
files = new Map();
compressor = null;
compressType = 'none';
maxFileSize = -1;

constructor(name, application, options = {}) {
super(name, application);
this.files = new Map();
this.ext = options.ext;
this.maxFileSize = -1;
const { compressType, ext } = options;
this.ext = ext;
if (compressType) {
const compressor = COMPRESSORS[compressType];
if (!compressor) {
throw new Error(`Unsupported compression type ${compressType}`);
}
this.compressType = compressType;
this.compressor = compressor();
}
}
Comment thread
tshemsedinov marked this conversation as resolved.

get(key) {
Expand All @@ -101,6 +120,12 @@ class Static extends Place {
this.files.delete(key);
}

compress(filePath) {
const fileStream = node.fs.createReadStream(filePath);
fileStream.pipe(this.compressor);
return node.streamConsumers.buffer(this.compressor);
}

async change(filePath) {
if (this.maxFileSize === -1) {
const maxFileSize = this.application.config?.cache?.maxFileSize;
Expand All @@ -115,7 +140,13 @@ class Static extends Place {
if (stat.size > this.maxFileSize) {
this.files.set(key, { data: null, stat });
} else {
const data = await fsp.readFile(filePath);
let data = null;
if (this.compressor) {
data = await this.compress(filePath);
stat.size = data.length;
} else {
data = await node.fsp.readFile(filePath);
}
this.files.set(key, { data, stat });
}
} catch {
Expand Down Expand Up @@ -155,17 +186,19 @@ class Static extends Place {
const fileExt = metautil.fileExt(filePath);
let file = this.find(filePath);
if (file.data && file.stat) {
if (file.code === -1) this.write(req, res, file.data, 200, 'html');
else this.write(req, res, file.data, file.code, fileExt);
return;
if (file.code === -1) {
return void this.write(req, res, file.data, 200, 'html');
}
const opt = { contentEncoding: this.compressType };
return void this.write(req, res, file.data, file.code, fileExt, opt);
}
const absPath = path.posix.join(this.path, url);
if (absPath.startsWith(this.path)) {
let { stat } = file;
if (!stat) stat = await fsp.stat(absPath).catch(() => null);
if (stat && stat.isFile()) {
const { size } = stat;
const options = { size };
const options = { size, contentEncoding: this.compressType };
let code = 200;
if (req.headers.range) {
const range = metautil.parseRange(req.headers.range);
Expand Down
89 changes: 89 additions & 0 deletions test/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const { test } = require('node:test');
const assert = require('node:assert');
const zlib = require('node:zlib');
const path = require('node:path');
const { Static } = require('../lib/static.js');

Expand Down Expand Up @@ -32,3 +33,91 @@ test('lib/static load - should load static files correctly', async () => {
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, 10000000);
});

test('lib/static load - should compress correctly by gzip', async () => {
const cache = new Static('lib', application, { compressType: 'gzip' });
assert.strictEqual(cache.files instanceof Map, true);
assert.strictEqual(cache.files.size, 0);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, -1);
assert.strictEqual(cache.get('/example/add.js'), undefined);

await cache.load();
assert.strictEqual(cache.files.size, 13);
const file = cache.get('/example/add.js');
assert.strictEqual(file.data instanceof Buffer, true);
assert.strictEqual(file.data.length, 116);
assert.strictEqual(cache.get('/example/unknown.js'), undefined);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, 10000000);
});

test('lib/static load - should compress correctly by deflate', async () => {
const cache = new Static('lib', application, {
compressType: 'deflate',
});
assert.strictEqual(cache.files instanceof Map, true);
assert.strictEqual(cache.files.size, 0);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, -1);
assert.strictEqual(cache.get('/example/add.js'), undefined);

await cache.load();
assert.strictEqual(cache.files.size, 13);
const file = cache.get('/example/add.js');
assert.strictEqual(file.data instanceof Buffer, true);
assert.strictEqual(file.data.length, 104);
assert.strictEqual(cache.get('/example/unknown.js'), undefined);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, 10000000);
});

test('lib/static load - should compress correctly by brotli', async () => {
const cache = new Static('lib', application, {
compressType: 'br',
});
assert.strictEqual(cache.files instanceof Map, true);
assert.strictEqual(cache.files.size, 0);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, -1);
assert.strictEqual(cache.get('/example/add.js'), undefined);

await cache.load();
assert.strictEqual(cache.files.size, 13);
const file = cache.get('/example/add.js');
assert.strictEqual(file.data instanceof Buffer, true);
assert.strictEqual(file.data.length, 100);
assert.strictEqual(cache.get('/example/unknown.js'), undefined);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, 10000000);
});

if (zlib.zstdCompress) {
test('lib/static load - should compress correctly by zstd', async () => {
const cache = new Static('lib', application, {
compressType: 'zstd',
});
assert.strictEqual(cache.files instanceof Map, true);
assert.strictEqual(cache.files.size, 0);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, -1);
assert.strictEqual(cache.get('/example/add.js'), undefined);

await cache.load();
assert.strictEqual(cache.files.size, 13);
const file = cache.get('/example/add.js');
assert.strictEqual(file.data instanceof Buffer, true);
assert.strictEqual(file.data.length, 109);
assert.strictEqual(cache.get('/example/unknown.js'), undefined);
assert.strictEqual(cache.ext, undefined);
assert.strictEqual(cache.maxFileSize, 10000000);
});
}

test('lib/static - should throw error on unsupported compression', async () => {
assert.throws(() => {
new Static('lib', application, {
compressType: 'unsupported',
});
}, new Error('Unsupported compression type unsupported'));
});
3 changes: 2 additions & 1 deletion types/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export interface Static {
name: string;
path: string;
files: Map<string, { data: Buffer | null; stat: object | null }>;
get(name: string): { data: Buffer | null; stat: object | null } | undefined;
get(name: string): unknown;
compress(filePath: string): Promise<Buffer>;
find(
path: string,
code?: number,
Expand Down
3 changes: 3 additions & 0 deletions types/impress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import * as _qs from 'node:querystring';
import * as _querystring from 'node:querystring';
import * as _assert from 'node:assert';
import * as _stream from 'node:stream';
import * as _streamConsumers from 'node:stream/consumers';
import * as _fs from 'node:fs';
import * as _crypto from 'node:crypto';
import * as _zlib from 'node:zlib';
Expand Down Expand Up @@ -84,6 +85,8 @@ declare global {
const querystring: typeof _qs;
const assert: typeof _assert;
const stream: typeof _stream;
const stream_consumers: typeof _streamConsumers;
const streamConsumers: typeof _streamConsumers;
const fs: typeof _fs;
const fsp: typeof _fs.promises;
const crypto: typeof _crypto;
Expand Down
Loading