node.js大文件的下载以及断点续传
发布于 12 年前 作者 EchoFUN 54035 次浏览 最后一次编辑是 8 年前

断点续传其实不是什么新鲜的功能了。
简单的做个扫盲,其基本原理就是,在文件的下载断开以后。客户端继续向服务器端请求的时候,http请求的头文件中会多了一个参数“Range”,来标示当前下载的文件所断开的位置。 用如下命令可以做个测试
wget -c -d --limit-rate=2048k -O target “http://127.0.0.1:8000”(ps. c表示让wget进行断点续传,如果不加c发送请求的头文件中是不会,d标示打印调试信息) <img src=“http://botobe.com/wp-content/uploads/2012/03/break.png” alt=“断点续传” title=“断点续传”> 接下来的下载,就是从这个断开的位置开始,服务器继续向客户端传输文件剩余的内容了。

主要的是关注两个地方,一个就是返回头文件的设置,和文件内容的读取。 新建一个叫Transfer的类,把请求的两个参数当成构造函数传进去。

function Transfer(req, resp) {
	this.req = req;
	this.resp = resp;
}

计算文件开始的位置和设置头文件的信息如下

/**
 * [@description](/user/description) 计算上次的断点信息
 * [@param](/user/param) {string} Range 请求http头文件中的断点信息,如果没有则为undefined,格式(range: bytes=232323-)
 * [@return](/user/return) {integer} startPos 开始的下载点
 */
Transfer.prototype._calStartPosition = function(Range) {
	var startPos = 0;
	if( typeof Range != 'undefined') {
		var startPosMatch = /^bytes=([0-9]+)-$/.exec(Range);
		startPos = Number(startPosMatch[1]);
	}
	return startPos;
}
/**
 * [@description](/user/description) 配置头文件
 * [@param](/user/param) {object} Config 头文件配置信息(包含了下载的起始位置和文件的大小)
 */
Transfer.prototype._configHeader = function(Config) {
	var startPos = Config.startPos, 
		fileSize = Config.fileSize,
		resp = this.resp;
	// 如果startPos为0,表示文件从0开始下载的,否则则表示是断点下载的。
	if(startPos == 0) {
		resp.setHeader('Accept-Range', 'bytes');
	} else {
		resp.setHeader('Content-Range', 'bytes ' + startPos + '-' + (fileSize - 1) + '/' + fileSize);
	}
	resp.writeHead(206, 'Partial Content', {
		'Content-Type' : 'application/octet-stream',
	});
}

在nodejs中每一次http请求的头文件,已经被他封装在了http.ServerRequest的headers对象中,用node inspector的方式进行调试,就可以很清楚的看到了http.ServerRequest和http.ServerResponse对象的结构:
<img src=“http://botobe.com/wp-content/uploads/2012/03/debug.png” alt=“断点续传” title=“断点续传”>
剩下来要做的,就是从断开的位置继续读取文件,并将其返回给客户端,可以用nodejs提供的ReadStream来实现:

/**
 * [@description](/user/description) 初始化配置信息
 * [@param](/user/param) {string} filePath
 * [@param](/user/param) {function} down 下载开始的回调函数
 */
Transfer.prototype._init = function(filePath, down) {
	var config = {};
	var self = this;
	fs.stat(filePath, function(error, state) {
		if(error)
			throw error;

		config.fileSize = state.size;
		var range = self.req.headers.range;
		config.startPos = self._calStartPosition(range);
		self.config = config;
		self._configHeader(config);
		down();
	});
}
/**
 * [@description](/user/description) 生成大文件文档流,并发送
 * [@param](/user/param) {string} filePath 文件地址
 */
Transfer.prototype.Download = function(filePath) {
	var self = this;
	path.exists(filePath, function(exist) {
		if(exist) {
			self._init(filePath, function() {
				var config = self.config
					resp = self.resp;
				fReadStream = fs.createReadStream(filePath, {
					encoding : 'binary',
					bufferSize : 1024 * 1024,
					start : config.startPos,
					end : config.fileSize
				});
				fReadStream.on('data', function(chunk) {
					resp.write(chunk, 'binary');
				});
				fReadStream.on('end', function() {
					resp.end();
				});
			});
		} else {
			console.log('文件不存在!');
			return;
		}
	});
}

最终的可以用如下代码进行测试

var fs = require('fs')
http = require('http')
path = require('path');
var server = http.createServer(function(req, resp) {
	var transfer = new Transfer(req, resp);
	var filePath = '/Users/xukai/Downloads/love.rmvb';
	transfer.Download(filePath);
});
server.listen('8000');
7 回复

good ,但最好不要用 nodejs 来做这种 吃力不讨好的工作

谢谢,以后会用到。

请问你这个断点续传有完整代码吗?能开源不?

代码跑了以后,下载的文件没有名字、后缀名,怎么办?

在头文件中添加"Content-Diposition"就会出现文件名和后缀,如: var filename =‘test.rar’; res.setHeader(‘Content-Disposition’, ‘attachment; filename=’+encodeURIComponent(filename));

回到顶部