mirror of
https://github.com/BobbyWibowo/lolisafe.git
synced 2025-02-23 13:49:03 +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 contentDisposition = require('content-disposition')
|
||||||
const etag = require('etag')
|
const etag = require('etag')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const parseRange = require('range-parser')
|
|
||||||
const SimpleDataStore = require('./../utils/SimpleDataStore')
|
const SimpleDataStore = require('./../utils/SimpleDataStore')
|
||||||
const errors = require('./../errorsController')
|
const errors = require('./../errorsController')
|
||||||
const paths = require('./../pathsController')
|
const paths = require('./../pathsController')
|
||||||
@ -152,13 +151,6 @@ class ServeStatic {
|
|||||||
return stat
|
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) {
|
async #handler (req, res) {
|
||||||
if (this.#options.ignorePatterns && this.#options.ignorePatterns.some(pattern => req.path.startsWith(pattern))) {
|
if (this.#options.ignorePatterns && this.#options.ignorePatterns.some(pattern => req.path.startsWith(pattern))) {
|
||||||
return errors.handleNotFound(req, res)
|
return errors.handleNotFound(req, res)
|
||||||
@ -176,90 +168,38 @@ class ServeStatic {
|
|||||||
return errors.handleNotFound(req, res)
|
return errors.handleNotFound(req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadStream options
|
// Set Content-Type
|
||||||
let len = stat.size
|
|
||||||
const opts = {}
|
|
||||||
let ranges = req.headers.range
|
|
||||||
let offset = 0
|
|
||||||
|
|
||||||
// set content-type
|
|
||||||
res.type(req.path)
|
res.type(req.path)
|
||||||
|
|
||||||
// set header fields
|
// Set header fields
|
||||||
await this.#setHeaders(req, res, stat)
|
await this.#setHeaders(req, res, stat)
|
||||||
|
|
||||||
// conditional GET support
|
// Conditional GET support
|
||||||
if (serveUtils.isConditionalGET(req)) {
|
if (serveUtils.assertConditionalGET(req, res)) {
|
||||||
if (serveUtils.isPreconditionFailure(req, res)) {
|
return res.end()
|
||||||
return res.status(412).end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serveUtils.isFresh(req, res)) {
|
// ReadStream options with Content-Range support if required
|
||||||
return res.status(304).end()
|
const { options, length } = serveUtils.buildReadStreamOptions(req, res, stat, this.#options.acceptRanges)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// HEAD support
|
// HEAD support
|
||||||
if (req.method === 'HEAD') {
|
if (req.method === 'HEAD') {
|
||||||
// If HEAD, also set Content-Length (must be string)
|
// If HEAD, also set Content-Length (must be string)
|
||||||
res.header('Content-Length', String(len))
|
res.header('Content-Length', String(length))
|
||||||
return res.end()
|
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()
|
res.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.#stream(req, res, fullPath, opts, len)
|
return this.#stream(req, res, fullPath, options, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
async #setHeaders (req, res, stat) {
|
async #setHeaders (req, res, stat) {
|
||||||
@ -289,8 +229,8 @@ class ServeStatic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #stream (req, res, fullPath, opts, len) {
|
async #stream (req, res, fullPath, options, length) {
|
||||||
const readStream = fs.createReadStream(fullPath, opts)
|
const readStream = fs.createReadStream(fullPath, options)
|
||||||
|
|
||||||
readStream.on('error', error => {
|
readStream.on('error', error => {
|
||||||
readStream.destroy()
|
readStream.destroy()
|
||||||
@ -298,7 +238,7 @@ class ServeStatic {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 2nd param will be set as Content-Length header (must be number)
|
// 2nd param will be set as Content-Length header (must be number)
|
||||||
return res.stream(readStream, len)
|
return res.stream(readStream, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
get handler () {
|
get handler () {
|
||||||
|
@ -62,29 +62,16 @@ class ServeLiveDirectory {
|
|||||||
this.#options = options
|
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) {
|
handler (req, res, file) {
|
||||||
// set content-type
|
// Set Content-Type
|
||||||
res.type(file.extension)
|
res.type(file.extension)
|
||||||
|
|
||||||
// set header fields
|
// Set header fields
|
||||||
this.#setHeaders(req, res, file)
|
this.#setHeaders(req, res, file)
|
||||||
|
|
||||||
// conditional GET support
|
// Conditional GET support
|
||||||
if (serveUtils.isConditionalGET(req)) {
|
if (serveUtils.assertConditionalGET(req, res)) {
|
||||||
if (serveUtils.isPreconditionFailure(req, res)) {
|
return res.end()
|
||||||
return res.status(412).end()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serveUtils.isFresh(req, res)) {
|
|
||||||
return res.status(304).end()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HEAD support
|
// HEAD support
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
const chokidar = require('chokidar')
|
const chokidar = require('chokidar')
|
||||||
const etag = require('etag')
|
const etag = require('etag')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const parseRange = require('range-parser')
|
|
||||||
const serveUtils = require('./../utils/serveUtils')
|
const serveUtils = require('./../utils/serveUtils')
|
||||||
const logger = require('./../../logger')
|
const logger = require('./../../logger')
|
||||||
|
|
||||||
@ -78,94 +77,33 @@ class ServeStaticQuick {
|
|||||||
this.#options = options
|
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) {
|
handler (req, res, stat) {
|
||||||
// ReadStream options
|
// Set Content-Type
|
||||||
let len = stat.size
|
|
||||||
const opts = {}
|
|
||||||
let ranges = req.headers.range
|
|
||||||
let offset = 0
|
|
||||||
|
|
||||||
// set content-type
|
|
||||||
res.type(req.path)
|
res.type(req.path)
|
||||||
|
|
||||||
// set header fields
|
// Set header fields
|
||||||
this.#setHeaders(req, res, stat)
|
this.#setHeaders(req, res, stat)
|
||||||
|
|
||||||
// conditional GET support
|
// Conditional GET support
|
||||||
if (serveUtils.isConditionalGET(req)) {
|
if (serveUtils.assertConditionalGET(req, res)) {
|
||||||
if (serveUtils.isPreconditionFailure(req, res)) {
|
return res.end()
|
||||||
return res.status(412).end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serveUtils.isFresh(req, res)) {
|
// ReadStream options with Content-Range support if required
|
||||||
return res.status(304).end()
|
const { options, length } = serveUtils.buildReadStreamOptions(req, res, stat, this.#options.acceptRanges)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// HEAD support
|
// HEAD support
|
||||||
if (req.method === 'HEAD') {
|
if (req.method === 'HEAD') {
|
||||||
// If HEAD, also set Content-Length (must be string)
|
// If HEAD, also set Content-Length (must be string)
|
||||||
res.header('Content-Length', String(len))
|
res.header('Content-Length', String(length))
|
||||||
return res.end()
|
return res.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len === 0) {
|
if (length === 0) {
|
||||||
res.end()
|
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
|
// 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)
|
if (this.#readyPromise === true) return Promise.resolve(true)
|
||||||
|
|
||||||
// Create a promise if one does not exist for ready event
|
// 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
|
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 fullPath = this.directory + req.path
|
||||||
const readStream = fs.createReadStream(fullPath, opts)
|
const readStream = fs.createReadStream(fullPath, options)
|
||||||
|
|
||||||
readStream.on('error', error => {
|
readStream.on('error', error => {
|
||||||
readStream.destroy()
|
readStream.destroy()
|
||||||
@ -265,7 +205,7 @@ class ServeStaticQuick {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 2nd param will be set as Content-Length header (must be number)
|
// 2nd param will be set as Content-Length header (must be number)
|
||||||
return res.stream(readStream, len)
|
return res.stream(readStream, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
get middleware () {
|
get middleware () {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const fresh = require('fresh')
|
const fresh = require('fresh')
|
||||||
|
const parseRange = require('range-parser')
|
||||||
|
|
||||||
const self = {
|
const self = {
|
||||||
BYTES_RANGE_REGEXP: /^ *bytes=/
|
BYTES_RANGE_REGEXP: /^ *bytes=/
|
||||||
@ -116,4 +117,75 @@ self.parseTokenList = str => {
|
|||||||
return list
|
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
|
module.exports = self
|
||||||
|
Loading…
Reference in New Issue
Block a user