静态文件服务器实现的功能

服务器
Server通过请求头中的Range:bytes=0-xxx来判断是否是做Range请求,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,如果无效,则返回416状态码,表明Request。
  • 读取静态文件 
  • MIME类型支持
  • 缓存支持/控制
  • 支持gzip压缩
  • Range支持,断点续传 
  • 发布为可执行命令并可以后台运行,可以通过npm install -g安装

首先先构建好项目目录,项目目录如下:

  1. project 
  2.  
  3. |---bin 命令行实现放置脚本 
  4. |---public 静态文件服务器默认静态文件夹 
  5. |---src 实现功能的相关代码 
  6. | | 
  7. | |__template 模板文件夹 
  8. | | 
  9. | |__app.js 主要功能文件(main文件) 
  10. | |__config.js 配置文件  
  11. |---package.josn (初始化) 

要启动一个服务器,我们需要知道这个服务器的启动时的端口号,在config.js配置一下:

[[223555]]

  1. let config = { 
  2.  host:'localhost' //提示用 , 
  3. port:8080 //服务器启动时候的默认端口号, 
  4. path:path.resolve(__dirname,'..','test-dir') //静态服务器启动时默认的工作目录 
  5.  } 

读取静态文件之前首先要先启动服务器,之后所有的方法都在class Server方法里

  1. //handlebar 编译模板,得到一个渲染的方法,然后传入实际数据数据就可以得到渲染后的HTML了 
  2. function list() { 
  3. let tmpl = fs.readFileSync(path.resolve(__dirname, 'template''list.html'),'utf8'); 
  4. return handlebars.compile(tmpl);//进行编译,***渲染 
  5. }class Server { 
  6.     constructor(argv) { 
  7.         this.list = list(); 
  8.         this.config = Object.assign({}, this.config, argv); 
  9.     } 
  10.     start() { 
  11.         let server = http.createServer();//创建服务器 
  12.         //当客户端向服务端发出数据的时候,会出发request事件 
  13.         server.on('request', this.request.bind(this)); 
  14.         server.listen(this.config.port, () => {//监听端口号 
  15.             let url = `http://${this.config.host}:${this.config.port}`; 
  16.             debug(`server started at ${chalk.green(url)}`); 
  17.         }); 
  18.     } 
  19. //发送错误信息, 
  20. sendError(err, req, res) { 
  21.     res.statusCode = 500; 
  22.     res.end(`${err.toString()}`); 
  23. }} 
  24. module.exports = Server; 

