實(shí)現(xiàn)以下功能
- 讀取靜態(tài)文件或目錄
- MIME類(lèi)型支持
- 緩存支持/控制
- 支持gzip壓縮
- 訪問(wèn)目錄可以自動(dòng)尋找下面的index.html文件
- Range支持,斷點(diǎn)續(xù)傳
- 圖片防盜鏈
- 后臺(tái)運(yùn)行
基本功能讀取靜態(tài)文件
此功能分為兩部分:
* 1、返回文件
調(diào)用fs建立可讀流讀取文件返回客戶(hù)端
fs.createReadStream(filepath).pipe(res);
* 2、返回目錄
因?yàn)槭且祷禺?dāng)前請(qǐng)求路徑下的所有目錄,并且支持點(diǎn)擊進(jìn)入下一級(jí),所有返回文件為html
采用handlebars模板引擎編輯模板
// 處理模板函數(shù)
let list = function () {
let template = fs.readFileSync(listTemplatePath, 'utf8');
return handlebars.compile(template);
}
// template 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
</head>
<body>
<ul>
{{#each files}}
<li>
<a href={{url}}>{{name}}</a>
</li>
{{/each}}
</ul>
</body>
</html>
// 讀取當(dāng)前路徑下的所有目錄
let { promisify, inspect } = util;
// 轉(zhuǎn)變fs.readdir 異步方法返回promise
let readdirPromise = promisify(fs.readdir);
// 返回filepath下所有目錄
let files = await readdirPromise(filepath)
// 處理files為我們想要的數(shù)據(jù)
files = files.map(file => ({
name: file,
url: path.join(pathname, file),
}))
// 最終返回客戶(hù)端的html
// list
let html = this.list({
title: pathname,
files,
});
MIME類(lèi)型支持
res.setHeader('Content-Type', mime.getType(filepath));
緩存支持/控制
toCache (req, res, filepath, stat) {
// 強(qiáng)制緩存
res.setHeader('Cache-Control', 'private,max-age=60'); // http 1.1
// private 客戶(hù)端可以緩存
// public 客戶(hù)端和代理服務(wù)器都可以緩存
// max-age=60 緩存內(nèi)容將在60秒后失效
// no-cache 需要使用對(duì)比緩存驗(yàn)證數(shù)據(jù),強(qiáng)制向源服務(wù)器再次驗(yàn)證
// no-store 所有內(nèi)容都不會(huì)緩存,強(qiáng)制緩存和對(duì)比緩存都不會(huì)觸發(fā)
res.setHeader('Expires', new Date(Date.now() + 60 * 1000).toUTCString()); // http 1.0
// 對(duì)比緩存
// last-modify
let ifModifiedSince = req.headers['if-modified-since'];
let lastModified = stat.ctime.toGMTString();
// etag
let ifNoneMatch = req.headers['if-none-match'];
let eTag = cypto.createHash('sha1').update(stat.ctime.toGMTString() + stat.size).digest('hex');
if (ifModifiedSince || ifNoneMatch) {
if (ifNoneMatch === eTag && lastModified === ifModifiedSince) {
res.statusCode = 304;
res.end('');
return false;
}
if ((ifModifiedSince && lastModified === ifModifiedSince) || (ifNoneMatch && ifNoneMatch === eTag)) {
res.statusCode = 304;
res.end('');
return false;
}
}
res.setHeader('Last-Modified', lastModified);
res.setHeader('ETag', eTag);
return true;
}
壓縮支持
async gzip(req, res, gzipInfo) {
let encoding = req.headers['accept-encoding'];
let gzipReg = /\bgzip\b/;
let deflateReg = /\bdeflate\b/;
let type, streamInfo;
if (gzipReg.test(encoding)) {
streamInfo = gzipInfo ? await gzipPromise(gzipInfo) : zlib.createGzip();
type = 'gzip';
} else if (deflateReg.test(encoding)) {
streamInfo = gzipInfo ? await deflatePromise(gzipInfo) : zlib.createDeflate();
type = 'deflate';
}
if (type) {
res.setHeader('Content-Encoding', type);
}
return streamInfo;
}
訪問(wèn)目錄可以自動(dòng)尋找下面的index.html文件
let indexFilepath = path.join(filepath, '/', 'index.html');
try {
let statIndex = await statPromise(indexFilepath);
if (statIndex) {
// 返回 inde.html文件
return this.sendFile(req, res, indexFilepath, stat);
}
} catch (e) {}
Range支持,斷點(diǎn)續(xù)傳
range (req, res, filepath, stat) {
res.setHeader('Accept-Range', 'bytes'); // 通知客戶(hù)端支持獲取部分資源
let range = req.headers['range']; // Range: bytes=0-xxx
let start = 0, end = stat.size;
if (range) {
let result = range.match(/bytes=(\d*)-(\d*)/);
start = isNaN(result[1]) ? result[1] : start;
end = isNaN(result[2]) ? result[2] : end;
}
return fs.createReadStream(filepath, {
start,
end,
})
}
圖片防盜鏈
notSteal (req, res) {
console.log(path.join(process.cwd(), 'imgs/load.png'))
let refer = req.headers['referer'] || req.headers['refer'];
//如果說(shuō)有refer的話(huà),則表示是從HTML頁(yè)面中引用過(guò)來(lái)的
if (refer) {
let { host } = Url.parse(refer);
if (host !== hostDomain) {
// 返回默認(rèn)圖
return fs.createReadStream(path.join(process.cwd(), 'imgs/load.png'));
}
}
}
開(kāi)啟子進(jìn)程運(yùn)行
let { spawn } = require('child_process');
let fs = require('fs');
let serverPath = '../src/child.js';
let child = spawn('node', [serverPath], {
detached: true,
stdio: ['ignore', process.stdout, 'ignore']
})
child.unref();
- 功能尚不完善,之后會(huì)慢慢豐富!
- 更多細(xì)節(jié)源碼請(qǐng)查看我的GitHub sevenStatic
參考