使用Express从NodeJS服务器下载文件

449

我该如何从我的服务器下载文件到访问nodeJS服务器页面的计算机上?

我正在使用ExpressJS,我已经尝试过以下方法:

app.get('/download', function(req, res){

  var file = fs.readFileSync(__dirname + '/upload-folder/dramaticpenguin.MOV', 'binary');

  res.setHeader('Content-Length', file.length);
  res.write(file, 'binary');
  res.end();
});

但我无法获取文件名和文件类型(或扩展名)。有人可以帮助我吗?


13
仅供参考。如果要在生产环境中使用,最好在nginx后面使用node.js,并让nginx处理静态内容。显然,这样更适合处理它们。 - Munim
7个回答

803

更新

Express 提供了一个帮助函数,以使生活更轻松。

app.get('/download', function(req, res){
  const file = `${__dirname}/upload-folder/dramaticpenguin.MOV`;
  res.download(file); // Set disposition and send it.
});

旧回答

就您的浏览器而言,该文件的名称只是“download”,因此您需要使用另一个HTTP标题来提供更多信息。

res.setHeader('Content-disposition', 'attachment; filename=dramaticpenguin.MOV');
你可能还希望发送这样的MIME类型:
res.setHeader('Content-type', 'video/quicktime');

如果您想要更深入的了解,这里是更详细的内容。

var path = require('path');
var mime = require('mime');
var fs = require('fs');

app.get('/download', function(req, res){

  var file = __dirname + '/upload-folder/dramaticpenguin.MOV';

  var filename = path.basename(file);
  var mimetype = mime.getType(file);

  res.setHeader('Content-disposition', 'attachment; filename=' + filename);
  res.setHeader('Content-type', mimetype);

  var filestream = fs.createReadStream(file);
  filestream.pipe(res);
});

您可以自定义标头的值。在此示例中,我正在使用一个mime-type库-node-mime,以检查文件的mime-type。

另一个需要注意的重要事项是,我已更改您的代码以使用readStream。这是更好的方法,因为不建议使用任何名称中带有“Sync”的方法,因为Node应该是异步的。


4
谢谢。是否可以从fs.readFileSync获取这些信息?在本例中,我正在使用静态文件,但是我将为任何文件使用此下载API,并传递其名称。 - Thiago Miranda de Oliveira
设置输出文件名可以使用res.setHeader('Content-disposition', 'attachment; filename=' + filename);,谢谢! - Capy
7
Express 4.x使用.set()代替.setHeader()。顺便说一下。 - Dana Woodman
@loganfsmyth 我刚刚使用这段代码在客户端获取docx文件。我正在使用Angular5。我通过HTTP GET请求获取docx文件。但是当我获取文件时,浏览器会抛出错误。语法错误:JSON位置0处的未预期标记P。 - rohitwtbs
@rohitwtbs,这听起来像是你把文件当作JSON处理了,但实际上它并不是JSON格式的。我无法确定为什么会出现这种情况。 - loganfsmyth
显示剩余2条评论

78

使用res.download()

res.download()以“附件”的形式传输指定路径下的文件。例如:

var express = require('express');
var router = express.Router();

// ...

router.get('/:id/download', function (req, res, next) {
    var filePath = "/my/file/path/..."; // Or format the path using the `id` rest param
    var fileName = "report.pdf"; // The default name the browser will use

    res.download(filePath, fileName);    
});

8
如果数据是从HTTP请求而不是文件中传入,而我们需要以流的方式让用户下载文件,该怎么办? - summerNight
3
@summerNight - 好的,这与指定的问题不同。搜索“nodejs代理文件下载响应”以获取最佳实践。 - Jossef Harush Kadouri
@1UC1F3R616 我最终是这样解决问题的:router.get(API_PREFIX + '/file-download', function (req, res, next) { var file = process.env.FILE_DOWNLOAD_LOCATION + '/' + req.query.filename res.download(file); }); - summerNight
1
@summerNight 和 @1UC1F3R616,注意你们容易受到目录遍历攻击的威胁。例如 https://.../api?filename=../../../keys/my-secret-ssl-key.pem。为了避免这种情况,你们需要验证查询参数。 - Jossef Harush Kadouri

