简体   繁体   English

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

[英]How to include output from ExpressJS res.render in a ZIP file?

I have a built a method in ExpressJS that exports a document as an HTML page: 我在ExpressJS中建立了一个将文档导出为HTML页面的方法:

html: function (req, res) {
    Project.findOne( { req.params.project },
        function (err, project) {
            res.contentType('text/html');
            res.render('exporting/html', { project.name });
        }
    );
},

Additionally, I'd like to create a method that includes that generated HTML page, together with some static assets, in a ZIP archive. 另外,我想创建一个方法,该方法将生成的HTML页面以及一些静态资产包含在ZIP存档中。

Here's my current code: 这是我当前的代码:

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());
        }
    );
}

How can I make the zip method include the output from the html method? 如何使zip方法包含html方法的输出?

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. 由于您是依靠express的Response.render从视图创建HTML的,因此您需要在服务器上调用该路由以检索页面的内容,以便将其包含在zip响应中。

Assuming you have var http=require('http'); 假设您有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: 通过使用更好的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));
  ...
});

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. 第二种方法利用express的app对象的render()方法,这正是res.render()用来将视图转换为HTML的方法。

See Express app.render() for how this function operates. 有关此函数的运行方式,请参见Express app.render()

Note that this solution is the same except for the portion annotated starting at // - NEW CODE HERE - . 请注意,此解决方案是相同的,除了从// - 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. 尽管此方法略短一些,但通过利用Express用于渲染视图的底层机制,它可以将zip路由“耦合”到Express引擎,以便将来Express API发生更改时,您需要对您的服务器代码进行两项更改(以正确处理html路由和zip路由),而不是仅使用先前的解决方案。

Personally, I favor the first solution as it is cleaner (in my mind) and more independent of unexpected change. 就我个人而言,我赞成第一个解决方案,因为它(在我看来)更干净并且不受意外更改的影响。 But as they say YMMV ;). 但是正如他们所说的YMMV ;)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM