Node.js 中实现HTTP文件下载

 2016年06月11日    1250     声明


HTTP实现文件下载时,只要在服务器设置好相关响应头,并使用二进制传输文件数据即可,而客户端(浏览器)会根据响应头接收文件数据。而在Node.js中,设置好响应头后,读取文件流,再使用.pipe()方法将流转接到响应对象Response就可以实现一个简单的文件下载服务器。

  1. 文件下载介绍
  2. Node.js文件下载服务器实现

1. 文件下载介绍

HTTP基于请求头和响应头实现状态交互,在得到服务器正确响应状态后,而客户端首先会解析响应头,并根据响应头来接收和展示数据(响应体)。对于文件下载来说,其实现过程如下:

  1. 客户端发起文件资源请求
  2. 服务器查找对应文件,并设置Content-TypeContent-Disposition等响应头,分别用于表示文件的MIME类型及文件描述
  3. 客户端根据服务器返回的响应头解析和接收文件数据

需要设置的响应头

设置文件下载响应头时,除了常用的HTTP响应头外,比较重要是还要设置以下两个响应头:

Content-Type: application/octet-stream
Content-Disposition: attachment; filename=MyFileName.ext

在上面的设置中,Content-Type: application/octet-stream告诉浏览器这是一个二进制文件,Content-Disposition告诉浏览器这是一个需要下载的附件并告诉浏览器默认的文件名。如果不添加Content-Disposition响应头,浏览器可能会下载或显示文件内容,不同浏览器的处理有所不同。


2. Node.js文件下载服务器实现

接下来我们基于Express 框架实现一个简单文件下载服务器,在这个服务器中主要包括两个功能:服务器文件的浏览、文件的下载。

2.1 添加路由

创建Express应用后,添加如下两个路由:

router.get('/files', function(req, res, next) {
  // 显示服务器文件 
});
router.get('/file/:fileName', function(req, res, next) {
  // 实现文件下载 
});

上面的添加的两个路由分别用于:显示服务器文件、实现文件下载。


2.2 显示服务器文件

实现服务器文件的显示,要通过fs模块读取文件目录并进行文件/目录检查等。还需要使用path模块处理文件路径。首先引入这两个模块:

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

显示服务器文件实现代码如下:

router.get('/files', function(req, res, next) {
  // 显示服务器文件 
  // 文件目录
  var filePath = path.join(__dirname, './');
  fs.readdir(filePath, function(err, results){
  	if(err) throw err;
  	if(results.length>0) {
  	  var files = [];
  	  results.forEach(function(file){
  	  	if(fs.statSync(path.join(filePath, file)).isFile()){
          files.push(file);
  	  	}
  	  })
  	  res.render('files', {files:files});
  	} else {
  	  res.end('当前目录下没有文件');
  	}
  });
});

上面代码中,读取目录后通过视图文件files.ejs显示可下载文件列表。其代码如下:

<!DOCTYPE html>
<html>
  <head>
    <title>下载文件选择</title>
  </head>
  <body>
    <h1>请选择下载文件:</h1>
    <% if(files.length>0) {%>
    <ul>
      <% files.forEach(function(file){ %>
      <li>
      	<a href="/file/<%- file %>" target="_blank"><%- file %></a>
      </li>
      <%})%>
    </ul>
    <%} else {%>
    <p>没有可下载文件…</p>
    <%}%>
  </body>
</html>


2.3 实现文件下载

实现文件下载时,可以先读取文件到一个Buffer中,再通过res.send()res.end()方法发送文件数,也可以基于流(Stream实现文件数据的发送。使用Stream实现文件下载时,可以使用fs.createReadStream()方法创建一个可读流,而响应对象Response是一个可写流。这样,只需要通过.pipe()方法将文件流转接到Response响应流中即可。

文件下载实现代码如下:

router.get('/file/:fileName', function(req, res, next) {
  // 实现文件下载 
  var fileName = req.params.fileName;
  var filePath = path.join(__dirname, fileName);
  var stats = fs.statSync(filePath); 
  if(stats.isFile()){
    res.set({
      'Content-Type': 'application/octet-stream',
      'Content-Disposition': 'attachment; filename='+fileName,
      'Content-Length': stats.size
    });
    fs.createReadStream(filePath).pipe(res);
  } else {
    res.end(404);
  }
});