HTTP头部详解-那些你必须了解的事儿

411 阅读17分钟

1. 前言

http是每个前端开发都无法逃避的一个知识点。随便打开一个网页(这里以Chrome浏览器打开为例),然后F12查看Network中的内容,可以看到大量的接口访问信息:

    这些信息里面就带着2个重要的头部信息:请求头+响应头。(图上用红框标识出来的部分)

  1. 客户端向服务器发送一个请求,请求头(Request Header)包含请求的方法、URI、协议版本、用户信息以及请求修饰符等等。
  2. 服务器响应:以状态行响应,内容包括成功/错误编码、消息协议版本、实体信息与内容、服务器信息等等。

那么,HTTP的头域包括哪些部分呢?一共有4个部分:通用头,请求头,响应头和实体头,每个头部信息的构成都是域名+冒号+域值

2. 版本对比

2.1 HTTP 1.0

    在此版本之前,还有一个最古老的版本HTTP 0.9,它发布于1991年,功能简单,只支持GET请求和纯文本内容,已经被时代所抛弃。

    对于1.0版本,主要支持以下几个特性:

  • 可以发送任何格式的内容,比如文字、图像、音频、视频和二进制等等;
  • 可使用GET, POST和HEAD命令;
  • 只使用header中的If-Modified-Since和Expires作为缓存失效的标志;
  • 不支持断点续传,也就意味着,每次都会传送全部的数据和页面;
  • http请求和相应的格式有所变化。除了数据部分外,每次通信都必须包括头部信息(也就是本文要着重讲解的内容),用于描述一些元数据。
  • 一般而言,一台计算机只能绑定一个IP,所以在请求的url中没有传递主机名称。

2.2 HTTP 1.1

    1.1版本的http协议是目前最为主流的版本,它的基本特征如下:

  • 引入了持久连接,也就是说TCP连接默认不关闭,多个请求可以复用该连接,不需要再次声明Connection: keep-alive。但是超长连接的时长可以通过请求头中的keep-alive来进行设置;
  • 支持断点续传,通过使用请求头中的Range来实现;
  • 新增了PUT, PATCH, OPTIONS, DELETE方法;
  • 引入管道机制,也就是说在同一个TCP连接里,客户端可以同时发送多个请求,进一步改进了HTTP协议的效率;
  • 使用虚拟网络,在一台物理机上可以存在多个虚拟主机,且共享同一个IP地址;
  • 头部信息中新增了E-tag, If-Unmodified-Since, If-Match, If-None-Match,用来控制缓存失效。

2.3 HTTP 2.0

    2.0版本的主要特点如下:

  • 多路复用 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或者响应,且不按照顺序一一对应,这样就解决了对头阻塞的问题;
  • 请求优先级 可以设置数据帧的优先级,让服务端在接收到请求时优先处理排在前面的任务,这样达到优化用户体验的目的;
  • 头部压缩 1.1版本会出现 【User-Agent, Cookie, Accept, Server, Range】等,这些字段可能会占用较大的空间从而导致HTTP头部偏重。2.0中使用HPACK算法进行压缩;
  • 二进制分帧 头部信息和数据都是二进制,并且统称为帧:头信息帧和数据帧;
  • 服务器推送 允许服务器不经过请求,主动向客户端发送自愿,即服务器推送;

接下来开始进入正题,讲一讲HTTP头部信息

3. HTTP头部-通用头

    通用头域包含请求和响应都支持的头域,如缓存头部Cache-Control、Pragma及信息性头部connection、Date、Transfer-Encoding、update、via.

3.1 Cache-Control

    该字段中指定了请求和响应都需要遵循的缓存规制,从它的名称中也可以看出一二。

请求时的缓存指令包括:no-cache, no-store, max-age, max-stale, only-if-cached, min-fresh;

