mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-02-22 21:29:09 +00:00
refactor: serve handlers/middlewares
moved shared codes into serveUtils to reduce complexity
This commit is contained in:
parent
2b2a7c407d
commit
0598a63989
@ -20,7 +20,6 @@
|
||||
const contentDisposition = require('content-disposition')
|
||||
const etag = require('etag')
|
||||
const fs = require('fs')
|
||||
const parseRange = require('range-parser')
|
||||
const SimpleDataStore = require('./../utils/SimpleDataStore')
|
||||
const errors = require('./../errorsController')
|
||||
const paths = require('./../pathsController')
|
||||
@ -152,13 +151,6 @@ class ServeStatic {
|
||||
return stat
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on https://github.com/pillarjs/send/blob/0.18.0/index.js
|
||||
* Copyright(c) 2012 TJ Holowaychuk
|
||||
* Copyright(c) 2014-2022 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
async #handler (req, res) {
|
||||
if (this.#options.ignorePatterns && this.#options.ignorePatterns.some(pattern => req.path.startsWith(pattern))) {
|
||||
return errors.handleNotFound(req, res)
|
||||
@ -176,90 +168,38 @@ class ServeStatic {
|
||||
return errors.handleNotFound(req, res)
|
||||
}
|
||||
|
||||
// ReadStream options
|
||||
let len = stat.size
|
||||
const opts = {}
|
||||
let ranges = req.headers.range
|
||||
let offset = 0
|
||||
|
||||
// set content-type
|
||||
// Set Content-Type
|
||||
res.type(req.path)
|
||||
|
||||
// set header fields
|
||||
// Set header fields
|
||||
await this.#setHeaders(req, res, stat)
|
||||
|
||||
// conditional GET support
|
||||
if (serveUtils.isConditionalGET(req)) {
|
||||
if (serveUtils.isPreconditionFailure(req, res)) {
|
||||
return res.status(412).end()
|
||||
}
|
||||
|
||||
if (serveUtils.isFresh(req, res)) {
|
||||
return res.status(304).end()
|
||||
}
|
||||
// Conditional GET support
|
||||
if (serveUtils.assertConditionalGET(req, res)) {
|
||||
return res.end()
|
||||
}
|
||||
|
||||
// adjust len to start/end options
|
||||
len = Math.max(0, len - offset)
|
||||
if (opts.end !== undefined) {
|
||||
const bytes = opts.end - offset + 1
|
||||
if (len > bytes) len = bytes
|
||||
}
|
||||
|
||||
// Range support
|
||||
if (this.#options.acceptRanges && serveUtils.BYTES_RANGE_REGEXP.test(ranges)) {
|
||||
// parse
|
||||
ranges = parseRange(len, ranges, {
|
||||
combine: true
|
||||
})
|
||||
|
||||
// If-Range support
|
||||
if (!serveUtils.isRangeFresh(req, res)) {
|
||||
// range stale
|
||||
ranges = -2
|
||||
}
|
||||
|
||||
// unsatisfiable
|
||||
if (ranges === -1) {
|
||||
// Content-Range
|
||||
res.header('Content-Range', serveUtils.contentRange('bytes', len))
|
||||
|
||||
// 416 Requested Range Not Satisfiable
|
||||
return res.status(416).end()
|
||||
}
|
||||
|
||||
// valid (syntactically invalid/multiple ranges are treated as a regular response)
|
||||
if (ranges !== -2 && ranges.length === 1) {
|
||||
// Content-Range
|
||||
res.status(206)
|
||||
res.header('Content-Range', serveUtils.contentRange('bytes', len, ranges[0]))
|
||||
|
||||
// adjust for requested range
|
||||
offset += ranges[0].start
|
||||
len = ranges[0].end - ranges[0].start + 1
|
||||
}
|
||||
} else if (req.method === 'GET' && this.setContentDisposition) {
|
||||
// Only set Content-Disposition on complete GET requests
|
||||
// Range requests are typically when streaming
|
||||
await this.setContentDisposition(req, res)
|
||||
}
|
||||
|
||||
// set read options
|
||||
opts.start = offset
|
||||
opts.end = Math.max(offset, offset + len - 1)
|
||||
// ReadStream options with Content-Range support if required
|
||||
const { options, length } = serveUtils.buildReadStreamOptions(req, res, stat, this.#options.acceptRanges)
|
||||
|
||||
// HEAD support
|
||||
if (req.method === 'HEAD') {
|
||||
// If HEAD, also set Content-Length (must be string)
|
||||
res.header('Content-Length', String(len))
|
||||
res.header('Content-Length', String(length))
|
||||
return res.end()
|
||||
}
|
||||
|
||||
if (len === 0) {
|
||||
// Only set Content-Disposition on initial GET request
|
||||
// Skip for subsequent requests on non-zero start byte (e.g. streaming)
|
||||
if (options.start === 0 && this.setContentDisposition) {
|
||||
await this.setContentDisposition(req, res)
|
||||
}
|
||||
|
||||
if (length === 0) {
|
||||
res.end()
|
||||
}
|
||||
|
||||
return this.#stream(req, res, fullPath, opts, len)
|
||||
return this.#stream(req, res, fullPath, options, length)
|
||||
}
|
||||
|
||||
async #setHeaders (req, res, stat) {
|
||||
@ -289,8 +229,8 @@ class ServeStatic {
|
||||
}
|
||||
}
|
||||
|
||||
async #stream (req, res, fullPath, opts, len) {
|
||||
const readStream = fs.createReadStream(fullPath, opts)
|
||||
async #stream (req, res, fullPath, options, length) {
|
||||
const readStream = fs.createReadStream(fullPath, options)
|
||||
|
||||
readStream.on('error', error => {
|
||||
readStream.destroy()
|
||||
@ -298,7 +238,7 @@ class ServeStatic {
|
||||
})
|
||||
|
||||
// 2nd param will be set as Content-Length header (must be number)
|
||||
return res.stream(readStream, len)
|
||||
return res.stream(readStream, length)
|
||||
}
|
||||
|
||||
get handler () {
|
||||
|
@ -62,29 +62,16 @@ class ServeLiveDirectory {
|
||||
this.#options = options
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on https://github.com/pillarjs/send/blob/0.18.0/index.js
|
||||
* Copyright(c) 2012 TJ Holowaychuk
|
||||
* Copyright(c) 2014-2022 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
handler (req, res, file) {
|
||||
// set content-type
|
||||
// Set Content-Type
|
||||
res.type(file.extension)
|
||||
|
||||
// set header fields
|
||||
// Set header fields
|
||||
this.#setHeaders(req, res, file)
|
||||
|
||||
// conditional GET support
|
||||
if (serveUtils.isConditionalGET(req)) {
|
||||
if (serveUtils.isPreconditionFailure(req, res)) {
|
||||
return res.status(412).end()
|
||||
}
|
||||
|
||||
if (serveUtils.isFresh(req, res)) {
|
||||
return res.status(304).end()
|
||||
}
|
||||
// Conditional GET support
|
||||
if (serveUtils.assertConditionalGET(req, res)) {
|
||||
return res.end()
|
||||
}
|
||||
|
||||
// HEAD support
|
||||
|
@ -22,7 +22,6 @@
|
||||
const chokidar = require('chokidar')
|
||||
const etag = require('etag')
|
||||
const fs = require('fs')
|
||||
const parseRange = require('range-parser')
|
||||
const serveUtils = require('./../utils/serveUtils')
|
||||
const logger = require('./../../logger')
|
||||
|
||||
@ -78,94 +77,33 @@ class ServeStaticQuick {
|
||||
this.#options = options
|
||||
}
|
||||
|
||||
/*
|
||||
* Based on https://github.com/pillarjs/send/blob/0.18.0/index.js
|
||||
* Copyright(c) 2012 TJ Holowaychuk
|
||||
* Copyright(c) 2014-2022 Douglas Christopher Wilson
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
handler (req, res, stat) {
|
||||
// ReadStream options
|
||||
let len = stat.size
|
||||
const opts = {}
|
||||
let ranges = req.headers.range
|
||||
let offset = 0
|
||||
|
||||
// set content-type
|
||||
// Set Content-Type
|
||||
res.type(req.path)
|
||||
|
||||
// set header fields
|
||||
// Set header fields
|
||||
this.#setHeaders(req, res, stat)
|
||||
|
||||
// conditional GET support
|
||||
if (serveUtils.isConditionalGET(req)) {
|
||||
if (serveUtils.isPreconditionFailure(req, res)) {
|
||||
return res.status(412).end()
|
||||
}
|
||||
|
||||
if (serveUtils.isFresh(req, res)) {
|
||||
return res.status(304).end()
|
||||
}
|
||||
// Conditional GET support
|
||||
if (serveUtils.assertConditionalGET(req, res)) {
|
||||
return res.end()
|
||||
}
|
||||
|
||||
// adjust len to start/end options
|
||||
len = Math.max(0, len - offset)
|
||||
if (opts.end !== undefined) {
|
||||
const bytes = opts.end - offset + 1
|
||||
if (len > bytes) len = bytes
|
||||
}
|
||||
|
||||
// Range support
|
||||
if (this.#options.acceptRanges && serveUtils.BYTES_RANGE_REGEXP.test(ranges)) {
|
||||
// parse
|
||||
ranges = parseRange(len, ranges, {
|
||||
combine: true
|
||||
})
|
||||
|
||||
// If-Range support
|
||||
if (!serveUtils.isRangeFresh(req, res)) {
|
||||
// range stale
|
||||
ranges = -2
|
||||
}
|
||||
|
||||
// unsatisfiable
|
||||
if (ranges === -1) {
|
||||
// Content-Range
|
||||
res.header('Content-Range', serveUtils.contentRange('bytes', len))
|
||||
|
||||
// 416 Requested Range Not Satisfiable
|
||||
return res.status(416).end()
|
||||
}
|
||||
|
||||
// valid (syntactically invalid/multiple ranges are treated as a regular response)
|
||||
if (ranges !== -2 && ranges.length === 1) {
|
||||
// Content-Range
|
||||
res.status(206)
|
||||
res.header('Content-Range', serveUtils.contentRange('bytes', len, ranges[0]))
|
||||
|
||||
// adjust for requested range
|
||||
offset += ranges[0].start
|
||||
len = ranges[0].end - ranges[0].start + 1
|
||||
}
|
||||
}
|
||||
|
||||
// set read options
|
||||
opts.start = offset
|
||||
opts.end = Math.max(offset, offset + len - 1)
|
||||
// ReadStream options with Content-Range support if required
|
||||
const { options, length } = serveUtils.buildReadStreamOptions(req, res, stat, this.#options.acceptRanges)
|
||||
|
||||
// HEAD support
|
||||
if (req.method === 'HEAD') {
|
||||
// If HEAD, also set Content-Length (must be string)
|
||||
res.header('Content-Length', String(len))
|
||||
res.header('Content-Length', String(length))
|
||||
return res.end()
|
||||
}
|
||||
|
||||
if (len === 0) {
|
||||
if (length === 0) {
|
||||
res.end()
|
||||
}
|
||||
|
||||
return this.#stream(req, res, stat, opts, len)
|
||||
return this.#stream(req, res, stat, options, length)
|
||||
}
|
||||
|
||||
// Returns a promise which resolves to true once ServeStaticQuick is ready
|
||||
@ -174,7 +112,9 @@ class ServeStaticQuick {
|
||||
if (this.#readyPromise === true) return Promise.resolve(true)
|
||||
|
||||
// Create a promise if one does not exist for ready event
|
||||
if (this.#readyPromise === undefined) { this.#readyPromise = new Promise((resolve) => (this.#readyResolve = resolve)) }
|
||||
if (this.#readyPromise === undefined) {
|
||||
this.#readyPromise = new Promise((resolve) => (this.#readyResolve = resolve))
|
||||
}
|
||||
|
||||
return this.#readyPromise
|
||||
}
|
||||
@ -255,9 +195,9 @@ class ServeStaticQuick {
|
||||
}
|
||||
}
|
||||
|
||||
#stream (req, res, stat, opts, len) {
|
||||
#stream (req, res, stat, options, length) {
|
||||
const fullPath = this.directory + req.path
|
||||
const readStream = fs.createReadStream(fullPath, opts)
|
||||
const readStream = fs.createReadStream(fullPath, options)
|
||||
|
||||
readStream.on('error', error => {
|
||||
readStream.destroy()
|
||||
@ -265,7 +205,7 @@ class ServeStaticQuick {
|
||||
})
|
||||
|
||||
// 2nd param will be set as Content-Length header (must be number)
|
||||
return res.stream(readStream, len)
|
||||
return res.stream(readStream, length)
|
||||
}
|
||||
|
||||
get middleware () {
|
||||
|
@ -1,4 +1,5 @@
|
||||
const fresh = require('fresh')
|
||||
const parseRange = require('range-parser')
|
||||
|
||||
const self = {
|
||||
BYTES_RANGE_REGEXP: /^ *bytes=/
|
||||
@ -116,4 +117,75 @@ self.parseTokenList = str => {
|
||||
return list
|
||||
}
|
||||
|
||||
self.assertConditionalGET = (req, res) => {
|
||||
if (self.isConditionalGET(req)) {
|
||||
if (self.isPreconditionFailure(req, res)) {
|
||||
res.status(412)
|
||||
return true
|
||||
}
|
||||
|
||||
if (self.isFresh(req, res)) {
|
||||
res.status(304)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.buildReadStreamOptions = (req, res, stat, acceptRanges) => {
|
||||
// ReadStream options
|
||||
let length = stat.size
|
||||
const options = {}
|
||||
let ranges = req.headers.range
|
||||
let offset = 0
|
||||
|
||||
// Adjust len to start/end options
|
||||
length = Math.max(0, length - offset)
|
||||
if (options.end !== undefined) {
|
||||
const bytes = options.end - offset + 1
|
||||
if (length > bytes) {
|
||||
length = bytes
|
||||
}
|
||||
}
|
||||
|
||||
// Range support
|
||||
if (acceptRanges && self.BYTES_RANGE_REGEXP.test(ranges)) {
|
||||
// Parse
|
||||
ranges = parseRange(length, ranges, {
|
||||
combine: true
|
||||
})
|
||||
|
||||
// If-Range support
|
||||
if (!self.isRangeFresh(req, res)) {
|
||||
// Stale
|
||||
ranges = -2
|
||||
}
|
||||
|
||||
// Unsatisfiable
|
||||
if (ranges === -1) {
|
||||
// Content-Range
|
||||
res.header('Content-Range', self.contentRange('bytes', length))
|
||||
|
||||
// 416 Requested Range Not Satisfiable
|
||||
return res.status(416).end()
|
||||
}
|
||||
|
||||
// Valid (syntactically invalid/multiple ranges are treated as a regular response)
|
||||
if (ranges !== -2 && ranges.length === 1) {
|
||||
// Content-Range
|
||||
res.status(206)
|
||||
res.header('Content-Range', self.contentRange('bytes', length, ranges[0]))
|
||||
|
||||
// Adjust for requested range
|
||||
offset += ranges[0].start
|
||||
length = ranges[0].end - ranges[0].start + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Set read options
|
||||
options.start = offset
|
||||
options.end = Math.max(offset, offset + length - 1)
|
||||
|
||||
return { options, length }
|
||||
}
|
||||
|
||||
module.exports = self
|
||||
|
Loading…
Reference in New Issue
Block a user