64 # Implement a http-server

Preparation

In the previous section, the parameters of the user were obtained through the configuration of the commander. The following section completes the unfinished task of using promises to write methods into classes, and implements one, http-serverhttps://www.npmjs.com/package/http-server , which http-serveris A simple zero configuration command line static HTTP server.

The core modules that need to be used

const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs").promises;
const {
    
     createReadStream, createWriteStream, readFileSync } = require("fs");

Third-party modules that need to be used:

  • ejs is used for template rendering
  • mime is used to obtain the MIME type according to the file extension, and can also obtain the extension according to the MIME type.
  • Chalk is used for console output coloring
  • debug can print according to environment variables
const ejs = require("ejs"); // 服务端读取目录进行渲染
const mime = require("mime");
const chalk = require("chalk");
const debug = require("debug")("server");

install dependencies

npm install [email protected] [email protected] [email protected] [email protected]

insert image description here

Configure environment variables

const debug = require("debug")("server");
// 根据环境变量来进行打印 process.env.EDBUG
debug("hello kaimo-http-server");
set DEBUG=server
kaimo-http-server

insert image description here

Code

  1. Open a server with the obtained configuration data

www.jsimplemented in

#! /usr/bin/env node

const program = require("commander");
const {
    
     version } = require("../package.json");
const config = require("./config.js");
const Server = require("../src/server.js");

program.name("kaimo-http-server");
program.usage("[args]");
program.version(version);

Object.values(config).forEach((val) => {
    
    
    if (val.option) {
    
    
        program.option(val.option, val.description);
    }
});

program.on("--help", () => {
    
    
    console.log("\r\nExamples:");
    Object.values(config).forEach((val) => {
    
    
        if (val.usage) {
    
    
            console.log("  " + val.usage);
        }
    });
});

// 解析用户的参数
let parseObj = program.parse(process.argv);

let keys = Object.keys(config);

// 最终用户拿到的数据
let resultConfig = {
    
    };
keys.forEach((key) => {
    
    
    resultConfig[key] = parseObj[key] || config[key].default;
});

// 将拿到的配置数据开启一个 server
console.log("resultConfig---->", resultConfig);
let server = new Server(resultConfig);
server.start();
  1. Realization server.js, the main logic is to implement a Server class with the parameters input by the user through the core module and the third-party module, in which the service is started through the start method, and the core logic realizes finding the file and returning it through the path. If it is a folder, process whether there is a file index.html, If not, the directory information in the folder is rendered through the template.
// 核心模块
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs").promises;
const {
    
     createReadStream, createWriteStream, readFileSync } = require("fs");

// 第三方模块
const ejs = require("ejs"); // 服务端读取目录进行渲染
const mime = require("mime");
const chalk = require("chalk");
const debug = require("debug")("server");
// 根据环境变量来进行打印 process.env.EDBUG
debug("hello kaimo-http-server");

// 同步读取模板
const template = readFileSync(path.resolve(__dirname, "template.ejs"), "utf-8");

class Server {
    
    
    constructor(config) {
    
    
        this.host = config.host;
        this.port = config.port;
        this.directory = config.directory;
        this.template = template;
    }
    async handleRequest(req, res) {
    
    
        let {
    
     pathname } = url.parse(req.url);
        // 需要对 pathname 进行一次转义,避免访问中文名称文件找不到问题
        console.log(pathname);
        pathname = decodeURIComponent(pathname);
        console.log(pathname);
        // 通过路径找到这个文件返回
        let filePath = path.join(this.directory, pathname);
        console.log(filePath);
        try {
    
    
            // 用流读取文件
            let statObj = await fs.stat(filePath);
            // 判断是否是文件
            if (statObj.isFile()) {
    
    
                this.sendFile(req, res, filePath, statObj);
            } else {
    
    
                // 文件夹的话就先尝试找找 index.html
                let concatFilePath = path.join(filePath, "index.html");
                try {
    
    
                    let statObj = await fs.stat(concatFilePath);
                    this.sendFile(req, res, concatFilePath, statObj);
                } catch (e) {
    
    
                    // index.html 不存在就列出目录
                    this.showList(req, res, filePath, statObj, pathname);
                }
            }
        } catch (e) {
    
    
            this.sendError(req, res, e);
        }
    }
    // 列出目录
    async showList(req, res, filePath, statObj, pathname) {
    
    
        // 读取目录包含的信息
        let dirs = await fs.readdir(filePath);
        console.log(dirs, "-------------dirs----------");
        try {
    
    
            let parseObj = dirs.map((item) => ({
    
    
                dir: item,
                href: path.join(pathname, item) // url路径拼接自己的路径
            }));
            // 渲染列表:这里采用异步渲染
            let templateStr = await ejs.render(this.template, {
    
     dirs: parseObj }, {
    
     async: true });
            console.log(templateStr, "-------------templateStr----------");
            res.setHeader("Content-type", "text/html;charset=utf-8");
            res.end(templateStr);
        } catch (e) {
    
    
            this.sendError(req, res, e);
        }
    }
    // 读取文件返回
    sendFile(req, res, filePath, statObj) {
    
    
        // 设置类型
        res.setHeader("Content-type", mime.getType(filePath) + ";charset=utf-8");
        // 读取文件进行响应
        createReadStream(filePath).pipe(res);
    }
    // 专门处理错误信息
    sendError(req, res, e) {
    
    
        debug(e);
        res.statusCode = 404;
        res.end("Not Found");
    }
    start() {
    
    
        const server = http.createServer(this.handleRequest.bind(this));
        server.listen(this.port, this.host, () => {
    
    
            console.log(chalk.yellow(`Starting up kaimo-http-server, serving ./${
      
      this.directory.split("\\").pop()}\r\n`));
            console.log(chalk.green(`       http://${
      
      this.host}:${
      
      this.port}`));
        });
    }
}

module.exports = Server;
  1. template template.ejscode
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>列表模板</title>
    </head>

    <body>
        <% dirs.forEach(item=> { %>
        <li>
            <a href="<%=item.href%>"><%=item.dir%></a>
        </li>
        <% }) %>
    </body>
</html>

achieve effect

In the console, we enter the following command to start a service with port 4000

kaimo-http-server --port 4000

We visit http://localhost:4000/and can see

insert image description here

We are entering the public file, which has index.htmlthe file

insert image description here

Click on the public directory to enter http://localhost:4000/public, you can see that the returned index.htmlpage

insert image description here

We are entering the public2 file, there is no index.htmlfile in it

insert image description here

Click on the public2 directory to enter http://localhost:4000/public2, and what you see is the list

insert image description here

We can click on any file to view: for example凯小默.txt

insert image description here

Guess you like

Origin blog.csdn.net/kaimo313/article/details/132070324