gzy-server

1.0.1 • Public • Published

模拟http-server 实现自己的一个服务工具

初始化文件夹

npm init -y

在package.json中添加

"bin":{
    "gzy-server":"./bin/www.js"
}

创建命令指定的目录及文件,并且制定运行环境

13.cli/bin/www.js

#! /usr/bin/env node

npm link 把当前这个包链接到npm中

// /Documents/webstorm/Project/blog/13.cli
npm link

安装commander命令行工具以及chalk粉笔工具

yarn add commander chalk --save

根据commander API 对你的工具设置个性化提示

ps:commander API

// 这个argv最后返回的就是一个对象,是根据你命令执行的后面的参数决定的
let argv = commander
    .version(version,'-v, --version') // 设置版本号
    .usage('[options] <gzy-server --port 3000>') // 用例设置
    .option('-p, --port <n>','server port')
    .option('-a, --address <n>', 'server address')
    .option('-d, --dir <n>', 'server show list')
    
    commander
    .command('log') // 这三个组成一个动作
    .option('--log','console.log')
    .action(function(){
        console.log('hello')
    })

    commander
    .on('--help', function(){
        console.log('')
        console.log('gzy-server:');
        console.log('  $ custom-help --help');
        console.log('  $ custom-help -h');
    })
    .parse(process.argv);

设置默认配置

// 如果执行命令传入了参数以参数为准
let config = Object.assign({
    dir:process.cwd(),
    address:'localhost',
    port:8080
},argv)

根据默认配置启动一个服务

// 写一个创建server的功能
let Server = require('../src/server.js')
let server = new Server(config); // 自己定义了一个启动服务的类
server.start(); // 定义了一个start方法,start()相当于http.createServer()

开始写Server类

定义实例上的属性
let tmplStr = fs.readFileSync(path.join(__dirname,'template.html'),
'utf8');
class Server {
    constructor(config) {
    this.port = config.port; // 端口号
    this.address = config.address; // eg:localhost
    this.dir = config.dir; // dir表示当前启动的目录 / 手动指定的
    this.tmpl = tmplStr; // 如果是目录的话渲染的template模版
  }
}
start (启动服务)
start() {
    let server = http.createServer(this.handleRequest());
    server.listen(this.port, this.address, () => {
      console.log(chalk.yellow(`Starting up http-server, serving ./
      Available on:`
      ));
      console.log(`  http://${this.address}:${chalk.green(this.port)}`)
    });
  }
handleRequest (处理请求)
    handleRequest() { // 处理请求的方法
        return async (req, res) => {
        // 需要判断当前请求的内容是文件还是文件
        let { pathname } = url.parse(req.url);
        pathname = decodeURI(pathname); // 文件名为汉字找不到
        if(pathname === '/favicon.ico') return this.sendError('找不到');
        let realPath = path.join(this.dir, pathname);
        try {
            let statObj = await fs.stat(realPath);
            if (statObj.isFile()) { // 文件操作
            this.sendFile(req, res, realPath, statObj);
            } else { // 目录操作
            let dirs = await fs.readdir(realPath);// ['www.js']
            // 渲染出一个渲染后的字符串
            // dirs应该包含 当前点击的链接 和显示的路径
            dirs = dirs.map((dir)=>{
                return {url:path.join(pathname,dir),dir}
            })
            let renderStr = ejs.render(this.tmpl, { dirs});
            res.setHeader('Content-Type','text/html;charset=utf8');
            res.end(renderStr);
            }
        } catch (e) {
            this.sendError(e,res);
        }
        }
    }
sendError (处理错误)
  sendError(e,res) {
    console.log(e);
    res.statusCode = 404;
    res.end(`Not found`);
  }
sendFile (处理文件)
  sendFile(req,res,path,statObj){
    // range (需要放到上面,先走分段)
    if(this.range(req,res,path,statObj)) return;

    // 先判断有没有缓存 有缓存 走缓存  boolean
    if (this.cache(req, res, path, statObj)){
      return res.statusCode = 304, res.end();
    }

    // gzip压缩 转化流
    // Accept-Encoding: gzip, deflate, br
    res.setHeader('Content-Type', mime.getType(path) + ';charset=utf-8');
    // 返回的文件需要压缩
    let zip;
    if (zip = this.gzip(req,res)){ // 调用方法后返回的是一个转化流
      return fs.createReadStream(path).pipe(zip).pipe(res);
    }
    fs.createReadStream(path).pipe(res);
  }
range (分段请求)
    range(req,res,path,statObj){
        let range = req.headers['range'];
        if(range){
            let [,start,end] = range.match(/(\d*)-(\d*)/);
            start = start ? Number(start):0;
            end = end ? Number(end):statObj.size;
            res.statusCode = 206;
            res.setHeader('Content-Range',`bytes=${start}-${end}/${statObj.size}`);
            res.setHeader('Content-Length',end-start+1);
            fs.createReadStream(path,{start,end}).pipe(res);
            return true;
        }else{
            return false
        }
    }
cache (缓存)
    cache(path,statObj,req,res){
        res.setHeader('Cache-Control','Max-Age=10');
        res.setHeader('Expries',new Date(Date.now()+10*1000).toLocaleString());
        let ctime = statObj.ctime.toLocaleString();
        let etag = ctime+'_'+statObj.size;
        res.setHeader('Last-Modified',ctime);
        res.setHeader('Etag',etag)

        let ifModifiedSince = req.headers['if-modified-since'];
        let ifNoneMatch = req.headers['if-none-match'];

        if(ifModifiedSince && ifNoneMatch ){
            if(ifModifiedSince === ctime && ifNoneMatch === etag){
                res.setHeader('Content-Encoding','gzip');
                return zlib.createGzip();
            }else{
                res.setHeader('Content-Encoding','gzip');
                return zlib.createDeflate();
            }
        }else{
            return false 
        }  

    }
gzip (压缩)
    gzip(req,res){
        let encoding = req.headers['accept-encoding'];
        if(encoding.includes('gzip')){
            res.setHeader('Content-Encoding','gzip');
            return zlib.createGzip();
        }   
        if(encoding.includes('deflate')){
            res.setHeader('Content-Encoding','deflate');
            return zlib.createDeflate();
        }
        return false;
    }

先切换到官方的源

nrm use npm

添加用户(没有的话是注册)

npm addUser

发布

npm publish

Readme

Keywords

none

Package Sidebar

Install

npm i gzy-server

Weekly Downloads

1

Version

1.0.1

License

MIT

Unpacked Size

19.9 kB

Total Files

7

Last publish

Collaborators

  • guozhiyuan