草庐IT

HTTP缓存看这一篇就够了

大转转FE 2023-03-28 原文

​前言

HTTP缓存机制是优化web性能的重要手段,也是优化用户体验的重要一环。了解和熟悉HTTP缓存机制也成为了前端工作者必不可少的技能。

HTTP缓存是用于临时存储网页资源(如HTML页面、图像等),以减少服务器延迟的一种技术。HTTP缓存系统会保存下通过这套系统的文档的副本;如果满足某些条件,则可以由缓存满足后续请求。HTTP缓存系统既可以指设备,也可以指计算机程序。

一、HTTP缓存的类别

HTTP缓存可分为强制缓存和协商缓存。

强制缓存:直接使用客户端缓存,不从服务器拉取新资源,也不验证缓存资源是否过期。返回的状态码为200(OK)。

协商缓存:通过服务器验证资源有效性,资源有效则返回304(Not Modified),资源失效则返回最新的资源文件。

HTTP主流的有三个版本:HTTP/1.0、HTTP/1.1、HTTP/2.0。其中HTTP/1.0和HTTP/1.1的应用最为广泛。HTTP/2.0因对缓存机制的改动有别于HTTP/1.0和HTTP/1.1,因此HTTP/2.0相关内容会在文末总结部分进行介绍。HTTP/1.0与HTTP/1.1可根据缓存类别区分如下:

HTTP版本

强制缓存

协商缓存

HTTP/1.0

Expires

Last-Modified

HTTP/1.1

Cache-Control

ETag

二、主流的HTTP缓存参数

2.1 强制缓存

2.1.1 HTTP/1.0 - Expires

Expires​的值为服务端返回的到期时间,是一个GMT​(格林尼治标准时间)绝对时间,如:Tue, 17 Jan 2023 03:48:45 GMT​。下一次请求时,客户端判断当前系统GMT​时间是否小于缓存携带的GMT时间。若小于,直接使用缓存数据,否则从服务器请求新的文件。

不过Expires存在的问题也显而易见。

首先,使用客户端获取的GMT​时间与服务器GMT时间作比较,如果客户端主动修改了系统时间,就会出现缓存命中的误差。

其次,GMT时间是基于格林尼治天文台测算时间后,每隔一小时想全世界发放调时信息。观测本身存在的误差以及非实时的同步机制,都可能会导致出现缓存命中的误差。

所以在HTTP/1.1版本中,使用Cache-Control​中的max-age替代。

2.1.2 HTTP/1.1 - Cache-Control

Cache-Control 是HTTP/1.1中重要的缓存规则。它可以在HTTP请求头和响应头中使用,提供了多样化的配置参数。同时也可以适用于更广泛的复杂场景。

指令格式具有以下有效规则:

  • 不区分大小写,但建议使用小写。
  • 多个指令以逗号分隔。
  • 具有可选参数,可以用令牌或者带引号的字符串语法。
常用的指令如下:

  • no-store:不使用任何形式的缓存。具有HTTP缓存的最高优先级。

  • no-cache:不使用强制缓存。每次进行响应前都向服务器进行缓存有效性验证。

  • public:公共缓存。任何从源服务器到客户端中的每个节点都可以对资源进行缓存。
  • private:私有缓存。仅客户端可以对资源进行缓存。
  • max-age:客户端缓存存储的最长时间,单位秒。判断的优先级高于Expires​,客户端会判断资源已缓存的时长是否小于设置的max-age​时长。是则直接使用缓存数据,否则会进行Expires的判断流程。

  • s-maxage:代理缓存服务器最长的缓存时间,单位秒。优先级高于max-age和Expires,仅适用于缓存服务器。

2.2 协商缓存

客户端缓存失效后会向服务器进行进行缓存有效性验证,这个缓存有效性验证的过程就是协商缓存​。若资源有效,则返回304(Not Modified)​。客户端拿到304状态码后会再从本地缓存中获取资源。整个请求响应过程是与无缓存流程一样的。相对于无缓存流程的优势在于仅响应状态码后,客户端直接从本地缓存获取文件,而无需进行文件下载。减少了网络响应的文件大小,进而加快了网络响应速度。

协商缓存的请求和响应是需要相互配合的,可组合使用。如下表:

版本/阶段

请求

响应

HTTP/1.0

If-Modified-Since/If-Unmodified-Since

Last-Modified

HTTP/1.1

If-None-Match/If-Match

ETag

协商缓存会先判断请求头中是否携带no-store。如果携带,则直接返回最新的服务器文件。

2.2.1 HTTP/1.0 - Last-Modified

客户端第一次向服务器请求资源时,服务器会返回资源。同时会在响应头中添加Last-Modified​字段来表明资源的最后修改时间。当客户端强制缓存失效后,会重新向服务器进行缓存有效性验证。在验证的请求头中,会添加If-Modified-Since​字段。服务器会对请求头中的If-Modified-Since​和其存储的资源Last-Modified​进行比较。若If-Modified-Since​的时间不小于Last-Modified​,则资源有效,返回304(Not Modified)​。否则返回资源本身,并且重新记录文件的Last-Modified。

