引入依賴模塊
let config = require('./config');
let chalk = require('chalk');//粉筆模塊(命令行變色)
let http = require('http');//http模塊
let fs = require('fs');//文件模塊
let zlib = require('zlib');
let handlebars = require('handlebars');//模板引擎
let url = require('url');//url模塊解析請(qǐng)求url
let mime = require('mime');//mime模塊通過(guò)文件名稱/路徑拿到內(nèi)容類型
let path = require('path');
let {promisify,inspect} = require('util');
let stat = promisify(fs.stat);
let readdir = promisify(fs.readdir);
process.env.DEBUG = 'static:*';
let debug = require('debug')('static:app');
編譯模板函數(shù)
function list() {
//編譯模板,得到渲染方法,傳入數(shù)據(jù)
let tmpl = fs.readFileSync(path.resolve(__dirname,'src/template','list.html'),'utf8');
return handlebars.compile(tmpl);//返回一個(gè)函數(shù)
}
此函數(shù)主要用于編譯模板,返回一個(gè)文件list列表用于返回給客戶端。
start方法啟動(dòng)服務(wù)
start(){
//啟動(dòng)服務(wù)
let server = http.createServer();
server.on('request',this.request.bind(this));
server.listen(this.config.port,()=>{
let url = `${this.config.host}:${this.config.port}`;
debug(`start at ${chalk.green(url)}`);
})
}
this.config.port可以自己定義端口,debug用于在控制臺(tái)打印信息。
請(qǐng)求處理方法
async request(req,res){
//接收請(qǐng)求
let { pathname } = url.parse(req.url);
let filepath = path.join(this.config.root,pathname);
try{
let statObj = await stat(filepath);
if(statObj.isDirectory()){//判斷是否為目錄
//是目錄的話使用handlebars模板引擎渲染目錄
let files = await readdir(filepath);//字符串?dāng)?shù)組
files = files.map((file)=>{
return {
name:file,
url:path.join(pathname,file)
}
});
let html = this.list({
title:pathname,
files:files
});
res.setHeader('Content-Type','text/html');
this.sendFile(req,res,html,statObj,'html');
}else{
this.sendFile(req,res,filepath,statObj,'');
}
}catch (e){
debug(inspect(e));
this.sendError(req,res)
}
}
這個(gè)是主要處理邏輯,首先通過(guò)解析客戶端的請(qǐng)求url,通過(guò)path模塊來(lái)拿到文件路徑,第一個(gè)參數(shù)是config中配置的靜態(tài)文件根目錄;然后來(lái)判斷當(dāng)前請(qǐng)求的是文件還是目錄,這里使用一個(gè)try catch代碼塊來(lái)捕獲異常,如果請(qǐng)求的是個(gè)目錄的話,遍歷這個(gè)目錄下的所有文件并且將文件名和文件路徑傳給list編譯模板,然后交給發(fā)送方法去處理。
發(fā)送處理方法
sendFile(req,res,filepath,statObj,type){
//發(fā)送文件
let expires = new Date(Date.now() + 60 * 1000);
//緩存設(shè)置
res.setHeader('Expires', expires.toUTCString());
res.setHeader('Cache-Control', 'max-age=600');
res.setHeader('Accept-Range', 'bytes');//斷點(diǎn)續(xù)傳
if(type == 'html'){
res.end(filepath);
}else {
res.setHeader('Content-Type',mime.getType(filepath));
fs.createReadStream(filepath).pipe(res);
}
}
發(fā)送方法里設(shè)置的緩存策略為強(qiáng)制緩存,并且在發(fā)送方法里調(diào)用zlibFn方法去看客戶端請(qǐng)求是否使用壓縮,并在最后將內(nèi)容pipe到res里返回給客戶端。
zlib壓縮方法
zlibFn(req,res){
let acceptEncoding = request.headers['accept-encoding'];//壓縮
if (!acceptEncoding) {
acceptEncoding = '';
}
if (acceptEncoding.match(/\bdeflate\b/)) {
res.setHeader('Content-Encoding','deflate');
return zlib.createDeflate();
} else if (acceptEncoding.match(/\bgzip\b/)) {
res.setHeader('Content-Encoding','gzip');
return zlib.createGzip();
} else {
return null;
}
}
這里主要是通過(guò)請(qǐng)求頭中的accept-encoding來(lái)判斷是否壓縮,以何種方式壓縮,壓縮后返回一個(gè)流對(duì)象。
初寫node靜態(tài)服務(wù)器(簡(jiǎn)易版)全部代碼請(qǐng)見(jiàn)Github