UNPKG

@chubbyts/chubbyts-http-static-file

Version:

A minimal static file handler for chubbyts-http-types.

66 lines (65 loc) 2.46 kB
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), }; }; };