在做内网穿透项目时,经常会遇到同一个资源被反复请求的问题。比如你用 frp 搭了个本地 Web 服务,同事每次打开页面都要重新加载一遍图片和 JS 文件,速度慢还占带宽。这时候,HTTP 缓存机制就能派上大用场。
缓存是怎么工作的?
当浏览器第一次请求一个资源,比如 /static/main.js,服务器返回文件的同时会带上一些缓存头信息。浏览器把这些内容存下来,下次再要这个文件时,先看看本地有没有,能不能直接用,不用再跑一趟服务器。
常见的缓存控制字段有两个:Expires 和 Cache-Control。Expires 是 HTTP/1.0 的产物,指定一个具体的过期时间:
Expires: Wed, 21 Oct 2025 07:28:00 GMT
但问题在于它依赖客户端时间,如果用户电脑时间不准,缓存就可能失效或滥用。所以现在更常用的是 Cache-Control,它是 HTTP/1.1 的标准,更灵活:
Cache-Control: max-age=3600
这表示资源最多缓存一小时。只要在这期间再次请求,浏览器直接用本地副本,连网络都不走,响应速度飞起。
强缓存与协商缓存的区别
max-age 这类规则属于“强缓存”。只要没过期,浏览器连问都不问服务器,直接拿本地的用。但一旦过期,就得跟服务器确认一下有没有更新。
这时候就进入“协商缓存”阶段。浏览器不会重新下载整个资源,而是带上上次的标识去问:我这儿有个旧版本,还是最新的吗?关键字段有两个:Last-Modified 和 ETag。
服务器在首次响应时会附带最后修改时间:
Last-Modified: Mon, 19 Jul 2024 10:30:00 GMT
浏览器下次请求时,加上这个头:
If-Modified-Since: Mon, 19 Jul 2024 10:30:00 GMT
服务器比对后发现没改,就回个 304 Not Modified,告诉浏览器继续用老的。数据省了,延迟也低了。
ETag 是另一种更精细的机制,基于资源内容生成指纹。比如文件变了,哪怕时间戳一样,ETag 也会变:
ETag: "abc123"
请求时发:
If-None-Match: "abc123"
服务器对比后决定是否返回新内容。适合内容频繁变动但时间戳不易判断的场景。
内网穿透中的实际应用
你在家里搭了个 Web 管理后台,通过 Nginx + frp 做内网穿透对外暴露。每次加载都慢吞吞,其实很大一部分原因是静态资源没缓存。
可以在 Nginx 配置里加上缓存策略:
location ~* \.js$ {
expires 1h;
add_header Cache-Control "public";
}
location ~* \.png$ {
expires 1d;
add_header Cache-Control "public";
}
这样一来,JS 文件缓存一小时,图片缓存一天。用户第二次访问时,大部分请求变成 304 或直接读本地,体验明显提升。
注意一点:HTML 文件通常不要设长缓存,因为它控制着页面结构,一旦更新了,你还指望用户立刻看到新界面。可以这样设:
location = /index.html {
add_header Cache-Control "no-cache";
}
确保每次都能拿到最新入口文件。
合理利用 HTTP 缓存机制,不仅能减轻内网服务的压力,还能让远程访问像本地一样快。别再让重复请求拖慢你的穿透服务了。