diff --git a/package.json b/package.json index 1658c87..cfd82e5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@types/feather-icons": "^4.7.0", "@types/formidable": "^1.0.31", "@types/jest": "^26.0.4", + "@types/mime": "^2.0.3", "@types/mysql": "^2.15.15", "@types/node": "^14.6.3", "@types/nodemailer": "^6.4.0", @@ -60,6 +61,8 @@ "dependencies": { "config": "^3.3.1", "express": "^4.17.1", + "mime": "^2.4.6", + "send-ranges": "^4.0.0", "swaf": "^0.22.5" } } diff --git a/src/controllers/GiteaRepoLatestReleaseController.ts b/src/controllers/GiteaRepoLatestReleaseController.ts index 467ee99..88d08c7 100644 --- a/src/controllers/GiteaRepoLatestReleaseController.ts +++ b/src/controllers/GiteaRepoLatestReleaseController.ts @@ -7,6 +7,8 @@ import {NotFoundHttpError, ServiceUnavailableHttpError} from "swaf/HttpError"; import * as fs from "fs"; import {promisify} from "util"; import path from "path"; +import sendRanges, {SendRangeGetStreamFn} from "send-ranges"; +import mime from "mime"; export const ASSETS_BASE_DIR = config.get('assets_base_dir'); @@ -110,8 +112,25 @@ export default class GiteaRepoLatestReleaseController extends Controller { await promisify(fs.rename)(tmpAssetPath, assetPath); } - // Respond - return res.download(assetPath, downloadProperties.asset.name); + log.debug('Download', assetPath, downloadProperties.asset.name); + + sendRanges(async _ => { + const filePath = assetPath; + const getStream: SendRangeGetStreamFn = range => fs.createReadStream(filePath, range); + const type = mime.getType(downloadProperties.asset.name) || 'application/json'; + const stats = await promisify(fs.stat)(filePath); + + return {getStream, type, size: stats.size}; + }, { + maxRanges: 1024, + })(req, res, (err: unknown) => { + if (err) return next(err); + + log.info('Fallback to express download.'); + + // Respond + return res.download(assetPath, downloadProperties.asset.name); + }); } } diff --git a/src/types/send-ranges.d.ts b/src/types/send-ranges.d.ts new file mode 100644 index 0000000..c918045 --- /dev/null +++ b/src/types/send-ranges.d.ts @@ -0,0 +1,24 @@ +declare module 'send-ranges' { + import {NextFunction, Request, RequestHandler} from "express"; + import {ReadStream} from "fs"; + export default function ( + fetchStream: ((req: Request) => Promise), + options: SendRangeOptions = {}, + ): RequestHandler; + + export type SendRangeOptions = { + beforeSend?: (info, next: NextFunction) => void, + maxRanges?: number, + }; + + export type SendRangeParams = { + getStream: SendRangeGetStreamFn; + type: string; + size: number; + }; + + export type SendRangeGetStreamFn = (range: { + start?: number; + end?: number; + }) => ReadStream; +} diff --git a/yarn.lock b/yarn.lock index 319cbaa..7a83556 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1281,7 +1281,7 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== -"@types/mime@*": +"@types/mime@*", "@types/mime@^2.0.3": version "2.0.3" resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== @@ -2212,6 +2212,14 @@ buffer@^5.2.1: base64-js "^1.3.1" ieee754 "^1.1.13" +byte-range-stream@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/byte-range-stream/-/byte-range-stream-2.0.1.tgz#bf62271ddd07c2dad3bf9feee5f1f3ff1a96938a" + integrity sha512-qifcJeL6kLv7GfJk/2h27/3VmU046AFrt6PWJ9hxrvP9A2IHHEGIC9cFvgEMRKIZjkd5Z56kKukQ7nzIBp5cuQ== + dependencies: + combined-stream "^1.0.5" + range-parser "^1.2.0" + bytes@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -2547,7 +2555,7 @@ colors@^1.1.2: resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -7432,7 +7440,7 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -range-parser@~1.2.1: +range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== @@ -7974,6 +7982,15 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +send-ranges@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/send-ranges/-/send-ranges-4.0.0.tgz#b5fd5e8f324d31ad48eef3c2e7a9fdca6c973197" + integrity sha512-BwhMXcHIwxCAo8P7RHbLz1PlASAvTiIAL9cV8oZxBATa8WZTJy1cZn4RBlA62pJZly6L/x+97baWhKhsp0Uh0Q== + dependencies: + byte-range-stream "^2.0.1" + pump "^3.0.0" + range-parser "^1.2.1" + send@0.17.1: version "0.17.1" resolved "https://registry.npmjs.org/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"