Last-Modified​:响应头携带的资源最后修改时间。格式为last-modified:GMT。

如:last-modified: Sat, 14 Jan 2023 08:40:00 GMT

If-Modified-Since​:请求头携带的资源是否在某个时间后有修改。服务器会使用此值和其本身存储的时间进行比较。格式为:If-Modified-Since:GMT​。只可以用在 GET​ 或 HEAD请求中。

If-Unmodified-Since​:请求头携带的资源是否在某个时间后没有修改。格式为:if-unmodified-since:GMT​ 。有别于If-Modified-Since,If-Unmodified-Since​被用于POST​或其他非简单请求。如果在If-Unmodified-Since​指定的时间内有过修改,则返回412(Precondition Failed)。

Last-Modified也是存在严重问题的。

首先,Last-Modified只关注文件的最后修改时间,和文件内容无关。所以文件内容在修改后又重新恢复,也会导致文件的最后修改时间改变。此时客户端的请求则无法使用缓存。

其次,Last-Modified​只能监听到秒级别的文件修改,如果文件在1秒内进行了多次修改,那么响应头返回的Last-Modified​的时间是不变的。此时客户端因接收到响应304,会导致资源无法及时更新,使用缓存的资源文件。

因此HTTP/1.1使用了ETag来进行缓存协商。

2.2.1 HTTP/1.1 - ETag

为了解决上述Last-Modified​可能存在的不准确的问题,HTTP/1.1推出了新的响应字段ETag​来进行协商缓存。ETag​的优先级比Last-Modified高。

服务器接收到浏览器请求后,会先进行If-None-Match与ETag​值的比较。若相等,则资源有效,返回304(Not Modified)​。否则返回资源本身,并且重新记录文件的ETag。

ETag​:响应头携带的资源标识符。格式为ETag:ETag-value可由服务器自行设置算法生成,通常是使用内容的散列或简单的使用版本号。

如:etag: "I82YRPyDtSi45r0Ps/eo8GbnDfg="

If-None-Match​:请求头携带的是否无匹配文件字段。优先级高于Last-Modified​。当服务器没有任何资源的ETag​与请求头携带的ETag值完全一样时,返回最新的资源,否则服务器会返回304。

: if-none-match:"I82YRPyDtSi45r0Ps/eo8GbnDfg="

If-Match​:请求头携带的是否存在匹配文件字段。对于简单请求需要搭配 Range​首部使用。对于非简单请求,如PUT​,可用于上传ETag。

: if-match:"I82YRPyDtSi45r0Ps/eo8GbnDfg="

三、总结

通过前文,我们了解到 HTTP 缓存主要分:强制缓存、协商缓存。强制缓存由Exipres(HTTP/1.0)、 Cache-Control(HTTP/1.1)控制。客户端直接读本地缓存,不会再跟服务器端交互,状态码 200。

协商缓存由 Last-Modified / If-Modified-Since(HTTP/1.0), Etag /If-None-Match(HTTP/1.1)进行有效性验证,每次请求需要让服务器判断一下资源是否更新过,从而决定客户端是否使用缓存,如果是,则返回 304,否则返回最新文件。

HTTP/2.0中设计了新的缓存方式,服务器推送(Push Server)。有别于强制缓存和协商缓存,属于推送缓存。这种新的缓存方式主要是为了解决客户端缓存时效性的问题,即还没有收到客户端的请求,服务器就把各种资源推送给客户端。比如,客户端只请求了a.html,但是服务器把a.html、a.css、a.png全部发送给客户端。这样的话,只需要一次请求,客户端就更新了所有文件的缓存,提高了缓存的时效性。

参考:

GMT(维基百科):https://en.wikipedia.org/wiki/Greenwich_Mean_Time

HTTP缓存(MDN):https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching


有关HTTP缓存看这一篇就够了的更多相关文章

  1. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  2. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

  3. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  4. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. ruby-on-rails - Rails - 从命名路由中提取 HTTP 动词 - 2

    Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba

  7. ruby-on-rails - Heroku 吃掉了我的自定义 HTTP header - 2

    我正在使用Heroku(heroku.com)来部署我的Rails应用程序,并且正在构建一个iPhone客户端来与之交互。我的目的是将手机的唯一设备标识符作为HTTPheader传递给应用程序以进行身份​​验证。当我在本地测试时,我的header通过得很好,但在Heroku上它似乎去掉了我的自定义header。我用ruby​​脚本验证:url=URI.parse('http://#{myapp}.heroku.com/')#url=URI.parse('http://localhost:3000/')req=Net::HTTP::Post.new(url.path)#boguspara

  8. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  9. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

  10. ruby - 如何使用 Ruby HTTP::Net 处理 404 错误? - 2

    我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。

随机推荐