1.0.1 • Published 5 years ago

gzy-server v1.0.1

Weekly downloads
-
License
MIT
Repository
-
Last release
5 years ago

模拟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