目录结构
package.json 结构
"bin": {
"jw-server": "./bin/www.js"
},
复制代码
www.js
// commander 命令行提示工具 可以帮你处理参数
// 知道启动服务的端口号 --port -p
// 改执行的目录 --dir -d
// 改主机名 --address -a
let commander = require('commander');
// vue init xxx => 生成项目 jw-server log
let version = require('../package.json').version
let args = commander
.version(version, '-v, --version') // 设置版本号
.option('-p, --port <n>', 'server port')
.option('-a, --address <n>', 'server address')
.option('-d, --dir <n>', 'server show list')
// 组成一个动作
.usage('[options] <jw-server --port 3000>');
// commander
// .command('log', 'print log')
// .action(function () {
// console.log('log')
// });
// commander
// .command('init', 'init project')
// .action(function () {
// console.log('init');
// });
commander.on('--help', function () {
console.log('');
console.log('jw-server:');
console.log(' $ jw-server --port');
console.log(' $ jw-server --address');
});
commander.parse(process.argv);
// 默认配置
let config =Object.assign({
dir:process.cwd(),
address:'localhost',
port:8080
},args);
// 启动一个服务
// 写一个创建server的功能
let Server = require('../src/server.js')
let server = new Server(config);
server.start();
复制代码
template.html
<!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>珠峰架构师</title>
</head>
<body>
<%dirs.forEach(f=>{%>
<li><a href="<%=f.url%>"><%=f.dir%></a></li>
<%})%>
</body>
</html>
复制代码
server.js(支持压缩,缓存)
let http = require('http');
let chalk = require('chalk');
let url = require('url');
let fs = require('mz/fs');
let mime = require('mime');
let path = require('path');
let ejs = require('ejs');
let zlib = require('zlib');
let tmplStr = fs.readFileSync(path.join(__dirname,'template.html'),
'utf8');
// koa
class Server {
constructor(config) {
this.port = config.port;
this.address = config.address;
this.dir = config.dir; // dir表示当前启动的目录 / 手动指定的
this.tmpl = tmplStr;
}
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);}
}
}
// 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;
}
// 缓存 304
cache(req, res, path, statObj){ // 设置强制缓存 对比缓存
res.setHeader('Cache-Control','max-age=30');
res.setHeader('Expires',new Date(Date.now()+5*1000).toGMTString());
let ctime = statObj.ctime.toLocaleString();
let etag = ctime + '_' + statObj.size
res.setHeader('Last-Modified', ctime);
// 最好能不读取文件 就不要读取文件
res.setHeader('Etag', etag )
// last-modified
let ifModifiedSince = req.headers['if-modified-since']
// etag
let ifNoneMatch = req.headers['if-none-match'];
// 对比缓存
console.log(ifModifiedSince , ifNoneMatch)
if (ifModifiedSince && ifNoneMatch){
if (ifModifiedSince === ctime && ifNoneMatch === etag ){
return true;
}else{
return false;
}
}else{
return false;
}
}
sendFile(req,res,path,statObj){
// 范围请求 start ,end
// 先判断有没有缓存 有缓存 走缓存 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);
}
sendError(e,res) {
console.log(e);
res.statusCode = 404;
res.end(`Not found`);
}
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)}`)
});
}
}
module.exports = Server;
复制代码