手写node的http-server插件

1.http-server介绍

有时候网页地址栏需要用到http协议,这个时候就可以通过http-server这个插件来达到效果。终端工具输入npm i -g http-server就可以全局安装http-server了。

npm i -g http-server
复制代码

安装之后在需要打开的文件的目录下输入http-server就可以开启http协议啦。
比如我在一个项目目录下打开命令行
image.png
输入http-server启动服务
image.png
然后打开这个http://127.0.0.1:8081/就能看到这样的效果,当前文件夹目录解构及文件。
image.png
这是一个最简单的用法场景,他还能配置许多选项。例如 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事件,显示了使用样例。
image.png
在命令工具里面输入my-hs -d /Users/xujian/workPlace/vue-better-drawer,就在/Users/xujian/workPlace/vue-better-drawer目录下启动了一个http服务
image.png
浏览器打开http://127.0.0.1:8080/这个地址就能访问到这个目录下的文件结构
image.png
能显示具体的文件文件类容:
image.png

并且能访问更深层的目录:
image.png
一个简易版的http-server就已经实现了,github地址:github.com/Itherma/my-…


作者:凌晨3点
链接:https://juejin.cn/post/6907530903841423373
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @ 2020-12-19 13:49  5118会员优惠码  阅读(165)  评论(0编辑  收藏  举报

bug专家https://www.bugzj.com/

投资理财