filesafe/controllers/middlewares/ServeLiveDirectory.js
2022-08-23 15:12:25 +07:00

146 lines
3.7 KiB
JavaScript

/*
* ServeLiveDirectory is a middleware wrapper for LiveDirectory library.
*
* It is mainly intended to add Conditional GETs support,
* which involves handling cache-related headers such as
* If-Match, If-Unmodified-Since, ETag, etc.
*
* LiveDirectory monitors and caches all the files in the configure directory into memory,
* which allows very fast lookups, thus allowing multiple instances of this middleware
* to be used together, if needed.
*
* However, due to the fact that it caches all the files into memory,
* this may not be the best choice in an environment where memory space is a premium.
*/
const LiveDirectory = require('live-directory')
const serveUtils = require('./../utils/serveUtils')
class ServeLiveDirectory {
instance
#options
constructor (directory, options = {}) {
if (!directory || typeof directory !== 'string') {
throw new TypeError('Root directory must be set')
}
this.directory = serveUtils.forwardSlashes(directory)
// Ensure does not end with a forward slash
if (this.directory.endsWith('/')) {
this.directory = this.directory.slice(0, -1)
}
if (options.etag === undefined) {
options.etag = true
}
if (options.lastModified === undefined) {
options.lastModified = true
}
if (options.setHeaders && typeof options.setHeaders !== 'function') {
throw new TypeError('Middleware option setHeaders must be a function')
}
const instanceOptions = Object.assign({}, options.instanceOptions)
instanceOptions.path = this.directory
delete options.instanceOptions
if (!instanceOptions.ignore) {
instanceOptions.ignore = path => {
// ignore dot files
return path.startsWith('.')
}
}
this.instance = new LiveDirectory(instanceOptions)
this.#options = options
}
get (path) {
const file = this.instance.get(path)
return file
}
handler (req, res, path, file) {
// Set Content-Type
res.type(file.extension)
// Set header fields
this.#setHeaders(req, res, file)
// Conditional GET support
if (serveUtils.assertConditionalGET(req, res)) {
return res.end()
}
// HEAD support
if (req.method === 'HEAD') {
return res.end()
}
return res.send(file.buffer)
}
ready () {
// Returns a promise which resolves to true once LiveDirectory instance is ready
return this.instance.ready()
}
#middleware (req, res, next) {
// Only process GET and HEAD requests
if (req.method !== 'GET' && req.method !== 'HEAD') {
return next()
}
// If root path is set, ensure it matches the request
let path = req.path
if (this.#options.root) {
if (path.indexOf(this.#options.root) === 0) {
// Re-map path for internal .get()
path = path.replace(this.#options.root, '')
} else {
// Immediately proceed to next middleware otherwise
return next()
}
}
const file = this.get(path)
if (file === undefined) {
return next()
}
return this.handler(req, res, path, file)
}
#setHeaders (req, res, file) {
// Always do external setHeaders function first,
// in case it will overwrite the following default headers anyways
if (this.#options.setHeaders) {
this.#options.setHeaders(req, res)
}
if (this.#options.lastModified && !res.get('Last-Modified')) {
const modified = new Date(file.last_update).toUTCString()
res.header('Last-Modified', modified)
}
if (this.#options.etag && !res.get('ETag')) {
const val = file.etag
res.header('ETag', val)
}
}
get middleware () {
return this.#middleware.bind(this)
}
}
module.exports = ServeLiveDirectory