如何让客户端下载一个非常大的动态生成的文件
How to make the client download a very large file that is genereted on the fly
我有一个导出功能,可以读取整个数据库并创建一个包含所有记录的 .xls 文件。然后将文件发送给客户端。
当然,导出全库的时候需要很多时间,请求很快就会超时错误结束。
处理这种情况的最佳解决方案是什么?
我听说过一些关于使用 Redis 创建队列的事情,但这需要两个请求:一个用于启动将生成文件的作业,第二个用于下载生成的文件。
这可以通过客户端的单个请求实现吗?
你不必发送整个文件一次,你可以分块发送这个文件(例如逐行发送),只需在结束时使用 res.write(chunk)
和 res.end()
将其标记为完成。
您可以将文件信息作为流发送,发送通过 res.write(chunk)
创建的每个单独的块,或者,如果逐块发送文件不是一个选项,您必须等待对于发送任何信息之前的整个文件,您始终可以通过将超时持续时间设置为 Infinity 或您认为足够高以允许创建文件的任何值来保持连接打开。然后设置一个创建 .xls 文件的函数,并且:
1) 接受回调,一旦准备好接收数据输出作为参数,发送该数据,然后关闭连接,或者;
2) Returns 一个在数据输出就绪后解析的承诺,允许您发送解析的值并关闭连接,就像回调版本一样。
看起来像这样:
function xlsRouteHandler(req, res){
res.setTimeout(Infinity) || res.socket.setTimeout(Infinity)
//callback version
createXLSFile(...fileCreationArguments, function(finishedFile){
res.end(finishedFile)
})
//promise version
createXLSFile(...fileCreationArguments)
.then(finishedFile => res.end(finishedFile))
}
如果您仍然担心超时,您始终可以设置一个间隔计时器来发送偶尔的 res.write()
消息以防止服务器连接超时,然后在最终文件内容完成后取消该间隔已准备好发送。
Excel 导出:
使用Streams。以下是可能会做什么的粗略想法:
使用 exceljs module. Because it has a streaming API 来解决这个问题。
var Excel = require('exceljs')
因为我们正在尝试启动下载。将适当的 headers 写入响应。
res.status(200);
res.setHeader('Content-disposition', 'attachment; filename=db_dump.xls');
res.setHeader('Content-type', 'application/vnd.ms-excel');
创建一个由 Streaming Excel writer 支持的工作簿。给作者的流是服务器响应。
var options = {
stream: res, // write to server response
useStyles: false,
useSharedStrings: false
};
var workbook = new Excel.stream.xlsx.WorkbookWriter(options);
至此,输出流就全部设置好了。对于输入流,更喜欢将查询 results/cursor 作为流提供的数据库 driver。
定义一个将 1 table 转储到 1 个工作表的异步函数。
var tableToSheet = function (name, done) {
var str = dbDriver.query('SELECT * FROM ' + name).stream();
var sheet = workbook.addWorksheet(name);
str.on('data', function (d) {
sheet.addRow(d).commit(); // format object if required
});
str.on('end', function () {
sheet.commit();
done();
});
str.on('error', function (err) {
done(err);
});
}
现在,让我们导出一些数据库 tables,使用 async module's mapSeries:
async.mapSeries(['cars','planes','trucks'],tableToSheet,function(err){
if(err){
// log error
}
res.end();
})
CSV 导出:
对于单个 table/collection 模块的 CSV 导出,可以使用 fast-csv:
// response headers as usual
res.status(200);
res.setHeader('Content-disposition', 'attachment; filename=mytable_dump.csv');
res.setHeader('Content-type', 'text/csv');
// create csv stream
var csv = require('fast-csv');
var csvStr = csv.createWriteStream({headers: true});
// open database stream
var dbStr = dbDriver.query('SELECT * from mytable').stream();
// connect the streams
dbStr.pipe(csvStr).pipe(res);
您现在正在将数据从数据库流式传输到 HTTP 响应,并即时将其转换为 xls/csv 格式。无需将整个数据缓冲或存储在内存或文件中。
参考这个link使用jedis(redis java客户端)
关键是LPOPRPUSH命令
https://blog.logentries.com/2016/05/queuing-tasks-with-redis/
我有一个导出功能,可以读取整个数据库并创建一个包含所有记录的 .xls 文件。然后将文件发送给客户端。
当然,导出全库的时候需要很多时间,请求很快就会超时错误结束。
处理这种情况的最佳解决方案是什么?
我听说过一些关于使用 Redis 创建队列的事情,但这需要两个请求:一个用于启动将生成文件的作业,第二个用于下载生成的文件。
这可以通过客户端的单个请求实现吗?
你不必发送整个文件一次,你可以分块发送这个文件(例如逐行发送),只需在结束时使用 res.write(chunk)
和 res.end()
将其标记为完成。
您可以将文件信息作为流发送,发送通过 res.write(chunk)
创建的每个单独的块,或者,如果逐块发送文件不是一个选项,您必须等待对于发送任何信息之前的整个文件,您始终可以通过将超时持续时间设置为 Infinity 或您认为足够高以允许创建文件的任何值来保持连接打开。然后设置一个创建 .xls 文件的函数,并且:
1) 接受回调,一旦准备好接收数据输出作为参数,发送该数据,然后关闭连接,或者;
2) Returns 一个在数据输出就绪后解析的承诺,允许您发送解析的值并关闭连接,就像回调版本一样。
看起来像这样:
function xlsRouteHandler(req, res){
res.setTimeout(Infinity) || res.socket.setTimeout(Infinity)
//callback version
createXLSFile(...fileCreationArguments, function(finishedFile){
res.end(finishedFile)
})
//promise version
createXLSFile(...fileCreationArguments)
.then(finishedFile => res.end(finishedFile))
}
如果您仍然担心超时,您始终可以设置一个间隔计时器来发送偶尔的 res.write()
消息以防止服务器连接超时,然后在最终文件内容完成后取消该间隔已准备好发送。
Excel 导出:
使用Streams。以下是可能会做什么的粗略想法:
使用 exceljs module. Because it has a streaming API 来解决这个问题。
var Excel = require('exceljs')
因为我们正在尝试启动下载。将适当的 headers 写入响应。
res.status(200); res.setHeader('Content-disposition', 'attachment; filename=db_dump.xls'); res.setHeader('Content-type', 'application/vnd.ms-excel');
创建一个由 Streaming Excel writer 支持的工作簿。给作者的流是服务器响应。
var options = { stream: res, // write to server response useStyles: false, useSharedStrings: false }; var workbook = new Excel.stream.xlsx.WorkbookWriter(options);
至此,输出流就全部设置好了。对于输入流,更喜欢将查询 results/cursor 作为流提供的数据库 driver。
定义一个将 1 table 转储到 1 个工作表的异步函数。
var tableToSheet = function (name, done) { var str = dbDriver.query('SELECT * FROM ' + name).stream(); var sheet = workbook.addWorksheet(name); str.on('data', function (d) { sheet.addRow(d).commit(); // format object if required }); str.on('end', function () { sheet.commit(); done(); }); str.on('error', function (err) { done(err); }); }
现在,让我们导出一些数据库 tables,使用 async module's mapSeries:
async.mapSeries(['cars','planes','trucks'],tableToSheet,function(err){ if(err){ // log error } res.end(); })
CSV 导出:
对于单个 table/collection 模块的 CSV 导出,可以使用 fast-csv:
// response headers as usual
res.status(200);
res.setHeader('Content-disposition', 'attachment; filename=mytable_dump.csv');
res.setHeader('Content-type', 'text/csv');
// create csv stream
var csv = require('fast-csv');
var csvStr = csv.createWriteStream({headers: true});
// open database stream
var dbStr = dbDriver.query('SELECT * from mytable').stream();
// connect the streams
dbStr.pipe(csvStr).pipe(res);
您现在正在将数据从数据库流式传输到 HTTP 响应,并即时将其转换为 xls/csv 格式。无需将整个数据缓冲或存储在内存或文件中。
参考这个link使用jedis(redis java客户端) 关键是LPOPRPUSH命令
https://blog.logentries.com/2016/05/queuing-tasks-with-redis/