前言
由于HTTP无状态协议,有时需要保存资源状态信息或者用户信息,根据状态判断是否需要重新请求资源或者在相同域名和端口下读取用户信息。
缓存可以使浏览器重用已获取的资源和保存用户信息,优化静态资源的加载速度以及加速页面的渲染速度。
缓存包括很多方面,这里仅简要介绍下数据缓存Cookie及相关设置和浏览器header缓存匹配策略(ETag, Cache-Control)
Cookie
一般Cookie是由首次请求时, 服务器在HTTP的响应头通过Set-Cookie给客户端的一串字符串及metadata,比如用户信息或资源状态信息或对缓存的控制信息等,浏览器收到响应后,会将Cookie保存在客户端一段时间,然后客户端每次访问的相同域名的网页时,浏览器会按照一定的原则(浏览器缓存策略)将Cookie发送给服务器。
Cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围
== Server -> User Agent ==
Set-Cookie:SID=31d4d96e407aad42; Path=/; Secure; HttpOnly
Set-Cookie: lang=en-US; Path=/; Domain=example.com
Set-Cookie: Expires=Wed, 09 Jun 2021 10:18:14 GMT
##path:/设置浏览器即UA下一次请求每个路径都带上缓存
##Domain: example.com每个example.com下的子域名都带上缓存
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42; lang=en-US
注意:
- 客户端可以对Cookie进行修改,通过控制台或者Javascript脚本。
- Cookie默认在用户关闭页面后就失效(保存在内存中),服务器可以通过cache-control设置强缓存-Cookie缓存位置及时间等(保存在硬盘中)。
- 可通过Set cookie指定expires为过去的一个值,使指定路径的Cookie失效。
- Cookie大小在4k左右, 域名、端口相同即可共享(CSRF)
- 可通过document.cookie获取cookie信息
-
Session
考虑到客户端可以对cookie中的用户信息等进行修改,为了提高安全性,首次请求时服务器仅将一个随机数生成的的SessionID通过Set-Cookie发送给客户端,而具体的哈希表则保存在服务器,即用户的状态信息等保存在服务端的内存中,客户端再次请求时,仅通过SessionID获取其对应的信息,服务器通过读取相应的SessionID返回对应用户的隐私信息。
##首次请求响应
HTTP/1.1 200 OK
Date: Sun, 17 Sep 2017 13:16:06 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache
Pragma: no-cache
Set-Cookie: SESSIONid=v666iuanetgmcv0couvmnf3jg4
Content-Length: 20
Content-Type: text/html
##接下来的请求
GET /clients/ HTTP/1.1
Host: mycompany.com
Accept: text/html
Cookie: SESSIONid=v666iuanetgmcv0couvmnf3jg4
-
Cookie & Session 区别
- Cookie保存在客户端浏览器中,而Session保存在服务器上
- Cookie可以设置路径path及domain属性值,使不同路径,不同二级域名下共享。
浏览器Header缓存策略
- 浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
- 如果没有命中强缓存,浏览器发送一个请求到服务器,通过last-modified或etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
- 如果前面两者都没有命中,直接从服务器加载资源
-
Cache-Control--强缓存
Cache-Control是HTTP/1.1中引入的Header设置,用于控制网页缓存。它有如下的选项:
1、public : 客户端和代理服务器(CDN)都可缓存
2、private : 只在客户端缓存,cache-control的默认值
3、no-cache: 不论是客户端缓存还是服务器缓存,在使用它以前必须用缓存里的值来重新验证
4、no-store: 不允许被缓存
5、max-age=<seconds> : 设置缓存时间
6、s-maxage=<seconds>:覆盖 max-age 属性。只在共享缓存中起作用。
7、immutable:表示文档是不能更改的。
8、must-revalidate:表示客户端(浏览器)必须检查代理服务器上是否存在,即使它已经本地缓存了也要检查。
9、proxy-revalidata:表示共享缓存(CDN)必须要检查源是否存在,即使已经有缓存。
流程图如下:
早期HTTP /1.0中则通过服务器在Header中Set-Cookie设置Expires来实现缓存控制,表示资源到什么时候过期(绝对时间),一旦修改本地时间,可能造成缓存失效。
-
ETag及二次验证--协商缓存
ETag也是设置在Header中,HTTP /1.1引入,用于缓存控制,但其值是内容的MD5值以及其他诸如文档版本/日期等的信息。
ETag的使用通常是首次请求服务器时,服务器返回包含Last-Modified及ETag字段的响应头,若客户端在很短时间再次访问相同的域名,浏览器将仅发送一个包含If-None-Match:MD5(之前响应的MD5值) ,若内容发生变化,服务器将返回一个新的响应,否则则返回一个HTTP 304响应头。
##Request
GET /hello.txt HTTP/1.1
If-None-Match: "8a75d48aaf3e72648a4e3747b713d730"
Host: www.foobar.tld
##Response
HTTP/1.1 304 Not Modified
Date: Sun, 05 Feb 2017 12:34:57 UTC
Server: Apache
Last-Modified: Sun, 05 Feb 2017 10:34:56 UTC
ETag: "8a75d48aaf3e72648a4e3747b713d730"
Content-Length: 8
Content-Type: text/plain; charset=UTF-8
早期HTTP /1.0使用首次响应返回Last-Modified,接下来请求头加上If-Modified-Since来实现协商缓存,相比ETag基于内容是否变更,其是否刷新缓存是通过修改时间是否变更来判断。有以下缺点:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
- 某些服务器不能精确的得到文件的最后修改时间。
若两者同时使用,则ETag优先级比Last Modified要高
-
浏览器缓存
浏览器发送请求时首先判断是否是强缓存,查看缓存信息,判断缓存状态,然后看是否需要进行协商缓存。而目前大部分web服务器都默认开启协商缓存,而且是同时启用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】- Cache-Control —— 请求服务器之前
- Expires —— 请求服务器之前
- If-None-Match (Etag) —— 请求服务器
- If-Modified-Since (Last-Modified) —— 请求服务器
注意:
- 分布式系统里多台机器间文件的Last-Modified必须保持一致,以免负载均衡到不同机器导致比对失败;
- 分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样);
其他:
-
URL重写:
通过设置cookie保存时间可使浏览器只读缓存中的内容而不重新发送请求,但有时需要强制更新cookie,可通过URL重写把SessionID+版本号附加到URL路径的后面。浏览器发现URL发送变化后将自动发送请求。 - 常见Set-Cookie配置信息
key | value |
---|---|
expires | Cookie失效时间(绝对时间)若不设置,浏览器关闭即删除 |
Max-Age | Cookie失效时间,相对时间,优先级高于expires |
path | 设置哪些路径带上cookie,一般默认为'/' |
Domain | 设置哪些域名带上cookie,一般为当前一级域名 |
Secure | 只在https下才能发送cookie |
HttpOnly | js脚本获取不到,且只能在http上才能发送cookie(防止xss) |
SameSite | 是否可能作为第三方 cookie(防止CSRF) |
- 常见响应头信息
key | value |
---|---|
pragma | http1.0, 值为no-cache为禁用缓存 |
vary | 基于字段区分缓存版本(res header) |
Date | 发送响应报文的时间(协商缓存、代理服务器缓存) |
Age | 文件存于服务器的时间 |
accept-encoding | 请求服务器返回的文件类型 |
referrer | 发送请求的源域名 |