nodejs自己搭建一个服务器来理解浏览器的缓存机制

搭建本地服务器

安装必要的包

npm install nodemon --s-dev
npm install express --s-dev

nodemon方便我们开发的时候,js文件更新自动编译执行
express是一个服务器渲染框架,能够搭建服务器

开始搭建服务器

搭建nodejs服务器

const fs=require('fs')// nodejs自带
const path = require('path')// nodejs自带
const express = require('express')
const app = express()

app.use(express.static(path.join(__dirname, 'public')))// 静态文件夹

//监听端口
app.listen(8080,()=>{
  console.log('HTTP Server is running on: http://localhost:%s', 8080)
})
// 监听请求
app.get('*',(req,res)=>{
  console.log(req)
  res.end('get end')
})

这里搭建一个监听所有get请求的方法,都会返回get end
在这里插入图片描述

请求html文件

我们知道所有访问html都是通过输入url实现的,所以这些都是get,我们只需要判断浏览器发出的get请求的参数,以此来判断返回什么样的东西。

app.get('*',(req,res)=>{
  console.log(req)
  res.end()
})

res对象保存就是请求数据,我们输出看看有什么。
在这里插入图片描述
有很多,具体大家可以去看看官方文档,里面有详细介绍请求对象和返回对象的属性方法。

这里不再赘叙,直接使用req.path来判断请求数据。

我们这里先建立一个简单的html文档

<!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>http缓存</title>
        <script src='./index.js'></script>
    </head>
    <body>
        <image src='../images/01.png' width='100px' height='100px'>
    </body>
</html>

在这里插入图片描述
然后在app.js添加访问路径

// 监听请求
app.get('*',(req,res)=>{
  if(req.url!=='/favicon.ico') {
    let urlPath = req.path
    console.log(urlPath)
    if(urlPath == '/'||urlPath=='/index.html'||urlPath=='/index'||urlPath=='') {
      console.log('返回index.html')
      res.end(fs.readFileSync(`./public/html/index.html`))
    } 
    res.end('')
  }
  res.end()
})

我们看下输出结果
在这里插入图片描述

nodejs图片请求返回

的确返回了html文件,但是没有图片出现了问题。
是因为获取图片也是用过个get请求获取的,所以同样要对所有的图片进行判断返回。
因为图片格式都是固定的,所以可以统一处理。

    let reg = /((.png$)|(.jpg$)|(.gif$))/
    if(reg.test(urlPath)) {
      // 图片处理
      console.log(urlPath)
      try {// 提高健壮性
        let img = fs.readFileSync(`./public${urlPath}`)
        res.end(img)
      } catch(e) {
        res.end('404 not found')
      }
    }

这里使用正则判断请求路径是否为图片,如果是则返回对应文件。
这里记得设置路径是相对于app.js这个目录下的,所以需要适当的调整,具体看你的路径如何放。
也使用了try catch来捕捉错误,一但访问文件不存在就返回404

js文件请求返回

同理,js文件也是默认get请求的,我们用相同方法来进行设置。

最终结果

在这里插入图片描述

设置缓存

如何查看网络请求

在设置缓存头前,我们需要了解下chrom如何查看请求的。
f12进入控制台,找到network,点击左上角的黑色按钮,或者使用快捷键Ctrl+r,等待几秒就可以看到请求啦。
在这里插入图片描述

缓存策略

缓存机制这个是所有web开发默认的规划协议,不同世界,不同语言的前端开发者都可以通过阅读响应头来得知该文件的缓存机制。
所有的服务器都可以阅读请求头中携带的缓存头信息来进行响应。
目前所有的服务器框架都已经封装好了这部分的功能,开发者使用的时候并不用担心这些设置,一般都会采用最优的缓存策略。

  • Express的缓存策略
    Express中的图片缓存机制,采用的询问机制,避免了文件修改而客户端不知情,也避免了过期时间到了,但是文件还没有修改过,重新下载文件的请求浪费。
    对于js这种修改比较经常的文件则一般都是不缓存的,除非你特别的需求,你也可以通过设置响应头来使这些缓存保存到客户端本地去。(这里就体现了学习缓存策略的作用了)

缓存的存在大大减少了网络重复请求的冗杂性,给服务器降低了很多负担。

