zoukankan      html  css  js  c++  java
  • 手写启动一个本地服务器的命令行工具

    本文学习自:
    手写启动一个本地服务器的命令行工具
    https://juejin.im/post/5b53f7c5e51d4534b93f27e9
    看一下我本地做的效果图

    首先

    mkdir xl-server
    cd xl-server
    npm init
    

    修改package.json文件

    //package.json
    {
      "name": "xl-server",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {},
      "bin": {
        "xl-server": "bin/xl-server.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "chalk": "^2.4.1",
        "commander": "^2.16.0",
        "debug": "^3.1.0",
      }
    }
    

    我们知道实现在本地命令行中运行 xl-server 必须有bin目录 上面的bin目录指向了 bin/xl-server.js

    // ./bin/xl-server.js
    commander.on('--help', () => {
        console.log('
     how to use:');
        console.log('    xl-server --port <val>');
        console.log('    xl-server --host <val>');
        console.log('    xl-server --dir <val>');
    })
    
    commander
        .version('1.0.0')
        .usage('[option]')
        .option('-p,--port <n>','server port')
        .parse(process.argv)
    
    let Server = require('../index')   //引入index文件导出的类
    let server = new Server(commander)  //实例
    server.start();   //启动
    
    let {exec} = require('child_process');
    if(process.platform === 'win32'){ //执行调起浏览器 localhost:port
        exec(`start http://localhost:${server.config.port}`);    
    }else{
        exec(`open http://localhost:${server.config.port}`);
    }
    

    上面的commander的是一个解析和配置命令行参数的包 具体用法可以去npm官网看看用法commander 我们在index.js里面创建服务

    //index.js
    // index.js
    let http = require('http');
    let util = require('util');
    let mime = require('mime');   //第三方模块 用来获取内容类型
    let chalk = require('chalk'); // 粉笔
    
    //初步最简单的命令行工具
    let config = require('./config');
    
    
    class Server {
        constructor(options) {
            this.config = {...config, ...options};    //覆盖默认配置例如端口号
        }
    
        start() {
            let server = http.createServer((req,res) => {
                res.end('hello')
            });
            let { port, host } = this.config;
            server.listen(port, host, function () {
                console.log(`server start http://${host}:${chalk.green(port)}`)
            });
        }
    
    }
    module.exports = Server;
    

    还缺少一个config.js的默认配置项

    module.exports = {
        port: 3000,
        host:'localhost',
        dir:process.cwd()     //当前运行目录
    }
    // 运行的配置 
    

    这样一个最简单的 好像什么用都没有的命令行工具就有了 我们在当前的根目录下执行下面的命令就可以看到浏览器打开了 localhost:3000

    // npm link
    // xl-server
    

    读取本地文件目录或文件内容

    //index.js
    // 复杂一点的命令行工具 展示目录和文件
    let http = require('http');
    let url = require('url');
    let path = require('path');
    let fs = require('fs');
    let util = require('util');
    let zlib = require('zlib');
    let mime = require('mime');   //第三方模块 用来获取内容类型
    // let debug = require('debug')('env')  //打印输出 会根据环境变量控制输出
    let chalk = require('chalk'); // 粉笔
    let ejs = require('ejs')    //高效的 JavaScript 模板引擎。
    
    
    let config = require('./config');
    
    let stat = util.promisify(fs.stat);
    let readdir = util.promisify(fs.readdir);
    
    let templateStr = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
    class Server {
        constructor(options) {
            this.config = {...config, ...options};
            this.template = templateStr;
        }
        async handleRequest(req, res) {               //这里根据请求的url来读取目录或者文件内容
            let { pathname } = url.parse(req.url, true);
            let realPath = path.join(this.config.dir, pathname);
            try{
                let statObj = await stat(realPath)
                if(statObj.isFile()) {   //文件
                    this.sendFile(req, res, statObj, realPath)
                } else {   //文件夹
                    let dirs = await readdir(realPath);
                    dirs = dirs.map(dir => ({ name: dir, path: path.join(pathname, dir) }));
                    let str = ejs.render(this.template, { dirs });
                    res.setHeader('Content-Type', 'text/html;charset=utf-8');
                    res.end(str);
                }
            } catch (e) {
                this.sendError(req, res, e);
            }
        }
        sendError(req, res, e) {
            console.log(e),
            res.end('404')
        }
        sendFile(req, res, statObj, realPath) {
            fs.createReadStream(realPath).pipe(res)
        }
        start() {
            let server = http.createServer(this.handleRequest.bind(this));    //这里用handleRequest来执行
            let { port, host } = this.config;
            server.listen(port, host, function () {
                console.log(`server start http://${host}:${chalk.green(port)}`)
            });
        }
    
    }
    module.exports = Server;
    

    目录结构的模板文件如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
       <!-- 实现渲染列表 {dirs:[{name:'201804',path:'/201804'},{}]} -->
       <p>什么什么</p>
       <%dirs.forEach(item=>{%>
            <li><a href="<%=item.path%>"><%=item.name%></a></li>
       <%})%>
    </body>
    </html>
    

    实现文件目录的缓存和文件压缩
    这个操作是在读取文件的过程中实现,所以下面只给出index.js里面的sendFile方法里面增加缓存和压缩

    sendFile(req, res, statObj, realPath) {
        if (this.cache(req, res, statObj, realPath)) {
            res.statusCode = 304;
            res.end();
            return;
        }
        res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8');
        let zip = this.compress(req, res, statObj, realPath);
        if(zip) {
            return fs.createReadStream(realPath).pipe(zip).pipe(res)
        }
        fs.createReadStream(realPath).pipe(res)
    }
    

    接下来就是写上面的cache方法和compress方法了

    cache(req, res, statObj, realPath) {
        res.setHeader('Cache-control','max-age=100')    //强制缓存  注意即使是强制缓存也不会缓存主网页
        let etag = statObj.ctime.toGMTString() + statObj.size;
        let lastModified = statObj.ctime.toGMTString();    //atime创建时间 ctime --- change time 修改时间
        res.setHeader('Etag', etag);   //Etag -- if-none-match
        res.setHeader('Last-Modified', lastModified);  //Last-Modified --- if-none-match
        let ifNoneMatch = req.headers['if-none-match'];
        let ifModifiedSince = req.headers['if-modified-since'];
        if (etag != ifNoneMatch) {       //两种方式 第一种就行
            return false
        }
        if (lastModified !=ifModifiedSince) {       //两种方式 第一种就行,此种只是列出304缓存的另一种方式
            return false
        }
        return true
    }
    compress(req, res, statObj, realPat) {    //实现压缩功能
        let encoding = req.headers['accept-encoding'];
        if (encoding) {
            if (encoding.match(/gzip/)) {
                res.setHeader('content-encoding','gzip')
                return zlib.createGzip()
            } else if (encoding.match(/deflate/)) {
                res.setHeader('content-encoding', 'deflate')
                return zlib.createDeflate();
            } else {
                return false
            }
        } else {
            return false
        }
    }
    

    写完之后 我们

    npm link 
    xl-server   
    //然后我们就可以看到启动了一个localhost:3000 并且展示了当前文件目录
    

    大家可以看我本地的目录

    //index.js中的内容
    // 复杂一点的命令行工具 展示目录和文件 完善--> 加上压缩和缓存
    let http = require('http');
    let url = require('url');
    let path = require('path');
    let fs = require('fs');
    let util = require('util');
    let zlib = require('zlib');
    let mime = require('mime');   //第三方模块 用来获取内容类型
    // let debug = require('debug')('env')  //打印输出 会根据环境变量控制输出
    let chalk = require('chalk'); // 粉笔
    let ejs = require('ejs')    //高效的 JavaScript 模板引擎。
    
    
    let config = require('./config');
    
    let stat = util.promisify(fs.stat);
    let readdir = util.promisify(fs.readdir);
    
    let templateStr = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');
    class Server {
        constructor(options) {
            this.config = {...config, ...options};
            this.template = templateStr;
        }
        async handleRequest(req, res) {
            let { pathname } = url.parse(req.url, true);
            let realPath = path.join(this.config.dir, pathname);
            try{
                let statObj = await stat(realPath)
                if(statObj.isFile()) {   //文件
                    this.sendFile(req, res, statObj, realPath)
                } else {   //文件夹
                    let dirs = await readdir(realPath);
                    dirs = dirs.map(dir => ({ name: dir, path: path.join(pathname, dir) }));
                    let str = ejs.render(this.template, { dirs });
                    res.setHeader('Content-Type', 'text/html;charset=utf-8');
                    res.end(str);
                }
            } catch (e) {
                this.sendError(req, res, e);
            }
        }
        sendError(req, res, e) {
            console.log(e); // 将错误打印出来
            res.statusCode = 404;
            res.end('Not Found');
        }
        cache(req, res, statObj, realPath) {
            res.setHeader('Cache-control','max-age=100')    //强制缓存  注意即使是强制缓存也不会缓存主网页
            let etag = statObj.ctime.toGMTString() + statObj.size;
            let lastModified = statObj.ctime.toGMTString();    //atime创建时间 ctime --- change time 修改时间
            res.setHeader('Etag', etag);   //Etag -- if-none-match
            res.setHeader('Last-Modified', lastModified);  //Last-Modified --- if-none-match
            let ifNoneMatch = req.headers['if-none-match'];
            let ifModifiedSince = req.headers['if-modified-since'];
            if (etag != ifNoneMatch) {       //两种方式 第一种就行
                return false
            }
            if (lastModified !=ifModifiedSince) {       //两种方式 第一种就行,此种只是列出304缓存的另一种方式
                return false
            }
            return true
        }
        compress(req, res, statObj, realPat) {    //实现压缩功能
            let encoding = req.headers['accept-encoding'];
            if (encoding) {
                if (encoding.match(/gzip/)) {
                    res.setHeader('content-encoding','gzip')
                    return zlib.createGzip()
                } else if (encoding.match(/deflate/)) {
                    res.setHeader('content-encoding', 'deflate')
                    return zlib.createDeflate();
                } else {
                    return false
                }
            } else {
                return false
            }
        }
        sendFile(req, res, statObj, realPath) {
            if (this.cache(req, res, statObj, realPath)) {
                res.statusCode = 304;
                res.end();
                return;
            }
            res.setHeader('Content-Type', mime.getType(realPath) + ';charset=utf-8');
            let zip = this.compress(req, res, statObj, realPath);
            if(zip) {
                return fs.createReadStream(realPath).pipe(zip).pipe(res)
            }
            fs.createReadStream(realPath).pipe(res)
        }
        start() {
            let server = http.createServer(this.handleRequest.bind(this));
            let { port, host } = this.config;
            server.listen(port, host, function () {
                console.log(`server start http://${host}:${chalk.green(port)}`)
            });
        }
    
    }
    module.exports = Server;
    
  • 相关阅读:
    石油采集
    石油采集
    Redis 笔记与总结7 PHP + Redis 信息管理系统(用户信息的增删改查)
    数据分析电子商务B2C全流程_数据分析师
    数据分析电子商务B2C全流程_数据分析师
    数据挖掘中分类算法小结_数据分析师
    大数据分析或提升企业税务职能价值
    大数据可视化必须避免的三种常见错误
    大数据可视化必须避免的三种常见错误
    数据分析帮你预知商机
  • 原文地址:https://www.cnblogs.com/smart-girl/p/12596725.html
Copyright © 2011-2022 走看看