refactor: serve handlers/middlewares

moved shared codes into serveUtils to reduce complexity
This commit is contained in:
Bobby Wibowo 2022-07-31 16:34:06 +07:00
parent 2b2a7c407d
commit 0598a63989
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
4 changed files with 112 additions and 173 deletions

View File

@ -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 () {

View File

@ -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

View File

@ -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 () {

View File

@ -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