手寫node的http-server外掛
1.http-server介紹
有時候網頁位址列需要用到http協議,這個時候就可以通過http-server
這個外掛來達到效果。終端工具輸入npm i -g http-server就可以全域性安裝http-server
了。
npm i -g http-server
複製程式碼
安裝之後在需要開啟的檔案的目錄下輸入http-server
就可以開啟http
協議啦。
比如我在一個專案目錄下開啟命令列
輸入http-server啟動服務
然後開啟這個http://127.0.0.1:8081/就能看到這樣的效果,當前資料夾目錄解構及檔案。
這是一個最簡單的用法場景,他還能配置許多選項。例如 http-server --port 3000可以設定服務的埠號,還可以用 --directory來指定目錄等等,詳細用法請參考GitHub:
2.程式碼實現
1.需求分析和實現
- 在
package.json
裡面配置bin
欄位,然後用npm link
連線到全域性npm
包,這樣就可以在全域性用自己定義的命令了,可以用粉筆工具chalk
設定提示的檔案顏色 - 引入
commander
包接受使用者在命令列輸入的引數來覆蓋預設引數,設定命令列提示、顏色等內容。 - 用
http-server
建立一個服務,根據請求路徑讀取目錄,判斷是檔案還是資料夾,如果是檔案直接返回對應的檔案,否則返回一個當前目錄結構的html
。返回的html
可以用ejs
這個包根據模板生成,不同型別的檔案需要設定不同的響應頭Content-Type
mime
這個包解析出來。
2.程式碼實現
1.初始化專案
npm init -y
複製程式碼
然後把專案名字改成my-http-server
,在package.json
裡面配置bin
欄位,增加一個bin
資料夾,裡面新建www
和config.js
檔案,bin
目錄是工具的啟動檔案目錄,後面執行my-hs
命令是直接執行www
裡面的程式碼。然後新建一個src
目錄並新增server.js
檔案,用來處理客戶端的請求和響應相關邏輯,新建template.html
用於處理路徑下是目錄要返回該目錄的html
情況。然後安裝chalk,commander,ejs,mime
這個4個需要用到的包。
目錄結構如下:
|-bin
| |-config.js ------------- 服務預設配置
| |-www -------------服務啟動檔案
|
|-src
| |-server.js --------- 服務邏輯處理
| |-template.html ---------- html模板,用於渲染資料夾目錄結構
|
|-package.json
複製程式碼
package.json
{
"name": "my-hs",
"version": "1.0.0",
"description": "",
"bin": {
"my-hs": "./bin/www"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"chalk": "^4.1.0",
"commander": "^6.2.1",
"ejs": "^3.1.5",
"mime": "^2.4.7"
}
}
複製程式碼
config.js 預設配置檔案
// 自定義需要顯示到命令列中的命令
const config = { // 給自己來維護引數的
'port':{
option:'-p,--port <n>', // <v> 表示時一個值
descriptor:'set your server port',
default: 8080,
usage:'my-hs --port <n>'
},
'directory':{
option:'-d,--directory <n>',
descriptor:'set your server start directory',
default: process.cwd(),
usage: 'my-hs --directory <n>'
},
'cache':{
option:'-c,--cache <n>',
descriptor:'set your server cache',
default:'no-cache',
usage: 'my-hs --cache <n>'
}
}
module.exports = config;
複製程式碼
www檔案
注意:檔案頭部的#! /usr/bin/env node
是固定寫法,告訴系統這個檔案要用node來執行,
#! /usr/bin/env node
const program = require('commander');
const chalk = require('chalk');
const config = require('./config');
const Server = require('../src/server')
// 配置使用的名字
program.name('my-hs');
// 預設的配置物件
const defaultConfig = {};
// 使用樣例集合
const usageList = [];
// 遍歷配置到當前的程式裡面
Object.entries(config).forEach(([key, value]) => {
defaultConfig[key] = value.default;
usageList.push(value.usage)
program.option(value.option, value.descriptor);
});
// 監聽--help事件,在命令列顯示樣例
program.on('--help',function () {
console.log('Examples:');
usageList.forEach(line=>{
console.log(` ${chalk.green(line)} \r`);
})
})
// 解析使用者執行時的引數
program.parse(process.argv);
// 根據使用者的引數 和 預設值 做出一個配置來
function mergeOtions(defaultConfig,newConfig){
const options = {}
for(let key in defaultConfig){
if(!(key in newConfig)){
options[key] = defaultConfig[key]
}else{
// 校驗newConfig 是否符合我的預期
options[key] = newConfig[key]
}
}
return options
}
let options = mergeOtions(defaultConfig,program);
// 獲取使用者的引數來建立一個服務並且啟動
let server = new Server(options);
server.start();
複製程式碼
server.js檔案
const http = require('http');
const url = require('url'); // 解析url引數
const path = require('path');
const fs = require('fs').promises; // 獲取fs模組的promise方法
const { createReadStream, createWriteStream } = require('fs'); // 獲取讀寫流方法
const chalk = require('chalk'); // 粉筆工具
const mime = require('mime'); // 解析檔案mime型別的包
const ejs = require('ejs'); // 生成html的包
// 服務類
class Server {
constructor(options) {
this.port = options.port;
this.directory = options.directory;
this.cache = options.cache;
}
async handleRequest(req, res) {
let { pathname } = url.parse(req.url);
pathname = decodeURIComponent(pathname); // pathname有可能是中文,把base64解析成中文
// 列出所有的資料夾
let requestUrl = path.join(this.directory, pathname); // 路徑帶/的不要用resolve會回到根路徑
try {
const statObj = await fs.stat(requestUrl); // 讀取路徑對應的型別,是目錄還是檔案
// 如果是目錄則返回一個該目錄的html
if (statObj.isDirectory()) {
// 獲取該目錄下的所有檔案及資料夾
let dirs = await fs.readdir(requestUrl);
let content = await ejs.renderFile(path.resolve(__dirname, 'template.html'), {
dirs: dirs.map(dir => ({
name: dir,
pathname: path.join(pathname, dir) // 加上字首來獲取深層的資料夾結構
}))
});
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.end(content);
} else {
// 檔案 讀取檔案
this.sendFile(requestUrl, req, res, statObj)
}
} catch (e) {
console.log(e)
this.sendError(e, req, res);
}
}
// 錯誤響應
sendError(err, req, res) {
res.statusCode = 404;
res.end('Not Found')
}
// 根據檔案型別設定響應頭並返回檔案
sendFile(filePath, req, res, stat) {
res.setHeader('Content-Type', `${mime.getType(filePath)};charset=utf-8`)
createReadStream(filePath).pipe(res);
}
// 啟動服務方法
start() {
const server = http.createServer(this.handleRequest.bind(this));
server.listen(this.port, () => {
console.log(`${chalk.yellow('Starting up http-server, serving')}`);
console.log(` http://127.0.0.1:${chalk.green(this.port)}`)
});
}
}
module.exports = Server;
複製程式碼
檔案目錄的html的模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<%dirs.forEach(dir=>{%>
<li><a href="<%=dir.pathname%>"><%=dir.name%></a></li>
<%})%>
</body>
</html>
複製程式碼
3.使用
在命令工具裡面輸入 my-hs --help
,我們在程式碼裡監聽了--help
事件,顯示了使用樣例。
在命令工具裡面輸入my-hs -d /Users/xujian/workPlace/vue-better-drawer
,就在/Users/xujian/workPlace/vue-better-drawer目錄下啟動了一個http服務
瀏覽器開啟http://127.0.0.1:8080/這個地址就能訪問到這個目錄下的檔案結構
能顯示具體的檔案檔案類容:
並且能訪問更深層的目錄:
一個簡易版的http-server
就已經實現了,github地址:github.com/Itherma/my-…
作者:凌晨3點
連結:https://juejin.cn/post/6907530903841423373
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。