如何将ExpressJS res.render的输出包含在ZIP文件中?

时间:2015-02-04 20:48:01

标签: node.js express adm-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 });
        }
    );
},

此外,我想创建一个方法,在ZIP存档中包含生成的HTML页面以及一些静态资源。

这是我目前的代码:

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 个答案:

答案 0 :(得分: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);
    });
  );
}

这是解决问题的最佳方法,即使它看起来很复杂。使用更好的方法可以减少一些复杂性 像superagent这样的HTTP客户端库将所有事件处理工具简化为简单:

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用于呈现视图的底层机制,它可以“耦合”#34;您的zip路由到Express引擎的方式是,如果Express API将来发生变化,您需要对服务器代码进行两次更改(以正确处理html路由和zip路由),而不是只使用前一个解决方案。

就个人而言,我赞成第一个解决方案,因为它更清洁(在我看来)并且更加独立于意外的变化。但正如他们所说YMMV;)。