响应消息中的指令包括:no-cache, no-store, private, public, max-age, must-revalidate, no-transform, proxy-revalidate;

  • no-cache:不能缓存。实际上可以存储在本地的缓存区域中,但在跟原始服务器进行新鲜度验证前,缓存不能用于客户端使用。

  • no-store:标识缓存应尽快从存储器中删除所有痕迹,因为其中可能会包含敏感信息。

  • private:标识对于单个用户的整个或部分响应消息,不能被共享缓存处理,只能用缓存的内容回应先前请求内容的那个用户。

  • public:标识响应可被任何缓存区缓存,可以用缓存内容回应任何用户。

  • max-age:这是一个绝对时间,标识缓存无法返回缓存时间大于max-age规定的时间(单位:秒)的内容。若不超过规定时间浏览器将不会发送对应的请求到服务器,数据由缓存直接返回;若超过max-age规定的时间,就由服务器决定时返回新数据还是仍然由缓存来提供;如果同时还发送了max-stale指令,则使用期可能会超过其过期的时间。

  • max-stale:标识客户端可以接收过期响应消息,如果指定max-stale消息的值,那么客户端可以接收过期但在指定期内的响应内容。

  • only-if-cached:只有当缓存中有副本存在时,客户端才会获得一份副本。

  • min-fresh:至少在未来规定时间内文档要保持最新,接收其新鲜生命期大于其当前Age与min-fresh值之和的缓存对象。

3.2 Pragma

    该字段用来包含实现特定的指令,最常用的是Pragma: no-cache。在HTTP/1.1版本的协议中,它的含义和Cache-Control: no-cache相同。

3.3 Connection

    connection用来标识是否需要持久连接。如果这里的值为Keep-Alive,或者HTTP用的是HTTP 1.1的版本(该版本是默认保持持久连接的)。除了Keep-Alive之外,connection的值还可能是:close, keepalive。

  • Close:告诉web服务器或者代理服务器,在完成本次请求响应后,断开连接,不要等待本次连接的后续请求;
  • Keepalive: 告诉web服务器或者代理服务器,在完成此次请求的响应后,保持连接,等待本次连接的后续请求;
  • Keep-Alive: 如果浏览器想保持请求连接,则该头部表明希望web服务器保持连接多长的时间(秒), 如Keep-Alive:500

(1)什么是Keep-Alive呢?

    HTTP协议采用的是“请求-应答”模式,也就是说客户端发出请求,服务端进行应答。当使用普通模式时,即不是Keep-Alive模式,每个“请求-应答”对于客户端和服务器而言都要建立一个连接,完成之后立即断开连接,因为HTTP是无连接的协议。使用Keep-Alive模式时,可以使得客户端到服务器的连接持续有效,当出现对服务器的后续请求时,Keep-Alive模式避免重新建立连接。

(2)那为什么要使用Keep-Alive呢?

    Keep-Alive模式的目的是能够在HTTP之前重用同一个TCP连接,从而减少创建/关闭多个TCP连接的开销,这些开销的来源主要是:CPU资源,管道拥堵,响应时间等等。

(3)那么,前端在发起http请求时,如何开启Keep-Alive?

    在1.0版本的HTTP协议中,默认情况是关闭的,需要在http头部加入“Connection: Keep-Alive”,就可以启用Keep-Alive了。

    但在1.1版本的HTTP协议中,默认情况是开启的,需要在http头部加入“Connection: Close”才可以关闭。

如果你正好也是用的1.1版本的HTTP协议,那你一定可以在HTTP的请求头部信息中国找到Keep-Alive的存在。

3.4 Date

    该字段表示消息发送的时间,服务器响应中要包含这个头部,因为在缓存进行新鲜度对比时需要使用,器时间格式由RFC822来定义,如上图中所示:Date: Web, 21 Oct 2020 02:24:08 GMT。

3.5 Transfer-Encoding

    web服务器用来表明自己对此次响应的消息体做了怎样的编码,如分块:chunked。

4. HTTP头部-请求头

    请求头用来说明具体是什么在发送请求,请求源在哪里,客户端的能力以及喜好。服务器则根据请求头部给出的客户端信息,尝试为客户端提供更好的响应。请求头域包含如下字段信息:(PS:对请求头域的扩展需要通讯双方都支持,如果遇到不支持的请求头域,则作为实体域进行处理)

