掘金 后端 ( ) • 2024-05-08 10:37

血泪史

最近在学习强缓存和协商缓存,看了好多文档,千篇一律都说:服务器会根据前端请求头中携带的If-None-Match内容和后端的文档唯一标识Etag作对比,来决定返回304还是200状态码。

我就秉承着实事求是的态度,去验证,搭了前后端的代码(后面贴上前后端代码),顺带学习配了跨域处理。结果好家伙!!!确实是有那个先比对强缓存,强缓存过期后再比对协缓存的过程和逻辑,就是浏览器显示的状态码不对,从来不显示304,只显示200,只有document类型的数据会正常显示服务器返回304。

image.png

最可气的是,我问国内的AI,AI说是我的代码有问题,改了一个多小时,最后使用postman去直接发请求,绕过浏览器,手动为header添加一个If-None-Match,终于捕捉到服务端返回的状态码304。最终我得出结论:浏览器对服务器匹配到的协商缓存返回的304状态码做了一次转换,转换成了200,这种转换对某些类型的请求不转换,比如document。

为了再次验证我的结论,我去科学上网问gpt4,好家伙确实浏览器会对服务端返回的304给转换成200,比如css,图片,js文件也会进行状态码转换,但是document不会。

为什么要选择性进行状态码转换?

转换的原因

浏览器确实对协商缓存的返回结果的状态码做了一次转换。这主要是为了提供更好的用户体验。

304状态码是HTTP协议中的一部分,主要用于服务器和浏览器之间的通信。当服务器返回304状态码时,它告诉浏览器请求的资源没有被修改,可以使用缓存的版本。然而,对于用户来说,他们并不关心资源是从服务器获取的还是从浏览器缓存中获取的,他们只关心资源是否获取成功。因此,浏览器在从缓存中获取资源后,会把状态码改为200,表示资源获取成功。

此外,使用200状态码还有一个好处,那就是它可以让开发者更容易地理解和调试问题。如果浏览器直接显示304状态码,开发者可能会误以为资源没有成功获取,因为在HTTP协议中,只有200状态码表示请求成功。通过将状态码转换为200,浏览器可以避免这种误解。

总的来说,浏览器对协商缓存的返回结果的状态码做了一次转换,主要是为了提供更好的用户体验和方便开发者调试问题。

document不转换的原因

对于页面(document)类型的请求,浏览器通常会直接显示服务器返回的状态码,也就是304,这是因为这种类型的请求通常表示一个全新的页面加载,浏览器会尽可能地提供更多的信息,以便于开发者对页面加载过程有更全面的理解。

贴上我的前后端代码

大家有兴趣的自行验证,postman返回的是304,下面是postman截图。绝不骗人!我比AI实诚哈哈哈

image.png

前端就一个html文件,用vscode插件,live server打开的话默认会在浏览器中开启一个5500的前端服务端口打开

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="https://lib.baomitu.com/jquery/3.5.1/jquery.js"></script>
    <script>
      $.ajax({
        type: "get",
        url: "http://127.0.0.1:5560/example-resource",
        success(res) {
          console.log(res);
        }
      });
    </script>
  </body>
</html>

后端nodejs

const express = require("express");
const cors = require("cors");
const path = require("path");
const fs = require("fs");
const crypto = require("crypto");

const app = express();

//处理跨域
app.use(
  cors({
    origin: ["http://127.0.0.1:5500", "http://localhost:5500"] // 跨域白名单
  })
);
app.get("/a", (req, res) => {
  res.status(200).json({
    msg: "dddddd"
  });
});

//设置缓存倒计时10秒,可以自己改
const ONE_DAY_IN_SECONDS = 10;

app.get("/example-resource", (req, res) => {
  //读取根目录public文件夹下的example.txt路径
  const filePath = path.join(__dirname, "public", "example.txt");
  // 生成或读取资源内容,这里简化处理直接返回文件
  const fileContent = fs.readFileSync(filePath, "utf8");

  // 设置强缓存
  res.setHeader("Cache-Control", `public, max-age=${ONE_DAY_IN_SECONDS}`);

  // 生成ETag,用于协商缓存
  const etag = crypto.createHash("md5").update(fileContent).digest("hex");

  console.log(req.headers["if-none-match"]);
  res.setHeader("ETag", etag);

  // 检查协商缓存
  if (req.headers["if-none-match"] === etag) {
    console.log("match");
    // 如果ETag匹配,表示资源未更改,返回304
    res.status(304).send();
  } else {
    // 否则,返回资源内容
    res.send(fileContent);
  }
});

app.listen(5560, function () {
  console.log("CORS-enabled web server listening on port 5560");
});

有收获的话,可以点个赞哟