node.js構建靜態伺服器
**利用node.js構建靜態伺服器,讓你理解底層的伺服器是怎樣實現的,讓你對伺服器有一個更好的認識。**
實現了如下功能:
- 讀取靜態文件
- MIME類型支持
- 緩存支持/控制
- 支持gzip壓縮
- 只能訪問指定目錄, 不能訪問指定目錄的上級目錄,保證安全
- 訪問目錄可以自動尋找下面的index.html文件
- Range支持,斷點續傳
- 發布為可執行命令並可以後台運行,可以通過npm install -g安裝
### 讀取靜態文件
```
let { promisify, inspect } = require(util);
let stat = promisify(fs.stat);
let readdir = promisify(fs.readdir);
let statObj = await stat(filepath);
if (statObj.isDirectory()) {//如果是目錄的話,應該顯示目錄 下面的文件列表
let files = await readdir(filepath);
files = files.map(file => ({
name: file,
url: path.join(pathname, file)
}));
let html = this.list({
title: pathname,
files
});
res.setHeader(Content-Type, text/html);
res.end(html);
} else {
this.sendFile(req, res, filepath, statObj);
}
sendFile(req, res, filepath, statObj) {
if (this.handleCache(req, res, filepath, statObj)) return; //如果走緩存,則直接返回
res.setHeader(Content-Type, mime.getType(filepath) + ;charset=utf-8);// .jpg
let encoding = this.getEncoding(req, res);
let rs = this.getStream(req, res, filepath, statObj);
if (encoding) {
rs.pipe(encoding).pipe(res);
} else {
rs.pipe(res);
}
}
getStream(req, res, filepath, statObj) {
let start = 0;
let end = statObj.size - 1;
let range = req.headers[range];
return fs.createReadStream(filepath, {
start, end
});
}
```
### MiMe類型檢測
這個很簡單,對應的代碼是:
```
let mime = require(mime);
res.setHeader(Content-Type, mime.getType(filepath) + ;charset=utf-8);// .jpg
```
### 緩存支持/控制, 對應的代碼是:
```
if (this.handleCache(req, res, filepath, statObj)) return; //如果走緩存,則直接返回
handleCache(req, res, filepath, statObj) {
let ifModifiedSince = req.headers[if-modified-since];
let isNoneMatch = req.headers[is-none-match];
res.setHeader(Cache-Control, private,max-age=30);
res.setHeader(Expires, new Date(Date.now() + 30 * 1000).toGMTString());
let etag = statObj.size;
let lastModified = statObj.ctime.toGMTString();
res.setHeader(ETag, etag);
res.setHeader(Last-Modified, lastModified);
if (isNoneMatch && isNoneMatch != etag) {
return fasle;
}
if (ifModifiedSince && ifModifiedSince != lastModified) {
return fasle;
}
if (isNoneMatch || ifModifiedSince) {
res.writeHead(304);
res.end();
return true;
} else {
return false;
}
}
```
### 支持gzip壓縮
```
getEncoding(req, res) {
//Accept-Encoding:gzip, deflate
let acceptEncoding = req.headers[accept-encoding];
if (/gzip/.test(acceptEncoding)) {
res.setHeader(Content-Encoding, gzip);
return zlib.createGzip();
} else if (/deflate/.test(acceptEncoding)) {
res.setHeader(Content-Encoding, deflate);
return zlib.createDeflate();
} else {
return null;
}
}
if (encoding) {
rs.pipe(encoding).pipe(res);
} else {
rs.pipe(res);
}
```
### 只能訪問指定目錄, 不能訪問指定目錄的上級目錄,保證安全
假如一個同學用瀏覽器訪問http://localhost:8000/../xxx.js 怎麼辦捏?瀏覽器會自動幹掉那兩個作為父路徑的點的。瀏覽器會把這個路徑組裝成http://localhost:8000/xxx.js
但是curl命令可以:
curl -i http://localhost:8000/../xxx.js
那麼怎麼辦呢?
```
var realPath = path.join("assets", path.normalize(pathname.replace(/../g, "")));
```
強制替換即可。
### Range支持,斷點續傳
```
getStream(req, res, filepath, statObj) {
let start = 0;
let end = statObj.size - 1;
let range = req.headers[range];
if (range) {
res.setHeader(Accept-Range, bytes);
res.statusCode = 206;//返回整個內容的一塊
let result = range.match(/bytes=(d*)-(d*)/);
if (result) {
start = isNaN(result[1]) ? start : parseInt(result[1]);
end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
}
}
return fs.createReadStream(filepath, {
start, end
});
}
```
*** 發布為可執行命令並可以後台運行,可以通過npm install -g安裝
在package中添加:
```
"bin": {
"studytest": "bin/www"
},
```
執行 npm link
這個時候其實就可以全局執行了
最後發布
- npm adduser
- npm publish
推薦閱讀:
※js中什麼技術能合併多個前端請求,並生成一個json文件發送?
※node.js 已經淡出眾多開發者的視野了嗎?
※Promise then中回調為什麼是非同步執行?
※nodejs中,zlib.gzip系列純cpu計算函數為什麼會有非同步版本?
※ECMAScript 6 的模塊相比 CommonJS 的require (...)有什麼優點?