1. 引言
今天刚刚学习到了有关网络状态码,了解到了一个腾讯前端的面试题,关于304及其实现。趁热打铁,写文章以记录。可供参考。
2. 304介绍
- 大白话说:你第二次向网站请求的系列小电影并没有更新,网站返回它告诉你没有更新,只能继续回顾经典
- 304 Not Modified:客户端有缓冲的文件并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
- 如果客户端在请求一个文件的时候,发现自己缓存的文件有 Last Modified(最近修改时间) ,那么在请求中会包含 If Modified Since ,这个时间就是缓存文件的 Last Modified 。因此,如果请求中包含 If Modified Since,就说明已经有缓存在客户端。只要判断这个时间和当前请求的文件的修改时间就可以确定是返回 304 还是 200 。对于静态文件,例如:CSS、图片,服务器会自动完成 Last Modified 和 If Modified Since 的比较,完成缓存或者更新。但是对于动态页面,就是动态产生的页面,往往没有包含 Last Modified 信息,这样浏览器、网关等都不会做缓存,也就是在每次请求的时候都完成一个 200 的请求。
- 一般的大的站点的图片服务器都有实现HTTP 304 缓存功能。
- 这个 304 状态一般主要在用户刷新页面(F5键)的时候触发,当用户在刷新页面的时候,因为原来的页面里的很多资源已经缓存过,客户端的浏览器已经记录了资源的最后更新时间(Last Mod),所以在用户刷新页面的时候,会向服务器提交一个字段:If-Modified-Since: Wed,
- 这个时候,服务器端的程序先取得这个字段的值,然后与服务器上的图片最后修改时间对比,如果相同,就直接返回 304 Not Modified ,然后停止。 设计的原因:
- 一切缓存皆为优化,提升体验。减少不必要的资源传输可以提升页面加载速度,
- 减少资源传输达到节省带宽的目的。 第一次:
第二次:
3. 场景设计 本文采用两种方式实现
(一)lastModified方式
- 运行环境:vs code+npm 7.0.8 +node 15.2.0
- 代码文件与static同级
- 根目录运行
npm init -y初始化项目,npm i express安装expres服务框架。假设有一个静态资源demo.js在 src/static目录下
- 基础服务配置
const express=require('express')//框架引入
const app=express()//设置服务对象
const fs=require('fs')//引入fs node.js的fs模块获取文件状态信息
const port=3000;//3000端口启动
const path=require('path')//引入path模块配置资源路径
const md5=require('md5')//md5加密方式完成304设计
- 取资源和资源信息
const jsPath = path.resolve(__dirname, './static/js/demo.js');
//resolve方法将绝对路径和目标路径串接 并且解析成路径树,让机器理解
let status = fs.statSync(jsPath);//statSync可以获取路径文件的各种信息
console.log(status, '--------------');
let lastModified = status.mtime.toUTCString();//status里的metime属性表示最后修改时间,
//用toUTCString来将世界时转换成字符串,便于比对
let count = fs.readFileSync(jsPath);//同步读取路径里的文件内容
console.log(lastModified,'+++++')
- 比对修改字段,设置返回头,状态码
if(lastModified==req.headers('if-modified-since')){//当前资源的修改时间和请求头的修改时间对比
res.writeHead(304,'NO Modified')
res.end();//为空 不需要有响应 。
return;
}
res.setHeader('Cache-Control','public,max-age=30')//设置浏览器缓存,
//public属性表示任何情况下都应该缓存该资源,设置了最长缓存时间
res.setHeader('Last-Modified',lastModified)//响应头携带文件修改时间给到用户端
res.writeHeader(200,'OK')//第一次请求成功状态码是200
res.end(count)//返回文件内容
关于cache-control :blog.csdn.net/u012375924/…
- 启动监听
app.listen(port,()=>{
console.log('listen ')
})
(二)md5 ETag方式
npm i md5引入MD5加密- md5将文件内容编成32位的码,内容的改变会引起编码的改变
- 在原代码基础上新增 Etag,Etag是 Entity tag的缩写,可以理解为“被请求变量的实体值”,Etag是服务端的一个资源的标识.通过
If-None-Match请求头带上了之前服务端返回的Etag的值。服务端收到第二次请求的时候,发现携带了If-None-Match字段,就重新计算服务器对应资源的Etag,如果二者匹配了,就认为资源没有发生变化,直接给客户端相应304,让客户端读取缓存中的数据。
let etag=md5(count);
if(req.header['if-none-match']==etag){
res.setHeader('ETag',etag)
res.writeHead(304,'NOt Modified')
res.end()
return;
}
图中两个标志字段一致,状态码是304
关于Etag:www.cnblogs.com/happy4java/…
- 源代码
const express=require('express')
const app=express()
const fs=require('fs')
const port=3000;
const path=require('path')
const md5=require('md5')
//请求demo.js
//路由是/demo.js
// fs把它读取并发送流
app.get('/', (req, res) => {
res.send(`<!DOCTYPE html>
<html lang="en">//字符串模板设计了网页
<head>
<title>Document</title>
</head>
<body>
demo1
<script src="/demo.js">//在src里请求资源 /demo.js
</script>
</body>
</html>
`)
})
app.get('/demo.js',(req,res)=>{
const jsPath = path.resolve(__dirname, './static/js/demo.js');
let status = fs.statSync(jsPath);
let lastModified = status.mtime.toUTCString();
let count = fs.readFileSync(jsPath);
let etag=md5(count);
console.log(lastModified,'+++++')
if(req.header['if-none-match']==etag){
res.setHeader('ETag',etag)
res.writeHead(304,'NOt Modified')
res.end()
return;
}
// if(lastModified==req.headers('if-modified-since')){
// res.writeHead(304,'NO Modified')
// res.end();//为空 不需要有响应 。
// return;
// } lastModified方法
res.setHeader('Cache-Control','public,max-age=30')
res.setHeader('Last-Modified',lastModified)
res.setHeader('ETag',etag)
res.writeHeader(200,'OK')
res.end(count)
})
app.listen(port,()=>{
console.log('listen ')
})
小结
- 状态码出现频率高,但数量多,需要挑重点,联系应用场景
- 请求头和响应头的几个常考属性可以多加了解,设置的方法应多多联系
- 网页启动一个node服务的流程要熟悉
*本人大三,正寻实习,与君共勉。寥有拙作,万望指正。*