本篇主要將 實現一個自己的命令列工具,web快取,以及web檔案壓縮融合到一起,如果有部分不是太清楚的地方可以檢視文末的連結。
實現本地伺服器命令列工具 xl-server
//package.json
{
"name": "xl-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"bin": {
"xl-server": "bin/xl-server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^2.4.1",
"commander": "^2.16.0",
"debug": "^3.1.0",
}
}
複製程式碼
我們知道實現在本地命令列中執行 xl-server 必須有bin目錄 上面的bin目錄指向了 bin/xl-server.js
// ./bin/xl-server.js
commander.on('--help', () => {
console.log('\r\n how to use:');
console.log(' xl-server --port <val>');
console.log(' xl-server --host <val>');
console.log(' xl-server --dir <val>');
})
commander
.version('1.0.0')
.usage('[option]')
.option('-p,--port <n>','server port')
.parse(process.argv)
let Server = require('../index') //引入index檔案匯出的類
let server = new Server(commander) //例項
server.start(); //啟動
let {exec} = require('child_process');
if(process.platform === 'win32'){ //執行調起瀏覽器 localhost:port
exec(`start http://localhost:${server.config.port}`);
}else{
exec(`open http://localhost:${server.config.port}`);
}
複製程式碼
上面的commander的是一個解析和配置命令列引數的包 具體用法可以去npm官網看看用法commander 我們在index.js裡面建立服務
// index.js
let http = require('http');
let util = require('util');
let mime = require('mime'); //第三方模組 用來獲取內容型別
let chalk = require('chalk'); // 粉筆
//初步最簡單的命令列工具
let config = require('./config');
class Server {
constructor(options) {
this.config = {...config, ...options}; //覆蓋預設配置例如埠號
}
start() {
let server = http.createServer((req,res) => {
res.end('hello')
});
let { port, host } = this.config;
server.listen(port, host, function () {
console.log(`server start http://${host}:${chalk.green(port)}`)
});
}
}
module.exports = Server;
複製程式碼
還缺少一個config.js的預設配置項
module.exports = {
port: 3000,
host:'localhost',
dir:process.cwd() //當前執行目錄
}
// 執行的配置
複製程式碼
這樣一個最簡單的 好像什麼用都沒有的命令列工具就有了 我們在當前的根目錄下執行下面的命令就可以看到瀏覽器開啟了 localhost:3000
// npm link
// xl-server
複製程式碼
讀取本地檔案目錄或檔案內容
//index.js
// 複雜一點的命令列工具 展示目錄和檔案
let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let util = require('util');
let zlib = require('zlib');
let mime = require('mime'); //第三方模組 用來獲取內容型別
// let debug = require('debug')('env') //列印輸出 會根據環境變數控制輸出
let chalk = require('chalk'); // 粉筆
let ejs = require('ejs') //高效的 JavaScript 模板引擎。
let config = require('./config');
let stat = util.promisify(fs.stat);
let readdir = util.promisify(fs.readdir);
let templateStr = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
class Server {
constructor(options) {
this.config = {...config, ...options};
this.template = templateStr;
}
async handleRequest(req, res) { //這裡根據請求的url來讀取目錄或者檔案內容
let { pathname } = url.parse(req.url, true);
let realPath = path.join(this.config.dir, pathname);
try{
let statObj = await stat(realPath)
if(statObj.isFile()) { //檔案
this.sendFile(req, res, statObj, realPath)
} else { //資料夾
let dirs = await readdir(realPath);
dirs = dirs.map(dir => ({ name: dir, path: path.join(pathname, dir) }));
let str = ejs.render(this.template, { dirs });
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end(str);
}
} catch (e) {
this.sendError(req, res, e);
}
}
sendError(req, res, e) {
console.log(e),
res.end('404')
}
sendFile(req, res, statObj, realPath) {
fs.createReadStream(realPath).pipe(res)
}
start() {
let server = http.createServer(this.handleRequest.bind(this)); //這裡用handleRequest來執行
let { port, host } = this.config;
server.listen(port, host, function () {
console.log(`server start http://${host}:${chalk.green(port)}`)
});
}
}
module.exports = Server;
複製程式碼
目錄結構的模板檔案如下:
//index.html 用來展示檔案目錄
<!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>Document</title>
</head>
<body>
<!-- 實現渲染列表 {dirs:[{name:'201804',path:'/201804'},{}]} -->
<%dirs.forEach(item=>{%>
<li><a href="<%=item.path%>"><%=item.name%></a></li>
<%})%>
</body>
</html>
複製程式碼
實現檔案目錄的快取和檔案壓縮
這個操作是在讀取檔案的過程中實現,所以下面只給出index.js裡面的sendFile方法裡面增加快取和壓縮
sendFile(req, res, statObj, realPath) {
if (this.cache(req, res, statObj, realPath)) {
res.statusCode = 304;
res.end();
return;
}
res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8');
let zip = this.compress(req, res, statObj, realPath);
if(zip) {
return fs.createReadStream(realPath).pipe(zip).pipe(res)
}
fs.createReadStream(realPath).pipe(res)
}
複製程式碼
接下來就是寫上面的cache方法和compress方法了
cache(req, res, statObj, realPath) {
res.setHeader('Cache-control','max-age=100') //強制快取 注意即使是強制快取也不會快取主網頁
let etag = statObj.ctime.toGMTString() + statObj.size;
let lastModified = statObj.ctime.toGMTString(); //atime建立時間 ctime --- change time 修改時間
res.setHeader('Etag', etag); //Etag -- if-none-match
res.setHeader('Last-Modified', lastModified); //Last-Modified --- if-none-match
let ifNoneMatch = req.headers['if-none-match'];
let ifModifiedSince = req.headers['if-modified-since'];
if (etag != ifNoneMatch) { //兩種方式 第一種就行
return false
}
if (lastModified !=ifModifiedSince) { //兩種方式 第一種就行,此種只是列出304快取的另一種方式
return false
}
return true
}
compress(req, res, statObj, realPat) { //實現壓縮功能
let encoding = req.headers['accept-encoding'];
if (encoding) {
if (encoding.match(/\bgzip\b/)) {
res.setHeader('content-encoding','gzip')
return zlib.createGzip()
} else if (encoding.match(/\bdeflate\b/)) {
res.setHeader('content-encoding', 'deflate')
return zlib.createDeflate();
} else {
return false
}
} else {
return false
}
}
複製程式碼
寫完之後 我們
npm link
xl-server
//然後我們就可以看到啟動了一個localhost:3000 並且展示了當前檔案目錄
複製程式碼
另外本文的三個部分具體一些的例子可以檢視下面的:
- 手寫命令列工具
- 聊聊web快取那些事!
- 至於壓縮這塊屬於node fs模組zlib的內容 大家可以檢視nodejs文件
以上就是完美實現本地伺服器命令列工具,不足之處歡迎各位提出寶貴的意見或建議,也希望能幫助到你從中獲得一些知識,謝謝大家的關注!