1. 程式人生 > 實用技巧 >手寫node的http-server外掛

手寫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:

github.com/http-party/…

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資料夾,裡面新建wwwconfig.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
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。