http1.0进化到http1.1,实现了能够使用缓存到更加灵活地使用缓存的进化。

1.0 时代:基于Pragma&Expires的缓存实现

参考资料

Pragma

当该字段值为“no-cache”的时候,会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。
Pragma属于通用首部字段,在客户端上使用时,常规要求我们往html上加上这段meta元标签。

<meta http-equiv="Pragma" content="no-cache">

它告诉浏览器每次请求页面时都不要读缓存,都得往服务器发一次请求才行。不过这种限制行为在客户端作用有限,作用仅仅是控制文件不使用缓存。
Pragma的优先级是高于Cache-Control (HTTP 1.1)的

Expire

有了Pragma来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间。

对http1.0而言,Expires就是做这件事的首部字段。Expires的值对应一个GMT(格林尼治时间),比如“Mon, 22 Jul 2002 11:12:01 GMT”(相当于·http1.1中的If-modified)来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。

在客户端我们同样可以使用meta标签来知会IE(也仅有IE能识别)页面(同样也只对页面有效,对页面上的资源无效)缓存时间:

<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">
1.1时代:Cache-Control:相对过期时间

针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间。

若报文中同时出现了ExpiresCache-Control,会以Cache-Control为准。换言之,这三者的优先级顺序为:Pragma -> Cache-Control -> Expires。

其实Cache-Control已经包含了1.0的2个语段的功能。

作为请求首部时,值可能有:

在这里插入图片描述

作为响应首部时,值可能有:
在这里插入图片描述

协商缓存

HTTP/1.1定义的 Cache-Control 头用来区分对缓存机制的支持情况, 请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。

  • express框架下的默认情况(对比缓存/协商缓存)Cache-Control: max-age=0
    对于图片来说,默认情况都是对比缓存的,并且后续的缓存会向服务器确认图片是否发生更新,如果是则更新缓存,否就会返回一个状态码’304 no modfie’
    我们不对图片进行处理,只是设置静态文件共享来测试看看。
  1. 第一次请求
    在这里插入图片描述
  2. 第二次请求如果服务器没有更新,则直接使用缓存。
    在这里插入图片描述

缓存过期机制

如果设置了不缓存这个值就没用了,设置了缓存验证的话,则会验证是否过期,如果过期则更新文件和缓存。
当然,如果文件修改了也会重新获取文件。
在这里插入图片描述
Last-Modified表示该文件修改的日期,会和服务器的文件进行比较,如果到了过期时间,但是文件依然没有改变,依旧发送请求的话就可能造成没必要的·带宽浪费,这里就可以先发送一个询问头,询问文件是否修改,如果修改了则更新文件,否则的话就延迟过期,继续使用本地缓存。

强制缓存

和对比缓存相比,强制缓存比较简单粗暴。
原理:给每一个文件设置一个过期事件,当浏览器访问的时候,会查看本地缓存,检验是否过期,如果过期则进行服务器请求更新缓存,如果没过期则直接使用。

Created with Raphaël 2.2.0 浏览器开始访问 找到对应的缓存文件 缓存验证 是否过期? 请求服务器更新缓存文件 使用缓存文件 yes no

强制缓存主要使用到的是Expires和Cache-control这里面的过期时间控制。
除此之外,也可以使用cache-control里面的来结合使用:

  • no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。

  • no-store:直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。

  • public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。

  • private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。

缓存权限设置

Cache-Control: private
Cache-Control: public

默认是private,这里设置的是中间件和CDN是否能够缓存,因为有些比较私密的如帐号密码,不能被这些中间件缓存,一般都是设置位private,对于一些公用的资源设置为public则可以提高用户的体验。
由于测试比较难表现,这里就不做测试了。

挑战1:nodejs自己设置Last-Modified的响应

默认情况下会添加这个数值,如果我们自己实现如何来做呢?
原理:利用fs阅读文件的修改日期,然后将值写进去即可
因为涉及异步读取所以需要使用到Promise,但是这里更符合async这个语法使用,所以将这部分功能封装出来。

