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: {
cache: false,
},
assets_base_dir: 'downloads',
}

View File

@ -4,6 +4,11 @@ import * as https from "https";
import config from "config";
import {log} from "swaf/Logger";
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 {
public routes(): void {
@ -15,7 +20,7 @@ export default class GiteaRepoLatestReleaseController extends Controller {
const {owner, name, file} = req.params;
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: {
'Accept': 'application/json',
},
@ -24,7 +29,7 @@ export default class GiteaRepoLatestReleaseController extends Controller {
r.on('data', c => {
data += c;
});
r.on('end', () => {
r.on('end', async () => {
try {
const releases = JSON.parse(data);
@ -32,8 +37,19 @@ export default class GiteaRepoLatestReleaseController extends Controller {
for (const release of releases) {
for (const asset of release.assets) {
if (asset.name === file) {
log.debug('Redirect to', asset.browser_download_url);
return res.redirect(302, asset.browser_download_url);
log.debug('Download', 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);
}
});
}).on('error', err => {
});
httpRequest.on('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;
};
};