@chubbyts/chubbyts-undici-static-file
Version:
A minimal static file handler for chubbyts-undici-server
66 lines (65 loc) • 2.53 kB
JavaScript
import { createHash, getHashes } from 'node:crypto';
import { accessSync, constants, createReadStream, statSync } from 'node:fs';
import { extname } from 'node:path';
import { STATUS_CODES } from 'node:http';
import { createNotFound } from '@chubbyts/chubbyts-http-error/dist/http-error';
import { Response } from '@chubbyts/chubbyts-undici-server/dist/server';
const assertHashAlgorithm = (hashAlgorithm) => {
const supportedHashAlgorithms = getHashes();
if (!supportedHashAlgorithms.includes(hashAlgorithm)) {
throw new Error(`Not supported hash algorithm: "${hashAlgorithm}", supported are: "${supportedHashAlgorithms.join('", "')}"`);
}
};
const access = (filepath) => {
try {
accessSync(filepath, constants.R_OK);
return true;
}
catch {
return false;
}
};
const checksum = async (filepath, hashAlgorithm) => {
return new Promise((resolve) => {
const hash = createHash(hashAlgorithm);
const stream = createReadStream(filepath);
stream.on('data', (data) => {
hash.update(data);
});
stream.on('end', () => {
resolve(hash.digest('hex'));
});
});
};
export const createStaticFileHandler = (publicDirectory, mimeTypes, hashAlgorithm = 'md5') => {
assertHashAlgorithm(hashAlgorithm);
const createResponse = (body, code, filename, hash) => {
const extension = extname(filename).slice(1);
const mimeType = mimeTypes.get(extension);
return new Response(body, {
status: code,
statusText: STATUS_CODES[code],
headers: {
'content-length': String(statSync(filename).size),
etag: hash,
...(mimeType ? { 'content-type': mimeType } : {}),
},
});
};
return async (serverRequest) => {
const url = new URL(serverRequest.url);
const filepath = publicDirectory + url.pathname;
if (!access(filepath) || statSync(filepath).isDirectory()) {
throw createNotFound({ detail: `There is no file at path "${url.pathname}"` });
}
const hash = await checksum(filepath, hashAlgorithm);
if (serverRequest.headers
.get('if-none-match')
?.split(',')
?.map((headerPart) => headerPart.trim())
?.includes(hash)) {
return createResponse(null, 304, filepath, hash);
}
return createResponse(createReadStream(filepath), 200, filepath, hash);
};
};