Буферы в javascript кажутся ограниченными 2 ГБ для 64-битных систем.
Использование express помощника res.download
найдено здесь мой сервер вылетает при попытке загрузить файл что превышает те же 2 ГБ.
Думаю, решением будет потоковая передача файла, из того, что я обнаружил, это можно сделать, используя:
var readStream = fileSystem.createReadStream('/tmp/outputs.zip');
readStream.pipe(res);
Но когда я это сделаю, мой топор ios клиент никогда не получает ответ на запрос GET.
Я не могу использовать кнопку загрузки в этом сценарии, потому что данные, которые я пытаюсь загрузить, защищены аутентификацией JWT.
Любая помощь приветствуется.
Ниже приведен код, который я использую сейчас:
Node.js сервер:
async function getOutputs(req, res, next) {
console.log("invoked getOutputs");
const request = req.params.request;
const uuid = req.params.uuid;
const { user } = await getRequestUser(req);
s3Service.getOutputs(req, res, request, uuid, user)
.then(
body => {
if(body.code !== 202) {
res.status(body.code).json(body.data);
}
}
)
.catch(err => next(err));
}
async function getOutputs(req, res, requestId, uuid, user) {
let canceled = false;
req.on('close', function (err){
canceled = true;
});
const requestModel = await Request.findOne({where: {id: requestId}});
const m = moment.utc(requestModel.dataValues.timestamp);
m.tz('Europe/Madrid');
const filePath = 'root/' + user.emailaddress + '/' + m.format("YYYYMMDDHHmmss") + '/output/results.zip';
const s3HeadParams = {
Bucket: bucket,
Key: filePath,
};
try{
/*
* S3 Max download is 2GB at a time
* Javascript Buffers cap at 2GB
* App crashes trying to download file above 2GB
*/
const part = 2*1000*1000*1000;
const s3head = await s3.headObject(s3HeadParams).promise();
const size = s3head.ContentLength;
const ranges = size / part;
const results = [];
let totalBytes = 0;
let prevProgress = 0;
for(let i = 0; i < ranges; i++) {
const s3params = {
Bucket: bucket,
Key: filePath,
Range: "bytes=" + (i * part) + "-" + ((i+1) * part -1),
};
const s3request = s3.getObject(s3params).on('httpData', function(chunk){
if(canceled) {
s3request.abort();
}
totalBytes += chunk.length;
let progress = Math.round(totalBytes / size * 100);
if(progress > prevProgress) {
emitProgress(uuid, progress);
prevProgress = progress;
}
});
results.push(await s3request.promise());
}
fs.writeFileSync('/tmp/outputs.zip', '');
for(let i = 0; i < results.length; i++) {
fs.appendFileSync('/tmp/outputs.zip', results[i].Body);
}
res.setHeader('Content-disposition', 'attachment; filename=outputs.zip');
res.setHeader('Content-type', 'application/zip');
var readStream = fileSystem.createReadStream('/tmp/outputs.zip');
readStream.pipe(res);
return {
code: 202,
};
} catch(error) {
return {
code: 400,
data: ['Results not yet available, please try again later'],
};
}
}
React client:
async function getOutputs(request, callback, setRequesting, uuid, setProgress) {
try{
const response = await Axios.get(ADDRESS + '/s3/getOutputs/' + request + '/' + uuid, {
cancelToken: source.token,
});
var file = new Blob([toArrayBuffer(response.data.data)], {type: 'application/zip'});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(file, 'Flomics-Request-' + request + '-outputs.zip');
}
else {
var url = URL.createObjectURL(file);
const link = document.createElement('a');
link.href = url;
link.download = 'Request-' + request + '-outputs.zip';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
setRequesting(false);
setProgress(0);
}
} catch(error) {
if(Axios.isCancel(error)) {
source = CancelToken.source();
} else if(error.response && error.response.data && error.response.data[0]) {
callback('warning', error.response.data[0]);
} else {
callback('error', 'Unexpected error');
}
setRequesting(false);
setProgress(0);
}
}