zhongxia245 / blog

这是一个Blog, 如果喜欢可以订阅,是Watch, 不是 Star 哈。。。

Home Page:https://zhongxia245.github.io/blog/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

【20161214】HTTP缓存之静态web服务器实现

zhongxia245 opened this issue · comments

占位占位

时间:2016-12-12 23:01:08
更新时间: 2016-12-14 15:03:06
学习了理论的知识,还是需要实践来加深对HTTP缓存的理解。 【这里占个位置,告诉自己要去实践一下】

一、教程

  1. 《用NodeJs打造你的静态文件服务器》

【推荐】根据教程一步一步敲出来,大大加深了对缓存的理解,也知道实际中该如何使用。

二、采用技术以及缓存应用学习

采用NodeJs的 http,url,fs,path,zlib 实现一个简单的web服务器,支持强缓存,协商缓存,GZip压缩。

1. 强缓存 , 协商缓存

  • 强缓存主要是通过设置 Response 的 Expires 和 Cache-Control`

  • 协商缓存主要是设置Response 返回 Last-Modified , 浏览器请求资源的时候,会把这个值放在 If-Modified-Since 的Header里面,然后这里判断是否过时了。

  • 协商缓存还有一个ETag可以配置使用,但是ETag需要这里生成唯一的资源标识,然后返回给浏览器,浏览器再次请求一个资源的时候,会带上这个ETag标记,放在 Request的 If-None-Match 的 header字段,如果文件没有被修改,则返回304 ,浏览器采用本地缓存。

2. 既生Last-Modified何生Etag?

你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  • 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

3. 浏览器行为

  • 在按CMD+R刷新浏览器的时候,一直没有看到使用缓存,莫名其妙的,后面发现是 按刷新的时候,跳过了强缓存,协商缓存会验证。
  • CTRL+CMD+R 跳过 强缓存 和 协商缓存
  • 只有浏览器地址栏,输入地址,或者页面超链接跳转,采用使用强缓存验证

三、代码

根据教程一步一步自己敲出来更好。

//config.js
exports.Expires = {
  fileMatch: /^(gif|png|jpg|js|css)$/ig,
  maxAge: 60 * 60 * 24 * 365
}
exports.Compress = {
  match: /css|js|html/ig
}
//MIME.js
// Header 的  Content-Type 
exports.types = {
  "css": "text/css",
  "gif": "image/gif",
  "html": "text/html",
  "ico": "image/x-icon",
  "jpeg": "image/jpeg",
  "jpg": "image/jpeg",
  "js": "text/javascript",
  "json": "application/json",
  "pdf": "application/pdf",
  "png": "image/png",
  "svg": "image/svg+xml",
  "swf": "application/x-shockwave-flash",
  "tiff": "image/tiff",
  "txt": "text/plain",
  "wav": "audio/x-wav",
  "wma": "audio/x-ms-wma",
  "wmv": "video/x-ms-wmv",
  "xml": "text/xml"
};
//index.js
var PORT = 9999
var http = require('http')
var url = require('url')
var fs = require('fs')
var path = require('path')
var zlib = require("zlib")

var MIME = require('./MIME.js').types
var config = require('./config.js')

var server = http.createServer(function (request, response) {
  // 获取资源路径
  var pathname = url.parse(request.url).pathname
  var realPath = 'assets' + pathname

  fs.exists(realPath, function (exists) {
    if (!exists) {
      response.writeHead(404, { 'Content-Type': 'text/plain' })
      response.write('This Request Url ' + pathname + ' was not found on this server')
      response.end()
    } else {
      fs.readFile(realPath, 'binary', function (err, file) {
        if (err) {
          response.writeHead(500, { 'Content-Type': 'text/plain' })
          response.end(err.Error)
        } else {
          var ext = path.extname(realPath)
          ext = ext ? ext.slice(1) : 'unknown'
          var contentType = MIME[ext] || 'text/plain'

          //设置强缓存 Expires   Cache-Control
          var expires = new Date()
          expires.setTime(expires.getTime() + config.Expires.maxAge * 1000)
          response.setHeader('Expires', expires.toUTCString())
          response.setHeader('Cache-Control', 'max-age=' + config.Expires.maxAge)


          //设置协商缓存  Last-Modified   If-Modified-Since   Tag  If-None-Match
          fs.stat(realPath, function (err, stat) {
            var lastModified = stat.mtime.toUTCString()
            response.setHeader("Last-Modified", lastModified)
            if (request.headers["If-Modified-Since"] && lastModified == request.headers["If-Modified-Since"]) {
              response.writeHead(304, "Not Modified")
              response.end()
            } else {

              //使用GZIP压缩资源   利用Node的 zlib 压缩
              var raw = fs.createReadStream(realPath)
              var acceptEncoding = request.headers['accept-encoding'] || ""
              var matched = ext.match(config.Compress.match)

              if (matched && acceptEncoding.match(/\bgzip\b/)) {
                response.writeHead(200, "Ok", { 'Content-Encoding': 'gzip' });
                raw.pipe(zlib.createGzip()).pipe(response);
              } else if (matched && acceptEncoding.match(/\bdeflate\b/)) {
                response.writeHead(200, "Ok", { 'Content-Encoding': 'deflate' });
                raw.pipe(zlib.createDeflate()).pipe(response);
              } else {
                response.writeHead(200, { 'Content-Type': contentType })
                raw.pipe(response);
              }
            }
          })
          console.log('load assets file:' + realPath)
        }
      })
    }
  })
})

server.listen(PORT)

console.log('Server runing at port:' + PORT + '...')