0

我在 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

4

1 回答 1

1

您有两种选择:一种相对简单,另一种稍微复杂一些。你必须决定你相信哪个是哪个。;)

第一种方法

由于您依赖 express 的 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 )可以降低一些复杂性,它将所有事件处理 rig-a-ma-roll 简化为:

var request = require('superagent');

request.get(url, function(err, res){
  ...
  var zip=new AdmZip();
  zip.addFile('filename',new Buffer(res.text));
  ...
});

第二种方法

第二种方法利用了render()expressapp对象的方法,这正是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 ;)。

于 2015-02-05T00:02:55.957 回答