UNPKG

@chubbyts/chubbyts-undici-static-file

Version:

A minimal static file handler for chubbyts-undici-server

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