zoukankan      html  css  js  c++  java
  • 手写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)} 
    `);
        })
    })
    
    // 解析用户执行时的参数
    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
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    使用ASP.Net MVC5 Web API OData和Sencha Touch 开发WebAPP
    @MarkFan 口语练习录音 20140401
    Listening Carefully SP1403S
    Listening Carefully SP1403
    团队股权分配
    Sencha Architect 安装与使用
    离乡与理想 Demo
    PowerDesigner创建物理模型
    SOA 面向服务架构 阅读笔记(六)
    SOA 面向服务架构 阅读笔记(五)
  • 原文地址:https://www.cnblogs.com/5118svip/p/14158822.html
Copyright © 2011-2022 走看看