wodetian 2019-06-26
首先先构建好项目目录,项目目录如下:
project
|---bin 命令行实现放置脚本
|
|---public 静态文件服务器默认静态文件夹
|
|---src 实现功能的相关代码
| |
| |__template 模板文件夹
| |
| |__app.js 主要功能文件(main文件)
| |__config.js 配置文件
|
|---package.josn (初始化)
要启动一个服务器,我们需要知道这个服务器的启动时的端口号,在config.js配置一下:
let config = { host:'localhost' //提示用 , port:8080 //服务器启动时候的默认端口号, path:path.resolve(__dirname,'..','test-dir') //静态服务器启动时默认的工作目录 }
读取静态文件之前首先要先启动服务器,之后所有的方法都在class Server方法里
//handlebar 编译模板,得到一个渲染的方法,然后传入实际数据数据就可以得到渲染后的HTML了 function list() { let tmpl = fs.readFileSync(path.resolve(__dirname, 'template', 'list.html'),'utf8'); return handlebars.compile(tmpl);//进行编译,最后渲染 }class Server { constructor(argv) { this.list = list(); this.config = Object.assign({}, this.config, argv); } start() { let server = http.createServer();//创建服务器 //当客户端向服务端发出数据的时候,会出发request事件 server.on('request', this.request.bind(this)); server.listen(this.config.port, () => {//监听端口号 let url = `http://${this.config.host}:${this.config.port}`; debug(`server started at ${chalk.green(url)}`); }); } //发送错误信息, sendError(err, req, res) { res.statusCode = 500; res.end(`${err.toString()}`); }} module.exports = Server;
async request(req, res) { //先取到客户端想要的是文件或文件夹路径 let { pathname } = url.parse(req.url);//获取路径的文件信息 let filepath = path.join(this.config.root, pathname);//服务器上的对应服务器物理路径 try { let statObj = await stat(filepath);//获取路径的文件信息 if (statObj.isDirectory()) {//如果是目录的话,应该显示目录 下面的文件列表 let files = await readdir(filepath);//读取文件的文件列表 files = files.map(file => ({//把每个字符串变成对象 name: file, url: path.join(pathname, file) })); //handlebar 编译模板 let html = this.list({ title: pathname, files }); res.setHeader('Content-Type', 'text/html');设置请求头 res.end(html); } else { this.sendFile(req, res, filepath, statObj);//读取文件 } } catch (e) {//不存在访问内就发送错误信息 debug(inspect(e));//inspect把一个对象转成字符 this.sendError(e, req, res); } }
设计思路
缓存分为强制缓存和对比缓存:
通过最后修改时间来判断缓存是否可用
ETag是资源标签。如果资源没有变化它就不会变。
1.客户端想判断缓存是否可用可以先获取缓存中文档的ETag,然后通过If-None-Match发送请求给Web服务器询问此缓存是否可用。
2 服务器收到请求,将服务器的中此文件的ETag,跟请求头中的If-None-Match相比较,如果值是一样的,说明缓存还是最新的,Web服务器将发送304 Not Modified响应码给客户端表示缓存未修改过,可以使用。
3.如果不一样则Web服务器将发送该文档的最新版本给浏览器客户端
handleCache(req, res, filepath, statObj) { let ifModifiedSince = req.headers['if-modified-since']; let isNoneMatch = req.headers['is-none-match']; res.setHeader('Cache-Control', 'private,max-age=30');//max-age=30缓存内容将在30秒后失效 res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString()); let etag = statObj.size; let lastModified = statObj.ctime.toGMTString(); res.setHeader('ETag', etag);//获取ETag res.setHeader('Last-Modified', lastModified);//服务器文件的最后修改时间 //任何一个对比缓存头不匹配,则不走缓存 if (isNoneMatch && isNoneMatch != etag) {//缓存过期 return fasle; } if (ifModifiedSince && ifModifiedSince != lastModified) {//缓存过期 return fasle; } //当请求中存在任何一个对比缓存头,则返回304,否则不走缓存 if (isNoneMatch || ifModifiedSince) {//缓存有效 res.writeHead(304); res.end(); return true; } else { return false; } }
浏览器都会携带自己支持的压缩类型,最常用的两种是gzip和deflate。根据请求头Accept-Encoding,返回不同的压缩格式.
getEncoding(req, res) { let acceptEncoding = req.headers['accept-encoding'];//获取客户端发送的压缩请求头的信息 if (/\bgzip\b/.test(acceptEncoding)) {//如果是gzip的格式 res.setHeader('Content-Encoding', 'gzip'); return zlib.createGzip(); } else if (/\bdeflate\b/.test(acceptEncoding)) {//如果是deflate的格式 res.setHeader('Content-Encoding', 'deflate'); return zlib.createDeflate(); } else { return null;//不压缩 } }
设计思路
Range Not Satisfiable
getStream(req, res, filepath, statObj) { let start = 0;//可读流起始位置 let end = statObj.size - 1;//可读流结束位置 let range = req.headers['range'];//获取客户端的range请求头信息, if (range) {//断点续传 res.setHeader('Accept-Range', 'bytes'); res.statusCode = 206;//返回整个内容的一块 let result = range.match(/bytes=(\d*)-(\d*)/);//断点续传的分段内容不能有小数,网络传输的最小单位为一个字节 if (result) { start = isNaN(result[1]) ? start : parseInt(result[1]); end = isNaN(result[2]) ? end : parseInt(result[2]) - 1; } } return fs.createReadStream(filepath, { start, end }); }
首先在package.json配置一下"bin": { "http-static": "bin/www" }
#! /usr/bin/env node //这段代码一定要写在开头,为了兼容各个电脑平台的差异性 // -d --root 静态文件目录 -o --host 主机 -p --port 端口号let yargs = require('yargs'); let Server = require('../src/app.js'); let argv = yargs.option('d',{ alias:'root', demand:'false', type:'string', default:process.cwd(), description:'静态文件跟目录' }) .option('o',{ alias:'host', demand:'localhost', type:'string', description:'请配置监听的主机'}) .option('p',{ alias:'root', demand:'false', type:'number', default:8080, description:'请配置端口号'}) .usage('http-static [options]').example( 'http-static -d / 8080 -o localhost','在本机的9090端口上监听客户端的请求' ).help('h').argv; // argv = {d,root,o,host,p,port}let server = new Server(argv);//启动服务server.start();
这样命令行当中通过输入http-static来直接启动静态文件服务器了,那么命令行调用的功能也就实现了,最后用npm publish发布一下,发布到npm上面去了,我们就可以通过npm install -g来进行全局安装了