67 lines
2.5 KiB
JavaScript
67 lines
2.5 KiB
JavaScript
|
import { createReadStream, statSync } from 'node:fs';
|
||
|
import { extname, isAbsolute } from 'node:path';
|
||
|
import { join } from 'node:path';
|
||
|
import mime from 'mime';
|
||
|
import { createETag } from './utils.js';
|
||
|
export const enableCaching = (res, caching) => {
|
||
|
let cc = caching.maxAge != null && `public,max-age=${caching.maxAge}`;
|
||
|
if (cc && caching.immutable)
|
||
|
cc += ',immutable';
|
||
|
else if (cc && caching.maxAge === 0)
|
||
|
cc += ',must-revalidate';
|
||
|
if (cc)
|
||
|
res.setHeader('Cache-Control', cc);
|
||
|
};
|
||
|
/**
|
||
|
* Sends a file by piping a stream to response.
|
||
|
*
|
||
|
* It also checks for extension to set a proper `Content-Type` header.
|
||
|
*
|
||
|
* Path argument must be absolute. To use a relative path, specify the `root` option first.
|
||
|
*
|
||
|
* @param res Response
|
||
|
*/
|
||
|
export const sendFile = (req, res) => (path, opts = {}, cb) => {
|
||
|
const { root, headers = {}, encoding = 'utf-8', caching, ...options } = opts;
|
||
|
if (!isAbsolute(path) && !root)
|
||
|
throw new TypeError('path must be absolute');
|
||
|
if (caching)
|
||
|
enableCaching(res, caching);
|
||
|
const filePath = root ? join(root, path) : path;
|
||
|
const stats = statSync(filePath);
|
||
|
headers['Content-Encoding'] = encoding;
|
||
|
headers['Last-Modified'] = stats.mtime.toUTCString();
|
||
|
headers.ETag = createETag(stats, encoding);
|
||
|
if (!res.getHeader('Content-Type'))
|
||
|
headers['Content-Type'] = `${mime.getType(extname(path))}; charset=utf-8`;
|
||
|
let status = res.statusCode || 200;
|
||
|
if (req.headers.range) {
|
||
|
status = 206;
|
||
|
const [x, y] = req.headers.range.replace('bytes=', '').split('-');
|
||
|
const end = (options.end = Number.parseInt(y, 10) || stats.size - 1);
|
||
|
const start = (options.start = Number.parseInt(x, 10) || 0);
|
||
|
if (start >= stats.size || end >= stats.size) {
|
||
|
res
|
||
|
.writeHead(416, {
|
||
|
'Content-Range': `bytes */${stats.size}`
|
||
|
})
|
||
|
.end();
|
||
|
return res;
|
||
|
}
|
||
|
headers['Content-Range'] = `bytes ${start}-${end}/${stats.size}`;
|
||
|
headers['Content-Length'] = end - start + 1;
|
||
|
headers['Accept-Ranges'] = 'bytes';
|
||
|
}
|
||
|
else {
|
||
|
headers['Content-Length'] = stats.size;
|
||
|
}
|
||
|
for (const [k, v] of Object.entries(headers))
|
||
|
res.setHeader(k, v);
|
||
|
res.writeHead(status, headers);
|
||
|
const stream = createReadStream(filePath, options);
|
||
|
if (cb)
|
||
|
stream.on('error', (err) => cb(err)).on('end', () => cb());
|
||
|
stream.pipe(res);
|
||
|
return res;
|
||
|
};
|
||
|
//# sourceMappingURL=sendFile.js.map
|