断点续传其实不是什么新鲜的功能了。
简单的做个扫盲,其基本原理就是,在文件的下载断开以后。客户端继续向服务器端请求的时候,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');
good ,但最好不要用 nodejs
来做这种 吃力不讨好的工作
谢谢,以后会用到。
请问你这个断点续传有完整代码吗?能开源不?
@jankuo 为什么
代码跑了以后,下载的文件没有名字、后缀名,怎么办?
在头文件中添加"Content-Diposition"就会出现文件名和后缀,如: var filename =‘test.rar’; res.setHeader(‘Content-Disposition’, ‘attachment; filename=’+encodeURIComponent(filename));