@chubbyts/chubbyts-http-static-file
Version:
A minimal static file handler for chubbyts-http-types.
66 lines (65 loc) • 2.46 kB
JavaScript
import { createHash, getHashes } from 'crypto';
import { accessSync, constants, createReadStream, statSync } from 'fs';
import { extname } from 'path';
import { createNotFound } from '@chubbyts/chubbyts-http-error/dist/http-error';
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 = (responseFactory, streamFromFileFactory, publicDirectory, mimeTypes, hashAlgorithm = 'md5') => {
assertHashAlgorithm(hashAlgorithm);
const createResponse = (code, filename, hash) => {
const extension = extname(filename).slice(1);
const mimeType = mimeTypes.get(extension);
const response = responseFactory(code);
return {
...response,
headers: {
...response.headers,
'content-length': [String(statSync(filename).size)],
etag: [hash],
...(mimeType ? { 'content-type': [mimeType] } : {}),
},
};
};
return async (request) => {
const filepath = publicDirectory + request.uri.path;
if (!access(filepath) || statSync(filepath).isDirectory()) {
throw createNotFound({ detail: `There is no file at path "${request.uri.path}"` });
}
const hash = await checksum(filepath, hashAlgorithm);
if (request.headers['if-none-match']?.includes(hash)) {
const response = createResponse(304, filepath, hash);
response.body.end();
return response;
}
const response = createResponse(200, filepath, hash);
return {
...response,
body: streamFromFileFactory(filepath),
};
};
};