Change redirection to caching local copy and then serving download

i.e. this allows clients to request partial downloads with range headers
This commit is contained in:
Alice Gaudon 2020-11-20 13:21:56 +01:00
parent 92c1a2ac07
commit 20d276cd10
2 changed files with 72 additions and 5 deletions

View File

@ -11,4 +11,5 @@
view: { view: {
cache: false, cache: false,
}, },
assets_base_dir: 'downloads',
} }

View File

@ -4,6 +4,11 @@ import * as https from "https";
import config from "config"; import config from "config";
import {log} from "swaf/Logger"; import {log} from "swaf/Logger";
import {NotFoundHttpError} from "swaf/HttpError"; import {NotFoundHttpError} from "swaf/HttpError";
import * as fs from "fs";
import {promisify} from "util";
import path from "path";
export const ASSETS_BASE_DIR = config.get<string>('assets_base_dir');
export default class GiteaRepoLatestReleaseController extends Controller { export default class GiteaRepoLatestReleaseController extends Controller {
public routes(): void { public routes(): void {
@ -15,7 +20,7 @@ export default class GiteaRepoLatestReleaseController extends Controller {
const {owner, name, file} = req.params; const {owner, name, file} = req.params;
if (!owner || !name) return next(); if (!owner || !name) return next();
https.get(`${config.get('gitea_instance_url')}/api/v1/repos/${owner}/${name}/releases`, { const httpRequest = https.get(`${config.get('gitea_instance_url')}/api/v1/repos/${owner}/${name}/releases`, {
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
}, },
@ -24,7 +29,7 @@ export default class GiteaRepoLatestReleaseController extends Controller {
r.on('data', c => { r.on('data', c => {
data += c; data += c;
}); });
r.on('end', () => { r.on('end', async () => {
try { try {
const releases = JSON.parse(data); const releases = JSON.parse(data);
@ -32,8 +37,19 @@ export default class GiteaRepoLatestReleaseController extends Controller {
for (const release of releases) { for (const release of releases) {
for (const asset of release.assets) { for (const asset of release.assets) {
if (asset.name === file) { if (asset.name === file) {
log.debug('Redirect to', asset.browser_download_url); log.debug('Download', asset.browser_download_url);
return res.redirect(302, asset.browser_download_url); return await this.download(req, res, next, {
repo: {
owner: owner,
name: name,
},
asset: {
id: asset.id,
name: asset.name,
url: asset.browser_download_url,
size: asset.size,
},
});
} }
} }
} }
@ -50,8 +66,58 @@ export default class GiteaRepoLatestReleaseController extends Controller {
return next(e); return next(e);
} }
}); });
}).on('error', err => { });
httpRequest.on('error', err => {
log.error(err); log.error(err);
}); });
httpRequest.end();
}
protected async download(
req: Request,
res: Response,
next: NextFunction,
downloadProperties: DownloadProperties,
): Promise<void> {
// Make base dir
if (!await promisify(fs.exists)(ASSETS_BASE_DIR)) {
await promisify(fs.mkdir)(ASSETS_BASE_DIR);
}
const assetPath = path.resolve(ASSETS_BASE_DIR, '' + downloadProperties.asset.id);
// Download asset if it doesn't exist
if (!await promisify(fs.exists)(assetPath)) {
const file = fs.createWriteStream(assetPath);
await new Promise((resolve, reject) => {
const httpRequest = https.get(downloadProperties.asset.url, res => {
res.on('end', () => {
resolve();
});
res.pipe(file);
});
httpRequest.on('error', () => {
reject();
});
httpRequest.end();
});
file.close();
}
// Respond
return res.download(assetPath, downloadProperties.asset.name);
} }
} }
export type DownloadProperties = {
repo: {
owner: string;
name: string;
};
asset: {
id: string,
name: string;
url: string;
size: number;
};
};