Как включить вывод из ExpressJS res.render в ZIP-файл?
У меня есть встроенный метод в ExpressJS, который экспортирует документ в виде HTML-страницы:
html: function (req, res) {
Project.findOne( { req.params.project },
function (err, project) {
res.contentType('text/html');
res.render('exporting/html', { project.name });
}
);
},
Кроме того, я хотел бы создать метод, который включает эту сгенерированную HTML-страницу вместе с некоторыми статическими ресурсами в ZIP-архив.
Вот мой текущий код:
zip: function (req, res) {
Project.findOne( { req.params.project },
function (err, project) {
res.contentType('application/zip');
res.setHeader('content-disposition', 'attachment; filename=' + project.name + '.zip');
var zip = new AdmZip();
zip.addFile("readme.txt", new Buffer("This was inside the ZIP!"));
//------ Here I'd like to use zip.addFile to include the HTML output from the html method above ------
res.send(zip.toBuffer());
}
);
}
Как я могу сделать zip
метод включает вывод из html
метод?
1 ответ
У вас есть два варианта: один относительно простой, а другой немного сложнее. Вы должны решить, что, по вашему мнению, есть что.;)
Первый способ
Поскольку вы используете экспресс-ответ "Response.render" для создания своего HTML-кода из представления, вам нужно вызвать этот маршрут на вашем сервере, чтобы получить содержимое страницы, чтобы вы могли включить его в свой ZIP-ответ.
Если у вас есть var http=require('http');
где-то в этом файле вы можете:
zip: function (req, res) {
var projectId=req.params.project||'';
if(!projectId){ // make sure we have what we need!
return res.status(404).send('requires a projectId');
}
// Ok, we start by requesting our project...
Project.findOne({id:projectId},function(err, project) {
if(err) { // ALWAYS handle errors!
return res.status(500).send(err);
}
if('object'!==typeof project){ // did we get what we expected?
return res.status(404).send('failed to find project for id: '+projectId);
}
var projectName=project.name || ''; // Make no assumptions!
if(!projectName){
return res.status(500).send('whoops! project has no name!');
}
// For clarity, let's write a function to create our
// zip archive which will take:
// 1. a name for the zip file as it is sent to the requester
// 2. an array of {name:'foo.txt',content:''} objs, and
// 3. a callback which will send the result back to our
// original requester.
var createZipArchive=function(name, files, cb){
// create our zip...
var zip=new AdmZip();
// add the files to the zip
if(Array.isArray(files)){
files.forEach(function(file){
zip.addFile(file.name,new Buffer(file.content));
});
}
// pass the filename and the new zip to the callback
return cb(name, zip);
};
// And the callback that will send our response...
//
// Note that `res` as used here is the original response
// object that was handed to our `zip` route handler.
var sendResult=function(name, zip){
res.contentType('application/zip');
res.setHeader('content-disposition','attachment; filename=' + name);
return res.send(zip.toBuffer());
};
// Ok, before we formulate our response, we'll need to get the
// html content from ourselves which we can do by making
// a get request with the proper url.
//
// Assuming this server is running on localhost:80, we can
// use this url. If this is not the case, modify it as needed.
var url='http://localhost:80/html';
var httpGetRequest = http.get(url,function(getRes){
var body=''; // we'll build up the result from our request here.
// The 'data' event is fired each time the "remote" server
// returns a part of its response. Remember that these data
// can come in multiple chunks, and you do not know how many,
// so let's collect them all into our body var.
getRes.on('data',function(chunk){
body+=chunk.toString(); // make sure it's not a Buffer!
});
// The 'end' event will be fired when there are no more data
// to be read from the response so it's here we can respond
// to our original request.
getRes.on('end',function(){
var filename=projectName+'.zip',
files=[
{
name:'readme.txt',
content:'This was inside the ZIP!'
},{
name:'result.html',
content:body
}
];
// Finally, call our zip creator passing our result sender...
//
// Note that we could have both built the zip and sent the
// result here, but using the handlers we defined above
// makes the code a little cleaner and easier to understand.
//
// It might have been nicer to create handlers for all the
// functions herein defined in-line...
return createZipArchive(filename,files,sendResult);
});
}).on('error',function(err){
// This handler will be called if the http.get request fails
// in which case, we simply respond with a server error.
return res.status(500).send('could not retrieve html: '+err);
});
);
}
Это действительно лучший способ решить вашу проблему, даже если это может показаться сложным. Некоторые из сложностей могут быть уменьшены с помощью лучшей клиентской библиотеки HTTP, такой как superagent, которая сводит простую обработку всех событий к простому:
var request = require('superagent');
request.get(url, function(err, res){
...
var zip=new AdmZip();
zip.addFile('filename',new Buffer(res.text));
...
});
Второй метод
Второй метод использует render()
метод экспресс app
объект, который именно то, что res.render()
использует для преобразования видов в HTML.
См. Express app.render(), чтобы узнать, как работает эта функция.
Обратите внимание, что это решение такое же, за исключением аннотированной части, начинающейся с // - NEW CODE HERE -
,
zip: function (req, res) {
var projectId=req.params.project||'';
if(!projectId){ // make sure we have what we need!
return res.status(404).send('requires a projectId');
}
// Ok, we start by requesting our project...
Project.findOne({id:projectId},function(err, project) {
if(err) { // ALWAYS handle errors!
return res.status(500).send(err);
}
if('object'!==typeof project){ // did we get what we expected?
return res.status(404).send('failed to find project for id: '+projectId);
}
var projectName=project.name || ''; // Make no assumptions!
if(!projectName){
return res.status(500).send('whoops! project has no name!');
}
// For clarity, let's write a function to create our
// zip archive which will take:
// 1. a name for the zip file as it is sent to the requester
// 2. an array of {name:'foo.txt',content:''} objs, and
// 3. a callback which will send the result back to our
// original requester.
var createZipArchive=function(name, files, cb){
// create our zip...
var zip=new AdmZip();
// add the files to the zip
if(Array.isArray(files)){
files.forEach(function(file){
zip.addFile(file.name,new Buffer(file.content));
});
}
// pass the filename and the new zip to the callback
return cb(name, zip);
};
// And the callback that will send our response...
//
// Note that `res` as used here is the original response
// object that was handed to our `zip` route handler.
var sendResult=function(name, zip){
res.contentType('application/zip');
res.setHeader('content-disposition','attachment; filename=' + name);
return res.send(zip.toBuffer());
};
// - NEW CODE HERE -
// Render our view, build our zip and send our response...
app.render('exporting/html', { name:projectName }, function(err,html){
if(err){
return res.status(500).send('failed to render view: '+err);
}
var filename=projectName+'.zip',
files=[
{
name:'readme.txt',
content:'This was inside the ZIP!'
},{
name:'result.html',
content:html
}
];
// Finally, call our zip creator passing our result sender...
//
// Note that we could have both built the zip and sent the
// result here, but using the handlers we defined above
// makes the code a little cleaner and easier to understand.
//
// It might have been nicer to create handlers for all the
// functions herein defined in-line...
return createZipArchive(filename,files,sendResult);
});
}
Хотя этот метод несколько короче, используя базовый механизм, который Express использует для визуализации представлений, он "связывает" ваши zip
маршрутизировать к движку Express таким образом, чтобы в случае изменения Express API в будущем вам нужно было сделать два изменения в коде вашего сервера (для правильной обработки html
маршрут и zip
маршруты), а не только один, использующий предыдущее решение.
Лично я предпочитаю первое решение, так как оно более чистое (на мой взгляд) и более независимое от неожиданных изменений. Но как говорится YMMV;).