登录
注册
node.js 学习社区
node.js大文件的下载以及断点续传

玖玥吾尔

2014-12-18 16:52

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

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

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

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

/**
 * @description 计算上次的断点信息
 * @param {string} Range 请求http头文件中的断点信息,如果没有则为undefined,格式(range: bytes=232323-)
 * @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 配置头文件
 * @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对象的结构:
断点续传
剩下来要做的,就是从断开的位置继续读取文件,并将其返回给客户端,可以用nodejs提供的ReadStream来实现:

/**
 * @description 初始化配置信息
 * @param {string} filePath
 * @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 生成大文件文档流,并发送
 * @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');

原文引自:http://cnodejs.org/topic/4f5b47c42373009b5c04e9cb

回复 · 5

  • @vincezheng 这个适合你

    1

  • @龙大 我也做过一版断点续传,也是参照这篇博客写的。

    可是传大文件的时候貌似有问题,会内存溢出。不知道是不是我写的有问题,后来才借用rsync进行文件传输下载

    0

  • @xc  我没试过这个功能,你是用 require('child_process').exec  去跑linux rsync 吗?还是其他模块有 rsync 的

    0

  • @龙大 恩,用的child_process。    应该不会有人写rsync的模块的吧,毕竟只是调它的命令而已。

    1

  • 最后这里不知道怎么用?

    我不需要在服务器端启动服务,只是做为客户端的下载

    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');

    0

发表回复

你可以在回复中 @ 其他人