You have two options: one is relatively simple and the other is a bit more complicated. You'll have to decide which you believe is which. ;)
First method
Since you are relying on express' Response.render to create your HTML from a view, you'll need to call that route on your server to retrieve the content of the page so you can include it in your zip response.
Assuming you have var http=require('http');
somewhere in this file, you can:
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);
});
);
}
This is really the best way to solve your problem, even though it might seem complex. Some of the complexity can be reduced by using a better
HTTP client library like superagent which reduce all the event handling rig-a-ma-roll to a simple:
var request = require('superagent');
request.get(url, function(err, res){
...
var zip=new AdmZip();
zip.addFile('filename',new Buffer(res.text));
...
});
Second method
The second method utilizes the render()
method of express' app
object, which is exactly what res.render()
uses to convert views into HTML.
See Express app.render() for how this function operates.
Note that this solution is the same except for the portion annotated starting at // - 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);
});
}
While this method is somewhat shorter, by utilizing the underlying mechanism that Express uses to render views, it "couples" your zip
route to the Express engine in such a way that, should the Express API change in the future, you'll need to make two changes to your server code (to properly handle the html
route and the zip
routes), rather than only one using the previous solution.
Personally, I favor the first solution as it is cleaner (in my mind) and more independent of unexpected change. But as they say YMMV ;).