4.1 Accept

    告诉WEB服务器自己可以接受什么类型的介质,/ 表示任何类型(图中使用的就是该类型),type/* 表示该类型下的所有子类型,type/sub-type。

4.2 Accept-Charset

    客户端告诉服务端自己能接收的字符集。

4.3 Accept-Encoding

    客户端申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法等(gzip,deflate, br)等等。

4.4 Accept-Language

   客户端申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等,图中可接受的语言为:zh-CN, zh-TW; q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6。

4.5 Authorization

    当客户端接收到来自WEB服务器的 WWW-Authenticate 响应时,用该头部来回应自己的身份验证信息给WEB服务器。

4.6 If-Match和If-None-Match

  • 如果对象的 ETag 没有改变,其实也就意味著对象没有改变,才执行请求的动作,获取文档。
  • 如果对象的 ETag 改变了,其实也就意味著对象也改变了,才执行请求的动作,获取文档。

4.7 If-Modified-Since和If-Unmodified-Since

    这里涉及到HTTP缓存的相关知识。

  • 如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作(比如返回对象),否则返回代码304,告诉浏览器该对象没有修改。这里涉及到HTTP缓存的相关知识。
  • 如果请求的对象在该头部指定的时间之后没修改过,才执行请求的动作(比如返回对象)。

4.8 If-Range 和Range

  • 浏览器告诉 WEB 服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。浏览器通过发送请求对象的ETag 或者自己所知道的最后修改时间给 WEB 服务器,让其判断对象是否改变了。总是跟 Range 头部一起使用。
  • 浏览器(比如 Flashget 多线程下载时)告诉 WEB 服务器自己想取对象的哪部分。

4.9 Host

    客户端指定自己想访问的WEB服务器的域名/IP 地址和端口号。如Host: www.baidu.com;

4.10 Referer

    浏览器向WEB 服务器表明自己是从哪个网页URL获得点击当前请求中的网址/URL,例如:referer: www.google.com/;

4.11 User-Agent

    浏览器表明自己的身份(是哪种浏览器)。例如:user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36;

5. HTTP头部-响应头

    响应头主要为客户端提供一些额外信息,如:谁在发送响应、响应者的功能,与响应相关的特殊指令。这些头部有助于客户端处理响应,并在将来发起更好的请求。对响应头域的扩展要求通讯双方都支持,如果存在不支持的响应头域,一般将会作为实体头域处理。主要包含下面的一些字段:

5.1 Age

    当代理服务器用自己缓存的实体去响应请求时,该字段用来表明实体从产生到现在经过了多长时间。

5.2 Server

    web服务器表明自己是什么软件及版本等信息。例如:server: gws;

5.3 Accept-Ranges

    web服务器表明自己是否接受获取其某个实体的一部分的请求。bytes:表示接受,none:表示不接受。

5.4 Vary

    web服务器用该头部的内容告诉 Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源web服务器在接到第一个请求消息时,其响应消息的头部为:Content-Encoding: gzip; Vary: Content-Encoding,那么Cache服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否与之前响应的Vary头部使用相同的内容编码方法,这样可以防止Cache服务器用自己Cache 里面压缩后的实体响应给不具备解压能力的浏览器。如vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers

6. HTTP头部-实体头

    实体头域提供了有关实体及其内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的源信息,主要有如下一些头部信息:

6.1 Allow

    服务器支持哪些请求方法(如GET、POST、PATCH、OPTIONS等)。

6.2 Location

    表示客户应当到哪里去提取文档,用于将接收端定位到资源的位置(URL)上。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。

6.3 Content-Base

    解析主体中的相对URL时使用的基础URL。

6.4 Content-Encoding

    web服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。

6.5 Content-Language

    web服务器告诉浏览器理解主体时最适宜使用的自然语言。

6.6 Content-Length

    web服务器告诉浏览器自己响应的对象的长度或尺寸,例如:Content-Length: 1024

6.7 Content-Location

    资源实际所处的位置。

6.8 Content-MD5

    主体的MD5校验和。

6.9 Content-Range

    实体头用于指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式: Content-Range:bytes-unitSPfirst-byte-pos-last-byte-pos/entity-legth。例如,传送头500个字节次字段的形式:Content-Range:bytes0- 499/1234如果一个http消息包含此节(例如,对范围请求的响应或对一系列范围的重叠请求),Content-Range表示传送的范围,Content-Length表示实际传送的字节数。

6.10 Content-Type

    web服务器告诉浏览器自己响应的对象的类型。例如:Content-Type:application/json

6.11 Etag

    Etag的作用跟Last-Modified的作用差不多,主要供web服务器判断一个对象是否改变了。比如前一次请求某个json文件时,获得了其 Etag,当这次又请求这个文件时,浏览器就会把先前获得Etag值发送给web服务器,然后web服务器会把这个Etag跟该文件的当前Etag进行对比,就可以清楚这个文件到底有没有改变了。

6.12 Expires

    web服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。是 HTTP/1.0 的头部。例如:expires: Wed, 21 Oct 2020 02:42:54 GMT;

6.13 Last-Modified

    web服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Wed, 21 Oct 2020 02:42:54 GMT;

7. HTTP缓存机制

    似乎在上面的介绍中,都能够看到Last-Modified, Expires, Cache-Control, Etag这些带有明显缓存意味的字段信息,那我们就来聊一聊HTTP的缓存机制,主要包括2种类型的缓存:强缓存和协商缓存。

7.1 强缓存

    与强缓存相关的2个关键字是:**Expires****Cache-Control**。强缓存又分为两种不同的情况:发送HTTP请求和不发送HTTP请求。在1.0版本的HTTP协议中使用的是Expires,在1.1版本的HTTP协议中使用的是Cache-Control。

  • Expires:它表示的是过期时间(相对于服务器而言),在这个时间期限内,客户端可以直接从缓存中获取数据,不需要再次发起请求,该字段存在于服务端的响应头中;但我们能够明显的发现一个问题就是:存在客户端时间与服务端时间不一致的情况,那就可能导致该字段设置的时间失效。
  • Cache-Control:它表示的是过期时长,对应的是max-age。如Cache-Control:max-age=5000,表示在资源返回后5000秒,可以直接使用缓存。

注意:当Expires和Cache-Control同时存在时,优先考虑Cache-Control。如果没有命中该种类型的缓存,则使用协商缓存。

7.2 协商缓存

    强缓存失效后,浏览器在请求头中携带响应的缓存Tag来向服务器发送请求,服务器根据对应的tag,来决定是否使用缓存。

与协商缓存相关的2个字段分别是:**ETag** 和 **Last-Modified**  。

  • ETag: 它是服务器根据当前文件的内容,对文件生成唯一的标识,比如MD5算法,只要里面的内容有改动,这个值就会修改,服务器通过把响应头把该字段给浏览器。浏览器接受到ETag值,会在下次请求的时候,将这个值作为**If-None-Match这个字段的内容,发给服务端。服务端在收到If-None-Match后,会拿来与服务器上该资源的ETag值**进行比较,则可能存在如下两种结果:
  1. 如果两者一样的话,直接返回304,告诉浏览器直接使用缓存
  2. 如果不一样的话,说明内容更新了,返回新的资源,跟常规的HTTP请求响应的流程一样
  • **Last-Modified:**它代表的是最后修改时间,在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。浏览器接收到后,再次发起请求,会在请求头中带上If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间。服务器拿到请求头中的If-Modified-Since的字段后,和这个服务器中Last-Modified比较,则可能出现下面两种结果:
  1. 如果请求头中的这个值小于最后修改时间,说明是时候更新了就返回新的资源。
  2. 否则返回304,告诉浏览器直接使用缓存。

7.3 两种缓存对比

**    如果两种方式都支持的话,服务器会优先考虑ETag**

  • 性能上,`Last-Modified`优于`ETag`,`Last-Modified`记录的是时间点,而`Etag`需要根据文件的MD5算法生成对应的hash值,一个是相对时间,一个是绝对时间。
  • 准确度上,`ETag`优于`Last-Modified`。`ETag`能准确感知资源变化,`Last-Modified`在某些场景并不能准确感知变化。

整个缓存使用过程总结起来就是:

  1. 先检查Cache-Control,看看能否使用强缓存;
  2. 如果可以,就直接用;
  3. 否者查看协商缓存,发送HTTP请求。服务端通过查看请求头中的If-Modified-Since或者If-None-Match字段来检查资源是否更新;
  4. 若资源更新,返回请求的内容和Http code: 200;
  5. 反之,返回304,直接告诉浏览器从缓存中取。