【问题标题】:Node/Express Generate a one time route / link / download?Node/Express 生成一次性路由/链接/下载?
【发布时间】:2020-08-28 15:58:02
【问题描述】:

如何在 nodeJS 或 Express 中创建一次性下载链接?

我正在尝试找到最简单的方法来完成此任务。到目前为止,我的想法是:

使用 fs 流读取然后删除文件 或者 以某种方式生成一个链接/路由,一旦单击下载按钮就会被删除

这些实现是否可行? 有没有更简单的方法?

任何帮助或示例代码将不胜感激!

-谢谢

【问题讨论】:

    标签: javascript node.js express download


    【解决方案1】:

    检查这个简单的实现:

    您将下载信息存储在一个文件中。文件名是下载会话 ID。文件内容是要下载的文件的真实路径。

    使用这三个函数来管理下载会话的生命周期:

    var fs     = require('fs');
    var crypto = require('crypto');
    var path   = require('path');
    
    // Path where we store the download sessions
    const DL_SESSION_FOLDER = '/var/download_sessions';
    
    /* Creates a download session */
    function createDownload(filePath, callback) {
      // Check the existence of DL_SESSION_FOLDER
      if (!fs.existsSync(DL_SESSION_FOLDER)) return callback(new Error('Session directory does not exist'));
    
      // Check the existence of the file
      if (!fs.existsSync(filePath)) return callback(new Error('File doest not exist'));
    
      // Generate the download sid (session id)
      var downloadSid = crypto.createHash('md5').update(Math.random().toString()).digest('hex');
    
      // Generate the download session filename
      var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
    
      // Write the link of the file to the download session file
      fs.writeFile(dlSessionFileName, filePath, function(err) {
        if (err) return callback(err);
    
        // If succeeded, return the new download sid
        callback(null, downloadSid);
      });
    }
    
    /* Gets the download file path related to a download sid */
    function getDownloadFilePath(downloadSid, callback) {
      // Get the download session file name
      var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
    
      // Check if the download session exists
      if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));
    
      // Get the file path
      fs.readFile(dlSessionFileName, function(err, data) {
        if (err) return callback(err);
    
        // Return the file path
        callback(null, data);
      });
    }
    
    /* Deletes a download session */
    function deleteDownload(downloadSid, callback) {
      // Get the download session file name
      var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');
    
      // Check if the download session exists
      if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));
    
      // Delete the download session
      fs.unlink(dlSessionFileName, function(err) {
        if (err) return callback(err);
    
        // Return success (no error)
        callback();
      });
    }
    

    使用createDownload() 随时随地创建下载会话。它返回下载 sid,然后您可以使用它来构建您的下载 URL,例如:http://your.server.com/download?sid=<RETURNED SID>

    最后,您可以在 /download 路由中添加一个简单的处理程序:

    app.get('/download', function(req, res, next) {
      // Get the download sid
      var downloadSid = req.query.sid;
    
      // Get the download file path
      getDownloadFilePath(downloadSid, function(err, path) {
        if (err) return res.end('Error');
    
        // Read and send the file here...
    
        // Finally, delete the download session to invalidate the link
        deleteDownload(downloadSid, function(err) {
          // ...
        });
      });
    });
    

    使用此方法,您不必创建/移动/删除大的下载文件,这可能会导致响应缓慢和不必要的资源消耗。

    【讨论】:

      【解决方案2】:

      您可以从 app.routes 对象中删除路由。请参阅Remove route mappings in NodeJS Express 了解更多信息。

      这是我按照您的要求做的快速且未经过很好测试的方法:

       var express = require('express');
       var app = express();
      
       app.get('/download', function(req,res,next){
          res.download('./path/to/your.file');
      
          //find this route and delete it.
          for(i = 0; i < app.routes.get.length; i++){
              if(app.routes.get[i].path === '/download'){
                  app.routes.get.splice(i,1); 
              }
          }
      });
      
      app.listen(80);
      

      【讨论】:

        【解决方案3】:

        我可能会映射一条路线来管理下载,然后在下载文件时移动或删除它。这样我可以防止大量的路线兑现,或者其他两个答案中的大量小临时文件,但 YMMV.像这样的:

        // say your downloads are in /downloads
        app.get('/dl/:filename', function(req, res) {
          var fileStream = fs.createReadStream('/downloads' + req.params.filename);
          // error handler, ie. file not there...
          fileStream.on('error', function(err) {
            if(err) {
              res.status(404); // or something
              return res.end();
            }
          });
          // here you ow pipe that stream to the response, 
          fileStream.on('data', downloadHandler);
          // and here delete the file or move it to other folder or whatever, do cleanup
          fileStream.on('end', deleteFileHandler);
        }
        

        注意:这是一个可能的安全漏洞,它可能让攻击者在您的下载位置之外下载文件。 filename 参数直接传递给 fs

        【讨论】:

        • 考虑到这是在 2014 年编写的,这里可能存在安全漏洞,可能允许攻击者下载下载文件夹中的文件以外的文件。
        • 另外,这是一个简化的例子。但你是对的——这很重要。我最好至少添加一个注释。
        猜你喜欢
        • 2018-01-21
        • 2019-06-21
        • 2017-12-07
        • 2019-05-20
        • 1970-01-01
        • 1970-01-01
        • 2018-04-23
        • 2018-01-10
        • 1970-01-01
        相关资源
        最近更新 更多