26

对于像pdf、Word文档等静态文件,只需在配置中使用Express的静态函数:

// Express config
var app = express().configure(function () {
    this.use('/public', express.static('public')); // <-- This right here
});

然后,只需将您的所有文件放在“public”文件夹中,例如:

/public/docs/my_word_doc.docx

然后一个普通的链接将允许用户下载它:

<a href="public/docs/my_word_doc.docx">My Word Doc</a>

3
这对于资产(尽管建议使用专用的服务代理如nginx)非常有效。但是对于需要安全访问的任何内容,推荐使用被接受的方法。一般来说,对于包含信息的文档和文件,我不建议使用公共方法。 - nembleton
1
你可以添加中间件来确保只有适当的用户可以访问文件。 - MalcolmOcean
1
...然后每个请求都会经过mEnsureAccess。当然,这意味着您需要能够根据安全文档的URL或其他内容来确定用户的访问级别。 - MalcolmOcean

19

这是我如何做到的:

  1. 创建文件
  2. 将文件发送给客户端
  3. 删除文件

代码:

let fs = require('fs');
let path = require('path');

let myController = (req, res) => {
  let filename = 'myFile.ext';
  let absPath = path.join(__dirname, '/my_files/', filename);
  let relPath = path.join('./my_files', filename); // path relative to server root

  fs.writeFile(relPath, 'File content', (err) => {
    if (err) {
      console.log(err);
    }
    res.download(absPath, (err) => {
      if (err) {
        console.log(err);
      }
      fs.unlink(relPath, (err) => {
        if (err) {
          console.log(err);
        }
        console.log('FILE [' + filename + '] REMOVED!');
      });
    });
  });
};

2
这是我在大约两天的搜索中找到的唯一适用于获取音频文件的解决方案。唯一的问题是,我不认为res.download()$.ajax调用兼容,不幸的是我不得不使用window.open("/api/get_audio_file");,请参见:https://dev59.com/K2Ij5IYBdhLWcg3whFYI#20177012 - user1063287

17
在Express 4.x中,Response对象有一个attachment()方法:
res.attachment();
// Content-Disposition: attachment

res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png

7
'use strict';

var express = require('express');
var fs = require('fs');
var compress = require('compression');
var bodyParser = require('body-parser');

var app = express();
app.set('port', 9999);
app.use(bodyParser.json({ limit: '1mb' }));
app.use(compress());

app.use(function (req, res, next) {
    req.setTimeout(3600000)
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept,' + Object.keys(req.headers).join());

    if (req.method === 'OPTIONS') {
        res.write(':)');
        res.end();
    } else next();
});

function readApp(req,res) {
  var file = req.originalUrl == "/read-android" ? "Android.apk" : "Ios.ipa",
      filePath = "/home/sony/Documents/docs/";
  fs.exists(filePath, function(exists){
      if (exists) {     
        res.writeHead(200, {
          "Content-Type": "application/octet-stream",
          "Content-Disposition" : "attachment; filename=" + file});
        fs.createReadStream(filePath + file).pipe(res);
      } else {
        res.writeHead(400, {"Content-Type": "text/plain"});
        res.end("ERROR File does NOT Exists.ipa");
      }
    });  
}

app.get('/read-android', function(req, res) {
    var u = {"originalUrl":req.originalUrl};
    readApp(u,res) 
});

app.get('/read-ios', function(req, res) {
    var u = {"originalUrl":req.originalUrl};
    readApp(u,res) 
});

var server = app.listen(app.get('port'), function() {
    console.log('Express server listening on port ' + server.address().port);
});

0

你可以使用res.sendFile()... Sample-download.xlsx应该与此函数在同一个目录中。

const downloadFile = (req,res) => {   
            var options = {
              root: path.join(__dirname),
            };
            
            let fileName = "Sample-download.xlsx";
            res.sendFile(fileName, options, function (err) {
              if (err) {
                console.log(err);
                return res.status(500).json({ success: false, message: "internal server error. please try again later" });
            
              } else {
                console.log("Sent:", fileName, "at", new Date().toString());
              }
            });
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接