读取静态文件

  • 设计思路
  • 首先输入一个url时,可能对应服务器上的一个文件,或者对应一个目录, 
  • 检查是否文件还是目录如果文件不存在,返回404状态码,发送not found页面到客户端 
  • 如果文件存在:打开文件读取
  • 设置response header 发送文件到客户端 
  • 如果是目录就打开目录列表
  1. async request(req, res) { 
  2.     //先取到客户端想要的是文件或文件夹路径  
  3.     let { pathname } = url.parse(req.url);//获取路径的文件信息 
  4.     let filepath = path.join(this.config.root, pathname);//服务器上的对应服务器物理路径 
  5.     try { 
  6.         let statObj = await stat(filepath);//获取路径的文件信息 
  7.         if (statObj.isDirectory()) {//如果是目录的话,应该显示目录 下面的文件列表 
  8.             let files = await readdir(filepath);//读取文件的文件列表 
  9.             files = files.map(file => ({//把每个字符串变成对象 
  10.                 name: file, 
  11.                 url: path.join(pathname, file) 
  12.             })); 
  13.             //handlebar 编译模板 
  14.             let html = this.list({ 
  15.                 title: pathname, 
  16.                 files 
  17.             }); 
  18.             res.setHeader('Content-Type''text/html');设置请求头 
  19.             res.end(html); 
  20.         } else { 
  21.             this.sendFile(req, res, filepath, statObj);//读取文件 
  22.         } 
  23.     } catch (e) {//不存在访问内就发送错误信息 
  24.         debug(inspect(e));//inspect把一个对象转成字符 
  25.         this.sendError(e, req, res); 
  26.     } 

缓存支持/控制

设计思路

缓存分为强制缓存和对比缓存: 

  • 两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则.
  •  强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互
  1. ***次访问服务器的时候,服务器返回资源和缓存的标识,客户端则会把此资源缓存在本地的缓存数据库中。
  2. 第二次客户端需要此数据的时候,要取得缓存的标识,然后去问一下服务器我的资源是否是***的。如果是***的则直接使用缓存数据,如果不是***的则服务器返回新的资源和缓存规则,客户端根据缓存规则缓存新的数据。

通过***修改时间来判断缓存是否可用

  1. Last-Modified:响应时告诉客户端此资源的***修改时间 
  2. If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向服务器请求时带上头If-Modified-Since。
  3. 服务器收到请求后发现有头If-Modified-Since则与被请求资源的***修改时间进行比对。若***修改时间较新,说明资源又被改动过,则响应***的资源内容并返回200状态码; 
  4. 若***修改时间和If-Modified-Since一样,说明资源没有修改,则响应304表示未更新,告知浏览器继续使用所保存的缓存文件。

ETag是资源标签。如果资源没有变化它就不会变。

  • 客户端想判断缓存是否可用可以先获取缓存中文档的ETag,然后通过If-None-Match发送请求给Web服务器询问此缓存是否可用。
  • 服务器收到请求,将服务器的中此文件的ETag,跟请求头中的If-None-Match相比较,如果值是一样的,说明缓存还是***的,Web服务器将发送304 Not Modified响应码给客户端表示缓存未修改过,可以使用。 
  • 如果不一样则Web服务器将发送该文档的***版本给浏览器客户端
  1. handleCache(req, res, filepath, statObj) { 
  2.     let ifModifiedSince = req.headers['if-modified-since']; 
  3.     let isNoneMatch = req.headers['is-none-match']; 
  4.     res.setHeader('Cache-Control''private,max-age=30');//max-age=30缓存内容将在30秒后失效 
  5.     res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString()); 
  6.     let etag = statObj.size
  7.     let lastModified = statObj.ctime.toGMTString(); 
  8.     res.setHeader('ETag', etag);//获取ETag 
  9.     res.setHeader('Last-Modified', lastModified);//服务器文件的***修改时间 
  10.     //任何一个对比缓存头不匹配,则不走缓存 
  11.     if (isNoneMatch && isNoneMatch != etag) {//缓存过期 
  12.         return fasle; 
  13.     } 
  14.     if (ifModifiedSince && ifModifiedSince != lastModified) {//缓存过期 
  15.         return fasle; 
  16.     } 
  17.     //当请求中存在任何一个对比缓存头,则返回304,否则不走缓存 
  18.     if (isNoneMatch || ifModifiedSince) {//缓存有效 
  19.         res.writeHead(304); 
  20.         res.end(); 
  21.         return true
  22.     } else { 
  23.         return false
  24.     } 

支持gzip压缩

设计思路

浏览器都会携带自己支持的压缩类型,最常用的两种是gzip和deflate。根据请求头Accept-Encoding,返回不同的压缩格式.

  1. getEncoding(req, res) { 
  2.  
  3.     let acceptEncoding = req.headers['accept-encoding'];//获取客户端发送的压缩请求头的信息 
  4.     if (/\bgzip\b/.test(acceptEncoding)) {//如果是gzip的格式 
  5.         res.setHeader('Content-Encoding''gzip'); 
  6.         return zlib.createGzip(); 
  7.     } else if (/\bdeflate\b/.test(acceptEncoding)) {//如果是deflate的格式 
  8.         res.setHeader('Content-Encoding''deflate'); 
  9.         return zlib.createDeflate(); 
  10.     } else { 
  11.         return null;//不压缩 
  12.     } 

Range支持,断点续传

设计思路

  • 该选项指定下载字节的范围,常应用于分块下载文件
  • 服务器告诉客户端可以使用range response.setHeader('Accept-Ranges', 'bytes')  
  • Server通过请求头中的Range:bytes=0-xxx来判断是否是做Range请求,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成206,如果无效,则返回416状态码,表明Request
  1. getStream(req, res, filepath, statObj) { 
  2.     let start = 0;//可读流起始位置 
  3.     let end = statObj.size - 1;//可读流结束位置 
  4.     let range = req.headers['range'];//获取客户端的range请求头信息, 
  5.     if (range) {//断点续传 
  6.         res.setHeader('Accept-Range''bytes'); 
  7.         res.statusCode = 206;//返回整个内容的一块 
  8.         let result = range.match(/bytes=(\d*)-(\d*)/);//断点续传的分段内容不能有小数,网络传输的最小单位为一个字节 
  9.         if (result) { 
  10.             start = isNaN(result[1]) ? start : parseInt(result[1]); 
  11.             end = isNaN(result[2]) ? end : parseInt(result[2]) - 1; 
  12.         } 
  13.     } 
  14.     return fs.createReadStream(filepath, { 
  15.         start, end 
  16.     }); 

发布为可执行命令

首先在package.json配置一下"bin": { "http-static": "bin/www" }

  1. #! /usr/bin/env node     //这段代码一定要写在开头,为了兼容各个电脑平台的差异性 
  2. // -d --root 静态文件目录 -o --host 主机 -p --port 端口号let yargs = require('yargs'); 
  3. let  Server = require('../src/app.js'); 
  4. let argv = yargs.option('d',{    
  5.  alias:'root',  
  6.  demand:'false',   
  7.  type:'string',    
  8.  default:process.cwd(),   
  9.  description:'静态文件跟目录'    }) 
  10. .option('o',{   
  11.   alias:'host',   
  12.   demand:'localhost',   
  13.   type:'string',     
  14. description:'请配置监听的主机'}) 
  15. .option('p',{   
  16.   alias:'root',   
  17.   demand:'false',    
  18.  type:'number',    
  19.  default:8080,   
  20.   description:'请配置端口号'}) 
  21. .usage('http-static [options]').example(   
  22.   'http-static -d / 8080 -o localhost','在本机的9090端口上监听客户端的请求' 
  23. ).help('h').argv; 
  24. // argv = {d,root,o,host,p,port}let server = new Server(argv);//启动服务server.start(); 

这样命令行当中通过输入http-static来直接启动静态文件服务器了,那么命令行调用的功能也就实现了,***用npm publish发布一下,发布到npm上面去了,我们就可以通过npm install -g来进行全局安装了。

 

责任编辑:武晓燕 来源: segmentfault
相关推荐

2021-09-10 10:07:17

Nginx虚拟主机服务器

2018-04-17 10:35:23

服务器静态文件Serve

2019-01-27 20:00:14

Linux静态文件服务器

2019-01-28 11:03:03

NginxFastDFS服务器

2010-07-22 14:02:44

远程文件服务器加密EFS

2011-09-13 09:31:07

文件服务器云存储云计算

2011-07-18 14:47:39

DC文件服务器

2009-09-25 10:13:15

2009-02-11 00:09:00

2011-08-02 13:55:44

服务器群集文件服务器

2011-09-01 18:12:43

Linuxsamba

2012-11-22 09:43:08

2011-09-13 10:01:47

文件服务器主板硬盘

2011-11-04 10:34:27

文件服务器中小企业调研报告

2010-09-27 11:28:15

2010-10-18 09:47:09

DRBDNFS负载均衡

2012-07-02 09:36:02

文件服务器故障服务器故障

2011-10-24 13:36:50

文件服务器存储

2011-10-27 07:42:06

服务器文件系统Linux

2012-05-29 10:12:21

点赞
收藏

51CTO技术栈公众号