NodeJS4-7静态资源服务器实战_缓存
浏览器发出一个请求,服务器解析出响应的结果返回给浏览器.
缓存是怎么工作的?
用户发起请求,浏览器检查本地是否存在缓存,如果第一次请求没有缓存,那就向服务器发起请求,服务器协商缓存的内容并且返回响应,接着返回缓存响应,再次请求时,会检查缓存是否失效,没有失效就使用本地缓存,如果本地缓存失效了,
缓存header
判断本地有没有失效的可以用
- 返回时间Expires/Cache-Control
Expires(比较老式):返回的是一个绝对时间,由于时区问题很少用
Cache-Control(常用):返回的是一个相对时间
- 修改时间Last-Modified/If-Modified_Since
Last-Modified:向服务器校验的时候拿到的结果,每次返回响应的时候会告诉Last-Modified时间
If-Modified_Since:浏览器第二次或者第三次发起请求时,会把上次的修改时间放在修改头的If-Modified_Since
- 服务器校验 If-None-Match /TRag
defaultConfig.js
module.exports={ root:process.cwd(), hostname:'127.0.0.1', port:9527, compress:/\.(html|js|css|md)/, cache:{ maxAge:600, expire:true, cacheControl:true, lastModified:true, etag:true } }
新建文件src/helper/cache.js
const {cache} = require('../config/defaultConfig') // 更新一下响应,修改时间 function refreshRes(stats,res){ const {maxAge,expires,cacheControl,lastModified,etag} = cache; if(expires){ res.setHeader('Expires',(new Date(Date.now() + maxAge *1000)).toUTCString()) } if(cacheControl){ res.setHeader('Cache-Control',`public,max-age=${maxAge}`) } if(lastModified){ res.setHeader('Last-Modified',stats.mtime.toUTCString()) } if(etag){ res.setHeader('ETag',`${stats.size} = ${stats.mtime}`); } } module.exports = function isFresh(stats,req,res){ refreshRes(stats,res) const lastModified = req.headers['if-modified-since'] const etag = req.headers['if-none-match'] if(!lastModified && !etag){ return false } if(lastModified && lastModified !==res.getHeader('Last-Modified')) { return false } if(etag && etag !== res.getHeader('Etag')){ return false } return true }
route.js引用ca'ch
const fs =require('fs') const path = require('path') const Handlebars = require('handlebars') const promisify = require('util').promisify; const stat = promisify(fs.stat) const readdir = promisify(fs.readdir); // //引用range范围 // const range = require('./range') const config = require('../config/defaultConfig') const tplPath = path.join(__dirname,'../template/dir.tpl') const source = fs.readFileSync(tplPath); const template = Handlebars.compile(source.toString()) //引入新加的mime,对contentType的判断 const mime = require('./mime') const compress = require('./compress') //引用range范围 const range = require('./range') // 引入cache const isFresh = require('./cache') module.exports=async function(req,res,filePath){ try{ const stats =await stat(filePath) if(stats.isFile()){ const contentType = mime(filePath) res.statusCode = 200 res.setHeader('content-Type',contentType) if(isFresh(stats,req,res)){ res.statusCode = 304; res.end() return } let rs; const {code,start,end} = range(stats.size, req, res) if(code === 200){ res.statusCode = 200 rs = fs.createReadStream(filePath) }else{ res.statusCode = 216 //测试随便定 rs = fs.createReadStream(filePath,{start,end}) } // let rs = fs.createReadStream(filePath) if(filePath.match(config.compress)){ rs = compress(rs,req,res) } rs.pipe(res); // fs.readFile(filePath,(err,data)=>{ // res.end(data) // }); }else if(stats.isDirectory()){ //所有异步调用必须用await const files =await readdir(filePath); res.statusCode = 200 res.setHeader('content-Type','text/html') const dir = path.relative(config.root,filePath) const data = { title:path.basename(filePath), // dir:config.root, dir:dir?`/${dir}`:'', files:files.map(file=>{ return { file, icon:mime(file) } }) } res.end(template(data)); } }catch(ex){ console.error(ex); res.statusCode = 404 res.setHeader('content-Type','text/plain') res.end(`${filePath} is not a directory or file\n ${ex.error}`) } }
主要代码是
运行结果
首次
刷新