let reg = /((.png$)|(.jpg$)|(.gif$))/
    if(reg.test(urlPath)) {
      try {
        let file = fs.readFileSync(`./public${urlPath}`)
        // 阅读时间
        readImage(`./public${urlPath}`,res,req,`image/${urlPath.slice(reg.exec(urlPath).index+1)}`,file)
      } catch(e) {
        res.end('404 not found')
      }
    
    }
    /*
    	封装读取文件时间
    */
async function readImage(url,res,req,imageType,file) {
  const time = await new Promise((resolve,reject)=>{
    fs.stat(url,function(err,data){
      //修改时间
      let mtime = new Date(Date.parse(data.mtime)).toUTCString()
      resolve(mtime)
    })
  }).then((str)=>{
    return str
  })
  res.writeHead(200,"OK",{
    "Content-Type":imageType,
     "Cache-Control":"no-cache",
    'Last-Modified':time
  })
  res.end(file)
}

查看结果
在这里插入图片描述
我们可以同时来看下请求头
在这里插入图片描述
里面包含的就是我们上次返回保存的数据,浏览器会自动将它添加到请求头。

挑战2:设置返回304和200的情况

express中就有一个API专门询问请求头信息

req.get('If-Modified-Since')

我们直接修改图片,并且调用看看输出情况:

if(reg.test(urlPath)) {
      console.log('请求图片',req.get('If-Modified-Since'))
      try {
        let file = fs.readFileSync(`./public${urlPath}`)
        // 阅读时间
        readImage(`./public${urlPath}`,res,req,`image/${urlPath.slice(reg.exec(urlPath).index+1)}`,file,req.get('If-Modified-Since'))// 添加一个参数 
      } catch(e) {
        res.end('404 not found')
      }
    }

// 阅读图片
// 更新lastTime
async function readImage(url,res,req,imageType,file,lastTime) {
  const time = await new Promise((resolve,reject)=>{
    fs.stat(url,function(err,data){
      //修改时间
      // console.log(reg.exec(urlPath))
      let mtime = new Date(Date.parse(data.mtime)).toUTCString()
      resolve(mtime)
      // return mtime
    })
  }).then((str)=>{
    return str
  })
  if(time!=lastTime) {// 比较是否最新文件
    res.writeHead(200,"OK",{
      "Content-Type":imageType,
       "Cache-Control":"public,max-age=0",
      'Last-Modified':time
    })
    res.end(file)
  } else {// 没有改变文件,只是返回信息
    res.writeHead(304,"No Modified",{
       "Content-Type":imageType,
       "Cache-Control":"public,max-age=0",
      'Last-Modified':time
    })
    res.end()
  }
}
  • 第一次访问,输出情况:
    在这里插入图片描述
    耗时:在这里插入图片描述

  • 第二次访问
    在这里插入图片描述
    耗时:在这里插入图片描述
    时间将近缩小了1/7
    浏览器能够识别304,自动使用缓存,图片正常显示。

  • 第三次访问,修改文件

在这里插入图片描述
能够正常识别,并且返回正确的图片。

ETAG和last-modefied的区别

  • Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。
  • last-modefied:保存的是文件修改的事件,性能优于Etag

总结

学习缓存机制的用处?

  1. 通过缓存的学习,我们能够了解缓存的机制是如何运行的,能够通过认识每一个请求头和响应头缓存信息。
  2. 框架会自然的封装默认的缓存机制,但是我们具体开发过程中对于一些特别的文件,需要特别的缓存的时候,我们就可以自己特定地设置这些缓存机制,提高性能。
  3. 从HTTP1.0开始,缓存的出现大大减低的服务器的负担,同时也需要一个协议来规定浏览器和服务器直接的信息交流。如:304浏览器会识别自动浏览并且使用缓存文件,服务器可以阅读请求头的If-Modified-Since来判断文件是否最新等。缓存只是一种开发者都默认和遵守的协议,我们每一个前端开发者都需要学习和了解它为何出现,为何要这样设计,如何来使用它。
  4. 这里使用到的响应头只有一个max-age=0,代表每次请求该文件都会发送一个请求到服务器,服务器会根据请求头的信息来判断如何响应。其他的响应头设置你们可以阅读参考资料来学习,最好通过自己一个一个来测试,比如如果设置了no-store的话,max-age是无效的,因为文件压根不会保存到缓存去。还有设置了no-cache就不能设置max-age等限制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值