http报文和浏览器缓存机制
1. 请求报文
HTTP 请求由三部分组成:请求行、 请求头和请求正文
1.1请求行
请求的第一行是“方法 URL 协议/版本”,并以 回车换行作为结尾。请求行以空格分隔。格式如下:
POST /index.php HTTP/1.1
以上代码中“GET”代表请求方法,“/index.php”表示URI,“HTTP/1.1代表协议和协议的版本
1.2 请求头
一些常用的请求头信息
- Host
客户端指定自己想访问的WEB服务器的域名/IP 地址和端口号。
- Connection
客户端(浏览器)想要优先使用的连接类型,keepalive(当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的 网页,会继续使用这一条已经建立的连接)
- Accept
可接受的响应内容类型(Content-Types)。 Accept: text/plain
- Accept-Charset
可接受的字符集 Accept-Charset: utf-8
- Accept-Encoding
浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法。 Accept-Encoding: gzip, deflate
- Accept-Language:
可接受的响应内容语言列表。 Accept-Language: en-US
- User-Agent:
告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本.
- Cookie:
很重要的header,客户端的Cookie就是通过这个报文头属性传给服务端的!
- If-Modified-Since
把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中。
例如:If-Modified-Since: Thu, 09 Feb 2012 09:07:57 GMT。
- If-None-Match
If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request 中加入If-None-Match信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag. 使用这样的机制将提高网站的性能
例如: If-None-Match: “03f2b33c0bfcc1:0”
- Pragma
防止页面被缓存, 在HTTP/1.1版本中,它和Cache-Control:no-cache作用一模一样
Pargma只有一个用法, 例如: Pragma: no-cache
注意: 在HTTP/1.0版本中,只实现了Pragema:no-cache, 没有实现Cache-Control
- Cache-Control
这个是非常重要的规则。 这个用来指定Response-Request遵循的缓存机制。如以下设置,相当于让服务端将对应请求返回的响应内容不要在客户端缓存:Cache-Control: no-cache
- Content-Length
以8进制表示的请求体的长度 Content-Length: 348
- Content-Type
请求体的MIME类型 (用于POST和PUT请求中) Content-Type: application/x-www-form-urlencoded
- Referer
表示浏览器所访问的前一个页面,可以认为是之前访问页面的链接将浏览器带到了当前页面。Referer其实是Referrer这个单词,但RFC制作标准时给拼错了,后来也就将错就错使用Referer了。 Referer: http://itbilu.com/nodejs
- Upgrade
要求服务器升级到一个高版本协议。 Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
- Via
告诉服务器,这个请求是由哪些代理发出的。 Via: 1.0 fred, 1.1 itbilu.com.com (Apache/1.1)
2. 响应报文
HTTP 响应也是由三个部分组成,分别是:状态行、消息报头和响应正文
2.1 状态行
状态行由协议版本、数字形式的状态代码,及相应的状态描述组成,各元素之间以空格分隔,结尾时回车换行符,格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
HTTP-Version 表示服务器 HTTP 协议的版本,Status-Code 表示服务器发回的响应代码,Reason-Phrase 表示状态代码的文本描述,CRLF 表示回车换行。例如:
HTTP/1.1 200 OK (CRLF)
1> 响应状态码
1xx 消息,一般是告诉客户端,请求已经收到了,正在处理,别急...。
2xx 处理成功,一般表示:请求收悉、我明白你要的、请求已受理、已经处理完成等信息。
3xx 重定向到其它地方。它让客户端再发起一个请求以完成整个处理。
4xx 处理发生错误,责任在客户端,如客户端的请求一个不存在的资源,客户端未被授权,禁止访问等。
5xx 处理发生错误,责任在服务端,如服务端抛出异常,路由出错,HTTP版本不支持等。
2> 常见的响应状态码
200 OK 处理成功!
301 永久重定向 Location响应首部的值仍为当前URL,因此为隐藏重定向;
302 临时重定向 显式重定向, Location响应首部的值为新的URL。
303 See Other redirect到其它的页面,目标的URL通过响应报文头的Location告诉你。
304 Not Modified 告诉客户端,你请求的这个资源至你上次取得后,并没有更改,你直接用你本地的缓存
400 Bad Request 客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
403 Forbidden 服务器收到请求,但是拒绝提供服务。
404 Not Found 你最不希望看到的,即找不到页面。
500 Internal Server Error 看到这个错误,你就应该查查服务端的日志了,肯定抛出了一堆异常。
503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
2.2 响应头
常见的响应头
- Access-Control-Allow-Origin
指定哪些网站可以跨域源资源共享 Access-Control-Allow-Origin: *
- Allow
对于特定资源的有效动作; Allow: GET, HEAD 固定
- Cache-Control
通知从服务器到客户端内的所有缓存机制,表示它们是否可以缓存这个对象及缓存有效时间。其单位为秒 Cache-Control: max-age=3600 固定
- Connection
针对该连接所预期的选项 Connection: close 固定
- Content-Encoding
响应资源所使用的编码类型。 Content-Encoding: gzip
- Content-Language
响就内容所使用的语言 Content-Language: zh-cn
- Content-Length
响应消息体的长度,用8进制字节表示 Content-Length: 348
- Content-Location
所返回的数据的一个候选位置 Content-Location: /index.htm
- Content-Type
当前内容的MIME类型 Content-Type: text/html; charset=utf-8
- Date
此条消息被发送时的日期和时间(以RFC 7231中定义的"HTTP日期"格式来表示) Date: Tue, 15 Nov 1994 08:12:31 GMT
- ETag
对于某个资源的某个特定版本的一个标识符,通常是一个 消息散列 ETag: "737060cd8c284d8af7ad3082f209582d"
- Expires
指定一个日期/时间,超过该时间则认为此回应已经过期 Expires: Thu, 01 Dec 1994 16:00:00 GMT
- Last-Modified
所请求的对象的最后修改日期(按照 RFC 7231 中定义的“超文本传输协议日期”格式来表示) Last-Modified: Dec, 26 Dec 2015 17:30:00 GMT
- Location
用于在进行重定向,或在创建了某个新资源时使用。 Location: http://www.itbilu.com/nodejs
- Pragma
与具体的实现相关,这些响应头可能在请求/回应链中的不同时候产生不同的效果 Pragma: no-cache
- Server
服务器的名称 Server: nginx/1.6.3
- Set-Cookie
设置HTTP cookie Set-Cookie: UserID=itbilu; Max-Age=3600; Version=1
- Status
通用网关接口的响应头字段,用来说明当前HTTP连接的响应状态。 Status: 200 OK
- Transfer-Encoding
用表示实体传输给用户的编码形式。包括:chunked、compress、 deflate、gzip、identity。 Transfer-Encoding: chunked
- Upgrade
要求客户端升级到另一个高版本协议。 Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
- Vary
告知下游的代理服务器,应当如何对以后的请求协议头进行匹配,以决定是否可使用已缓存的响应内容而不是重新从原服务器请求新的内容。 Vary: *
- Via
告知代理服务器的客户端,当前响应是通过什么途径发送的。 Via: 1.0 fred, 1.1 itbilu.com (nginx/1.6.3)
3. 浏览器缓存
3.1 缓存的优点:
1> 服务器响应更快:因为请求从缓存服务器(离客户端更近)而不是源服务器被相应,这个过程耗时更少,让服务器看上去响应更快。
2> 减少网络带宽消耗:当副本被重用时会减低客户端的带宽消耗;客户可以节省带宽费用,控制带宽的需求的增长并更易于管理。
3.2 缓存工作原理
3.2.1 首先了解下缓存的相关字段
通用首部: 请求报文和响应报文双方都会使用的首部
字段名 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Pragma | http1.0产物,值为no-cache时禁用缓存 |
请求首部
字段名 | 说明 |
---|---|
if-Match | 比较ETag(资源计算得出一个唯一标志符,比如md5标志)是否一致 |
if-None-Match | 比较ETag(资源计算得出一个唯一标志符,比如md5标志)是否【不一致】 |
if-Modified-Since | 比较资源最后更新时间(Last-Modified的值)是否一致 |
if-Modified-Since | 比较资源最后更新时间(Last-Modified的值)是否【不一致】 |
响应首部
字段名 | 说明 |
---|---|
ETag | 资源的匹配信息,例如Etag: "5d8c72a5edda8d6a:3239" |
实体首部:包含在请求报文和响应报文中的实体部分所使用的首部。
字段名 | 说明 |
---|---|
Expires | http1.0产物,实体主体过期时间 |
Last-Modified | 资源最后修改时间 |
3.2.2 http1.0时代缓存方式
在 http1.0 时代,给客户端设定缓存方式可通过两个字段——Pragma和Expires来规范。虽然这两个字段早可抛弃,但为了做http协议的向下兼容,你还是可以看到很多网站依旧会带上这两个字段。
1、Pragma
实现禁用缓存,当该字段值为no-cache的时候(事实上现在RFC中也仅标明该可选值),会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。
Pragma的优先级是高于Cache-Control的。
Pragma -> Cache-Control
2、Expires
实现启用缓存和定义缓存时间, Expires的值对应一个GMT(格林尼治时间),比如Mon, 22 Jul 2002 11:12:01 GMT来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。
如果Pragma头部和Expires头部同时存在,则起作用的会是Pragma
3.2.3 http1.1时代缓存方式
针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间。
注意:若报文中同时出现了 Expires 和 Cache-Control,则以 Cache-Control 为准。
也就是说优先级从高到低分别是
Pragma -> Cache-Control -> Expires
Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了 Cache-Control 的格式为:
"Cache-Control" ":" cache-directive
作为请求首部时,cache-directive 的可选值有:
作为响应首部时,cache-directive 的可选值有:
这里要注意下:
- no-cahce并不是表示无缓存,而是指使用缓存一定要先验证新鲜度
- response header的
no-cache
和max-age=0
和request header的max-age=0
的作用是一样的:都要求在使用缓存之前验证新鲜度
Cache-Control 允许自由组合可选值,例如:
Cache-Control: max-age=3600, must-revalidate
它意味着该资源是从原服务器上取得的,且其缓存(新鲜度)的有效时间为一小时,在后续一小时内,用户重新访问该资源则无须发送请求。 当然这种组合的方式也会有些限制,比如 no-cache 就不能和 max-age、min-fresh、max-stale 一起搭配使用。
3.2.3 http1.1时代的补充
如果客户端向服务器发了请求,那么是否意味着一定要读取回该资源的整个实体内容呢?如果客户端现在存有的缓存文件,其实跟自己所有的文件是一致的,其实是可以复用的。为了让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,Http1.1新增了几个首部字段来做这件事情。
首先,了解下etag 和 last-modified这两个关键点,他们有如下的对应关系。
etag —— if-none-match
etag —— If-Match
last-modified —— if-modified-since
last-modified —— If-Unmodified-Since
1、Last-Modified
服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码,内容为空,这样就节省了传输数据量 。如果两个时间不一致,则服务器会发回该资源并返回200状态码,和第一次请求时类似。这样保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。一个304响应比一个静态资源通常小得多,这样就节省了网络带宽。
至于传递标记起来的最终修改时间的请求报文首部字段一共有两个:
If-Modified-Since: Last-Modified-value
该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。
当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。
If-Unmodified-Since: Last-Modified-value
该值告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。
Last-Modified 存在一定问题,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。这个问题可以ETag来解决
2、ETag
为了解决上述Last-Modified可能存在的不准确的问题,Http1.1还推出了 ETag 实体首部字段。 服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。例如:
Etag: "5d8c72a5edda8d6a:3239"
客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。
如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
那么客户端是如何把标记在资源上的 ETag 传回给服务器的呢?请求报文中有两个首部字段可以带上 ETag 值:
If-None-Match: ETag-value
告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。
If-Match: ETag-value
告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。
3.3 火狐浏览器缓存分析实例
3.3.1 第一次访问
请求头中没有关于缓存的字段,响应200,并且响应头中则返回了ETag字段。
ETag W/"651c64-iihm0BriQfynDiDtEbdScsPqrwA"
3.3.2 地址栏直接回车
请求头中带上了关于缓存的字段(If-None-Match),并且带上了第一次Etag返回的值
If-None-Match W/"651c64-iihm0BriQfynDiDtEbdScsPqrwA"
响应 304,并且继续返回Etag字段
3.3.3 按f5
请求头中带上了关于缓存的字段(If-None-Match,Cache-Control),并且带上了第一次Etag返回的值
Cache-Control max-age=0
If-None-Match W/"651c64-iihm0BriQfynDiDtEbdScsPqrwA"
响应 304,并且继续返回Etag字段
和直接在地址栏回车的区别是加了Cache-Control max-age=0
头部,意思是告诉浏览器要进行新鲜度的检查,我们发现检查后发现时最新资源,所有返回了304
。
3.3.4 ctrl + f5
请求头中带上了关于缓存的字段(Cache-Control,Pragma)
Cache-Control no-cache
Pragma no-cache
响应 200,并且返回Etag字段
这个操作的意思是我不光要最新资源,并且中间的各级缓存(不只是浏览器缓存,可能还有中间的prxoy缓存等)都不要返给我。
3.3.5 总结:
浏览器的刷新访问行为是有自己的一套准则的,以上是我试验的火狐浏览器,大家可以测试下自己的浏览器,其实我们可以认识到不同的操作,会有不同的请求策略,一些静态资源浏览器可能会为其增加新的请求头部信息,以此来达到缓存的策略。
参考资料:
https://www.jianshu.com/p/b1ea16450fff
https://cnbin.github.io/blog/2016/02/20/http-qing-qiu-,-xiang-ying-,-huan-cun/