nodejs+koa2 大文件分片下载
const koa = require('koa')
const path = require('path')
const fs = require('fs')
const app = new koa()
app.use(async(ctx, next) => {
//request里面切出标识符字符串
let requestUrl = ctx.request.originalUrl;
//获取资源文件的绝对路径
let filePath = path.resolve(__dirname + "/files" + decodeURI(requestUrl));
let resHred = readFile(ctx.headers.range, filePath);
ctx.status = resHred.code
ctx.set(resHred.head);
let stream = fs.createReadStream(filePath, resHred.code == 200 ? {} : { start: resHred.start, end: resHred.end });
stream.pipe(ctx.res);
// //也可使用这种方式。
// stream.on('data', e => ctx.res.write(e));
// // 接收完毕
// stream.on('end', e => ctx.res.end());
ctx.respond = false;
return
})
// 全局拦截错误
app.on("error",(err,ctx)=>{
// console.log(6666,err);
})
// 启动
app.listen(process.env.PORT || 4001, () => {
console.log(`server is running at http://localhost:${process.env.PORT || 4001}`);
})
/**
* [读文件]
* @param {String} range [数据起始位]
* @param {String} filePath [文件路径]
* @param {Number} chunkSize [每次请求碎片大小 (900kb 左右)]
*/
function readFile(range, filePath, chunkSize = 499999 * 2) {
//mime类型
const mime = {
"css": "text/css",
"gif": "image/gif",
"html": "text/html",
"ico": "image/x-icon",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"mp3": "audio/mp3",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"xml": "text/xml",
"mp4": "video/mp4"
};
// 获取后缀名
let ext = path.extname(filePath);
ext = ext ? ext.slice(1) : 'unknown';
//未知的类型一律用"text/plain"类型
let contentType = mime[ext.toLowerCase()];
//建立流对象,读文件
let stat = fs.statSync(filePath)
let fileSize = stat.size;
let head = {
code: 200,
head: {
'Content-Length': fileSize,
'content-type': contentType,
}
};
if (range) {
// 大文件分片
let parts = range.replace(/bytes=/, "").split("-");
let start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : start + chunkSize;
end = end > fileSize - 1 ? fileSize - 1 : end;
chunkSize = (end - start) + 1;
head = {
code: 206,
filePath,
start,
end,
head: {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'content-type': contentType,
'Content-Length': chunkSize,
'Accept-Ranges': 'bytes'
}
}
}
return head;
}
注意的是使用kECONNRESET 或 Error: writeEoa 默认会报错 Error: read CONNRESET 报错信息。这找了大半天也没找着原因这里使用了全局的错误拦截去隐藏了
原生写法比较简单
"use strict";
//加载所需要的模块
let http = require('http');
let url = require('url');
let fs = require('fs');
let path = require('path');
//创建服务
let httpServer = http.createServer(processRequest);
let port = 4001;
//指定一个监听的接口
httpServer.listen(port, e => {
console.log(`启动成功 端口:${port}`);
});
//响应请求的函数
function processRequest(request, response) {
//request里面切出标识符字符串
let requestUrl = request.url;
//获取资源文件的绝对路径
let filePath = path.resolve(__dirname + "/files" + decodeURI(url.parse(requestUrl).pathname));
fs.stat(filePath, (err, stats) => {
if (err) {
response.writeHead(404, { "content-type": "text/html" });
response.end("<h1>404 Not Found</h1>");
}
//没出错 并且文件存在
if (!err && stats.isFile()) {
let resHred = readFile(request.headers.range, filePath);
response.writeHead(resHred.code, resHred.head);
let stream = fs.createReadStream(filePath, resHred.code == 200 ? {} : { start: resHred.start, end: resHred.end });
// stream.pipe(response);//也可使用这种方式。
// 二进制流
stream.on('data', e => response.write(e));
// 接收完毕
stream.on('end', e => response.end());
}
});
}
/**
* [读文件]
* @param {String} range [数据起始位]
* @param {String} filePath [文件路径]
* @param {Number} chunkSize [每次请求碎片大小 (900kb 左右)]
*/
function readFile(range, filePath, chunkSize = 499999 * 2) {
//mime类型
const mime = {
"css": "text/css",
"gif": "image/gif",
"html": "text/html",
"ico": "image/x-icon",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"pdf": "application/pdf",
"png": "image/png",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tiff": "image/tiff",
"txt": "text/plain",
"mp3": "audio/mp3",
"wav": "audio/x-wav",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"xml": "text/xml",
"mp4": "video/mp4"
};
// 获取后缀名
let ext = path.extname(filePath);
ext = ext ? ext.slice(1) : 'unknown';
//未知的类型一律用"text/plain"类型
let contentType = mime[ext.toLowerCase()];
//建立流对象,读文件
let stat = fs.statSync(filePath)
let fileSize = stat.size;
let head = {
code: 200,
head: {
'Content-Length': fileSize,
'content-type': contentType,
}
};
if (range) {
// 大文件分片
let parts = range.replace(/bytes=/, "").split("-");
let start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : start + chunkSize;
end = end > fileSize - 1 ? fileSize - 1 : end;
chunkSize = (end - start) + 1;
head = {
code: 206,
filePath,
start,
end,
head: {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'content-type': contentType,
'Content-Length': chunkSize,
'Accept-Ranges': 'bytes'
}
}
}
return head;
}
先初始化项目
npm i
完成后
node koa2
浏览器打开