From d88dd3042824db03452be720626c609bdbe331fd Mon Sep 17 00:00:00 2001 From: Hans Date: Wed, 3 Aug 2022 14:40:11 +0800 Subject: [PATCH] fix(Server): fix range-stream process --- packages/server/js/response.js | 16 +++++++++++--- packages/server/js/utils.js | 27 +++++++++++++++++++++++ packages/server/package.json | 2 +- test/cases/http-pipe-file-stream-range.js | 4 ++-- yarn.lock | 9 +------- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/server/js/response.js b/packages/server/js/response.js index fca14ce..b2b16bc 100644 --- a/packages/server/js/response.js +++ b/packages/server/js/response.js @@ -1,11 +1,10 @@ const ServerError = require('./errors') -const { buildHeaderValue } = require('./utils') +const { buildHeaderValue, rangeStream } = require('./utils') const fs = require('fs') const path = require('path') const mime = require('mime-types') const { Writable } = require('stream') const parseRange = require('range-parser') -const rangeStream = require('ranges-stream') const staticPath = path.resolve(process.cwd(), 'static') @@ -271,7 +270,7 @@ class Response extends Writable { if (this._totalSize && end <= this._totalSize) { this.status(206) .setHeader('Content-Range', `bytes ${start}-${end}/${this._totalSize}`) - this._totalSize = end - start + this._totalSize = end - start + 1 return stream.pipe(rangeStream(ranges)).pipe(this) } } @@ -338,6 +337,17 @@ class Response extends Writable { return this } + set (keyOrMap, value) { + if (typeof keyOrMap === 'object') { + for (const [key, val] of Object.entries(keyOrMap)) { + this.setHeader(key, val) + } + } else { + this.setHeader(keyOrMap, value) + } + return this + } + location (loc, code = 302) { this.setHeader('Location', loc).status(code) return this diff --git a/packages/server/js/utils.js b/packages/server/js/utils.js index efa0dc4..89f68d7 100644 --- a/packages/server/js/utils.js +++ b/packages/server/js/utils.js @@ -1,4 +1,5 @@ const CIDRMatcher = require('cidr-matcher') +const through = require('through2') const V4Prefix = Buffer.from([0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff]) @@ -68,3 +69,29 @@ exports.createCidrMatcher = (CIDRs) => .map((rangeOrName) => namedCidrs[rangeOrName] || rangeOrName) .flat() ) + +// Ref: https://github.com/finnp/ranges-stream +exports.rangeStream = (ranges) => { + let pos = 0 + let currentRange = ranges.shift() + return through(function processChunk (chunk, enc, cb) { + if (!(currentRange)) return cb() + + if (pos + chunk.length > currentRange.start) { + const bufStart = Math.max(currentRange.start - pos, 0) + const bufEnd = currentRange.end - pos + 1 + if (currentRange.end <= pos + chunk.length) { + this.push(chunk.slice(bufStart, bufEnd)) + currentRange = ranges.shift() // next Range + pos += bufEnd + return processChunk.bind(this)(chunk.slice(bufEnd), enc, cb) + } else { + // the range continues to the next chunk + this.push(bufStart > 0 ? chunk.slice(bufStart) : chunk) + } + } + + pos += chunk.length + cb() + }) +} diff --git a/packages/server/package.json b/packages/server/package.json index 2c2c02a..0d99f17 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -44,8 +44,8 @@ "multipart-formdata": "^1.1.0", "qs": "^6.10.3", "range-parser": "^1.2.1", - "ranges-stream": "^1.0.0", "replicator": "^1.0.5", + "through2": "^4.0.2", "uWebSockets.js": "https://github.com/uNetworking/uWebSockets.js#binaries" }, "gitHead": "e939a8cb4e569fc3155c95c942470ab279e9d77d" diff --git a/test/cases/http-pipe-file-stream-range.js b/test/cases/http-pipe-file-stream-range.js index 5eef3ab..1c58881 100644 --- a/test/cases/http-pipe-file-stream-range.js +++ b/test/cases/http-pipe-file-stream-range.js @@ -3,7 +3,7 @@ const axios = require('axios') module.exports = async function ({ HTTP_PORT }) { const res = await axios.get(`http://localhost:${HTTP_PORT}/stream/file`, { headers: { - Range: 'bytes=0-4' + Range: 'bytes=0-3' } }) if (res.status !== 206) { @@ -15,7 +15,7 @@ module.exports = async function ({ HTTP_PORT }) { if (Number(res.headers['content-length']) !== 4) { throw new Error(`Invalid Content-Length: ${res.headers['content-length']}`) } - if (!['bytes 0-4/10', 'bytes 0-4/11'].includes(res.headers['content-range'])) { + if (!['bytes 0-3/10', 'bytes 0-3/11'].includes(res.headers['content-range'])) { throw new Error(`Invalid Content-Range: ${res.headers['content-range']}`) } if (res.data !== 'TEST') { diff --git a/yarn.lock b/yarn.lock index bba92ee..355e6a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5469,13 +5469,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -ranges-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ranges-stream/-/ranges-stream-1.0.0.tgz#7d304c513d6a017018818a9d51e815c8645e9035" - integrity sha512-wQ2WoBcfqJuUVNE09lOHCa12ttGo0kBybgEBqoqw19sISdZh/FD7bpWuwA/5hCm7Z4rcJP5Qwaswk4YZwFkgdw== - dependencies: - through2 "^2.0.0" - raw-body@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" @@ -6158,7 +6151,7 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through2@^4.0.0: +through2@^4.0.0, through2@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==