Как включить вывод из 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;).

Другие вопросы по тегам