HTTP:header头总结
HTTP 头字段非常灵活,不仅可以使用标准里的 Host、Connection 等已有头,也可以任意添加自定义头,这就给HTTP 协议带来了无限的扩展可能。
使用头字段需要注意下面几点:
- 字段名不区分大小写,例如“Host”也可以写成“host”,但首字母大写的可读性更好;
- 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。例如,“test-name”是合法的字段名,而“test name”“test_name”是不正确的字段名;
- 字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格;
- 字段的顺序是没有意义的,可以任意排列不影响语义;
- 字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie。
分类
对 HTTP 报文的解析和处理实际上主要就是对头字段的处理,理解了头字段也就理解了 HTTP 报文。
分类
HTTP 协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:
- 通用字段:在请求头和响应头里都可以出现;
- 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;
- 响应字段:仅能出现在响应头里,补充说明响应报文的信息;
- 实体字段:它实际上属于通用字段,但专门描述 body 的额外信息。
分类
端到端标头:
- 这些标头必须传输到消息的最终接收者:请求的服务器,或响应的客户端。
- 中间代理必须未经修改地重新传输这些标头,并且缓存必须存储它们。
逐跳报头
- 这些标头仅对单个传输级连接有意义,并且不得由代理重新传输或缓存。
- 请注意,只能使用
Connection
首部设置逐跳首部。
具体
Host字段
作用: 客户端发送请求时,用来指定服务器的域名。有了Host
字段,就可以将请求发往「同一台」服务器上的不同网站。
- 它属于请求字段,只能出现在请求头里,它同时也是唯一一个 HTTP/1.1 规范里要求必须出现的字段,也就是说,如果请求头里没有 Host,那这就是一个错误的报文。
- Host字段告诉服务器这个请求应该由哪个主机来处理,当一条计算机上托管了多个虚拟主机的时候,服务端就需要用host字段来选择
举个例子,假如在 127.0.0.1 上有三个虚拟主机:“www.chrono.com”“www.metroid.net”和“origin.io”。那么当使用域名的方式访问时,就必须要用 Host字段来区分这三个 IP 相同但域名不同的网站,否则服务器就会找不到合适的虚拟主机,无法处理。
User-Agent字段
- 请求字段,只出现在请求头里
- 它使用一个字符串来描述发起HTTP请求的客户端,服务器可以依据它来返回最合适此浏览器显式的页面
但由于历史的原因,User-Agent 非常混乱,每个浏览器都自称是“Mozilla”“Chrome”“Safari”,企图使用这个字段来互相“伪装”,导致 User-Agent 变得越来越长,最终变得毫无意义。
Date字段
- Date字段是一个通用字段,但通常出现在响应头里,表示HTTP 报文创建的时间
- 客户端可以使用这个时间再搭配其他字段决定缓存策略。
Server字段
- Server字段是响应字段,只能出现在响应头里。
- 它告诉客户端当前正在提供 Web 服务的软件名称和版本号
- Server 字段也不是必须要出现的,因为这会把服务器的一部分信息暴露给外界,如果这个版本恰好存在 bug,那么黑客就有可能利用 bug 攻陷服务器。所以,有的网站响应头里要么没有这个字段,要么就给出一个完全无关的描述信息。
- 比如 GitHub,它的 Server 字段里就看不出是使用了Apache 还是 Nginx,只是显示为“GitHub.com”。
Content-Length字段
作用:
Content-Length
是一个实体消息首部,表示报文里body的长度,也就是请求头或者响应头空行后面数据的长度。 比如:- 服务器在返回数据时,带上
Content-Length
字段,表明本次回应的数据长度。 - 服务器看到客户端的请求字段有这个,就知道了后续有多少数据,可以直接接收
- 如果没有这个字段,那么body就是不定长的,需要使用chunked方式分段传输。
- 服务器在返回数据时,带上
语法:
Content-Length: <length>
举例:
- 如下图:
Content-Length: 1000
是告诉浏览器,本次服务器回应的数据长度是 1000 个字节,后面的字节就属于下一个回应了
Content-Type 字段
作用:
Content-Type
字段用于服务器回应时,告诉客户端,本次数据是什么格式
补充:
- 客户端请求的时候,可以使用
Accept
字段声明自己可以接受哪些数据格式。- 比如:
Accept: */*
,客户端声明自己可以接受任何格式的数据。
- 比如:
举例:
- 如下图:
Content-Type: text/html; charset=utf-8
表明,发送的是网页,而且编码是UTF-8。
Content-Encoding 字段
作用:
Content-Encoding
字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式。- 它实体消息首部,用于对特定媒体类型的数据进行压缩。
- 当这个首部出现的时候,它的值表示消息主体进行了何种方式的内容编码转换。
- 这个消息首部用来告知客户端应该怎样解码才能获取在
Content-Type
中标示的媒体类型内容。 - 一般建议对数据尽可能地进行压缩,因此才有了这个消息首部的出现。不过对于特定类型的文件来说,比如jpeg图片文件,已经是进行过压缩的了。有时候再次进行额外的压缩无助于负载体积的减小,反而有可能会使其增大。
语法:
Content-Encoding: gzip
Content-Encoding: compress
Content-Encoding: deflate
Content-Encoding: identity
Content-Encoding: br
补充:
- 客户端在请求时,用
Accept-Encoding
字段说明自己可以接受哪些压缩方法。举例:
Accept-Encoding: gzip, deflate
举例:
- 如下图,
Content-Encoding: gzip
表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。
使用流程:
- 客户端可以事先声明一系列的可以支持压缩模式,与请求一齐发送。
Accept-Encoding
这个首部就是用来进行这种内容编码形式协商的:
Accept-Encoding: gzip, deflate
- 服务器在 Content-Encoding 响应首部提供了实际采用的压缩模式:
Content-Encoding: gzip
- 需要注意的是,服务器端并不强制要求一定使用何种压缩模式。采用哪种压缩方式高度依赖于服务器端的设置,及其所采用的模块。
Connection字段
作用:
Connection
字段最常用于客户端要求服务器使用 TCP 持久连接,以便其他请求复用。- Connection 头(header) 决定当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。
- 除去标准的逐段传输(hop-by-hop)头(Keep-Alive, Transfer-Encoding, TE, Connection, Trailer, Upgrade (en-US), Proxy-Authorization and Proxy-Authenticate),任何逐段传输头都需要在 Connection 头中列出,这样才能让第一个代理知道必须处理它们且不转发这些头
语法:
Connection: keep-alive
Connection: close
- close:表明客户端或服务器想要关闭该网络连接,这是HTTP/1.0请求的默认值
- keep-alive:表明客户端想要保持该网络连接打开,HTTP/1.1的请求默认使用一个持久连接
为什么要引入这个字段:
- HTTP 是基于
Resquest-Response
形式的,客户端每次发送完一个请求之后,等待服务器响应,最后和服务器断开连接,本次请求结束。 - 由于 HTTP 是基于
TCP/IP
传输的,当 HTTP 发送请求的时候要和服务器经过三次握手建立连接,断开的时候要经过四次挥手进行断开。如果有大量的 HTTP 请求的话,每次都要进过 三次握手 和 四次挥手 ,就会非常的耗时。
- 为了解决这个问题,HTTP引入了
keep-alive
机制。所谓的 keep-alive 就是客户端和服务器三次握手之后,可以发送多个请求,最后再进行四次挥手。
- 这样我们就可以节省很多次握手和挥手的步骤,提升了服务器的性能。
- HTTP 头部中的 connection 就是完成这个功能的,当我们设置
Connection: keep-alive
的时候,就会保持该链接,直到某个请求的Connection: close
为止。这样可以在很大程度上提高 HTTP 的性能。
举例:
Connection: keep-alive
表示一个可以复用的 TCP 连接已经建立了,直到客户端或服务器主动关闭连接。
Keep-Alive字段
作用:
Keep-Alive
是一个通用消息头,允许消息发送者暗示连接的状态,还可以用来设置超时时长和最大请求数。-
- 需要将 Connection 首部的值设置为 “keep-alive” 这个首部才有意义。同时需要注意的是,在HTTP/2 协议中, Connection 和 Keep-Alive 是被忽略的;在其中采用其他机制来进行连接管理。
-
- 是逐跳标头,仅对单个传输级别连接有意义,并且不能由代理重新传输或缓存
-
语法:
Keep-Alive: parameters
- parameters:一系列用逗号隔开的参数,每一个参数由一个标识符和一个值构成,并使用等号 (‘=’) 隔开。下述标识符是可用的:
timeout
:指定了一个空闲连接需要保持打开状态的最小时长(以秒为单位)。需要注意的是,如果没有在传输层设置 keep-alive TCP message 的话,大于 TCP 层面的超时设置会被忽略。MAX
:在连接关闭之前,在此连接可以发送的请求的最大值。在非管道连接中,除了 0 以外,这个值是被忽略的,因为需要在紧跟着的响应中发送新一次的请求。HTTP 管道连接则可以用它来限制管道的使用。
示例:
Accept-Ranges字段
作用:
- 该字段表示服务器自身是否支持 范围请求 ,它的值用于表示范围请求的单位。
语法:
Accept-Ranges: bytes
Accept-Ranges: none
- none:不支持任何范围请求单位,由于其等同于没有返回此头部,因此很少使用。不过一些浏览器,比如IE9,会依据该头部去禁用或者移除下载管理器的暂停按钮。
- bytes:范围请求的单位是 bytes (字节)。
Content-Range字段
作用:
- 这个字段是配合
Range
请求使用的,适用于断点续传,下载等功能。 - 比如在下载的时候,可以使用多个进程,分别下载文件的一部分,到最后合并成一个文件,加快下载速度。
Upgrade字段
作用:
- 用于将已建立的客户端/服务器连接升级到不同的协议。
- 注意,如果发送了
Upgrade
那么就一定要设置Connection: upgrade
语法:
Connection: upgrade
Upgrade: protocol_name[/protocol_version]
- Connection带有类型的标头upgrade 必须始终与Upgrade标头一起发送
- 协议之间以逗号分隔
- 协议按优先级降序排列
- 协议版本是可选的(以“/”分隔)。例如:
Connection: upgrade
Upgrade: a_protocol/1, example ,another_protocol/2.2
举例:
(1) 客户端可以使用它来将连接从 HTTP 1.1 升级到 HTTP 2.0,或者将 HTTP 或 HTTPS 连接升级为 WebSocket。
- 注意,
Upgrade
仅可以在HTTP/1.1中使用。HTTP/2 明确禁止使用此机制/标头
Connection: Upgrade
Upgrade: websocket
(2)客户端可以使用Upgrade header字段邀请服务器以降序优先顺序切换到列出的一个(或多个)协议。
- 例如,客户端可能会发送如下请求,列出要切换到的首选协议(在本例中为“example/1”和“foo/2”):
GET /index.html HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: example/1, foo/2
-
服务器可以出于任何原因选择忽略该请求,在这种情况下,它应该像尚未发送升级头一样进行响应,比如200 OK
-
如果服务器决定升级连接,它必须:
-
- 第一步:响应
101 Switching Protocols
, 以及Upgrade
指定要切换到的协议的标头,比如:
- 第一步:响应
HTTP/1.1 101 Switching Protocols
Upgrade: foo/2
Connection: Upgrade
-
- 第二步:使用新协议发送对原始请求的响应
Proxy-Connection
从字面上的意思看,这个字段表示和代理的连接。下面我们来具体看一下
首先我们看一下设置了浏览器代理之后HTTP的请求有哪些变化
GET / HTTP/1.1
Host: www.example.com
Connection: keep-alive
GET http://www.example.com/ HTTP/1.1
Host: www.example.com
Proxy-Connection: keep-alive
我们看到有两个变化
- 请求的资源URI变成了完整路径
- Connection 头变为了 Proxy-Connection头
为什么需要完整路径
早期的HTTP设计中,浏览器直接和单个服务器对话,不存在虚拟主机。 所以单个服务器总知道自己的主机名和端口,这样浏览器发送请求只需要发送相对地址就可以了。如下:
GET / HTTP /1.0
有了代理就麻烦了,代理不知道GET / HTTP /1.0 这个请求发给哪个主机,所以HTTP 1.0 又要求浏览器给代理发送的时候必须发送完整的路径名称:
GET http: //www .example.com/ HTTP /1 .0
HTTP 1.1 规定了必须包含Host主机名这个字段。所以 HTTP 1.1 可以是:
GET / HTTP/1.1
Host: www.example.com
但是由于不清楚代理是1.0 还是1.1的 也许代理不认识Host这个头。 所以http 1.1 发给代理的最后格式就变为
GET http://www.example.com/ HTTP/1.1
Host: www.example.com
Accept-Encoding字段
作用:
-
HTTP 请求头 Accept-Encoding 会将客户端能够理解的内容编码方式——通常是某种压缩算法——进行通知(给服务端)。通过内容协商的方式,服务端会选择一个客户端提议的方式,使用并在响应头
Content-Encoding
中通知客户端该选择。 -
即使客户端和服务器都支持相同的压缩算法,在identity指令可以被接受的情况下,服务器也可以选择对响应主体不进行压缩。导致这种情况出现的两种常见情形是:
- 要发送的数据已经经过压缩,再次进行压缩不会导致被传输的数据量更小。一些图像格式的文件会存在这种情况
- 服务器超载,无法承受压缩需求导致的计算开销。通常,如果服务器使用超过80%的计算能力,微软建议不要压缩。
-
只要 identity —— 表示不需要进行任何编码——没有被明确禁止使用(通过 identity;q=0 指令或是 *;q=0 而没有为 identity 明确指定权重值),则服务器禁止返回表示客户端错误的 406 Not Acceptable 响应。
语法:
Accept-Encoding: gzip
Accept-Encoding: compress
Accept-Encoding: deflate
Accept-Encoding: br
Accept-Encoding: identity
Accept-Encoding: *
// Multiple algorithms, weighted with the quality value syntax:
Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5
说明:
-
gzip:
- 表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及32位CRC校验的编码方式。
- 这个编码方式最初由 UNIX 平台上的 gzip 程序采用。
- 出于兼容性的考虑, HTTP/1.1 标准提议支持这种编码方式的服务器应该识别作为别名的 x-gzip 指令。
-
compress:
- 采用 Lempel-Ziv-Welch (LZW) 压缩算法。这个名称来自UNIX系统的 compress 程序,该程序实现了前述算法。
- 与其同名程序已经在大部分UNIX发行版中消失一样,这种内容编码方式已经被大部分浏览器弃用,部分因为专利问题(这项专利在2003年到期)。
-
deflate:
- 采用 zlib 结构 (在 RFC 1950 中规定),和 deflate 压缩算法(在 RFC 1951 中规定)。
-
identity:
- 用于指代自身(例如:未经过压缩和修改)。
- 除非特别指明,这个标记始终可以被接受。
-
br:
- 表示采用 Brotli 算法的编码方式。
-
*
:- 匹配其他任意未在该请求头字段中列出的编码方式。
- 假如该请求头字段不存在的话,这个值是默认值。
- 它并不代表任意算法都支持,而仅仅表示算法之间无优先次序
-
;q= (qvalues weighting):
- 值代表优先顺序,用相对质量值 表示,又称为权重
- 相对质量值:由后缀’;q=’后紧跟一个介于0和1之间的值来标记
示例
Accept-Encoding: gzip
Accept-Encoding: gzip, compress, br
Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
Transfer-Encoding
Transfer-Encoding
消息首部指明了将 实体安全传递给用户所采用的编码形式。
Transfer-Encoding
是一个逐跳传输消息首部,即仅应用于两个节点之间的消息传递,而不是所请求的资源本身。一个多节点连接中的每一段都可以应用不同的Transfer-Encoding 值。如果你想要将压缩后的数据应用于整个连接,那么请使用端到端传输消息首部Content-Encoding
。
语法
Transfer-Encoding: chunked
Transfer-Encoding: compress
Transfer-Encoding: deflate
Transfer-Encoding: gzip
Transfer-Encoding: identity
// Several values can be listed, separated by a comma
Transfer-Encoding: gzip, chunked
chunked
:
- 数据以一系列分块的形式进行发送。
Content-Length
首部在这种情况下不被发送。- 在每一个分块的开头需要添加当前分块的长度,以十六进制的形式表示,后面紧跟着
\r\n
,之后是分块本身,后面也是\r\n
。 - 终止块是一个常规的分块,不同之处在于其长度为0.终止块后面是一个挂载(trailer),由一系列(或者为空)的实体消息首部构成。
示例
分块编码
分块编码主要应用于如下场景,即要传输大量的数据,但是在请求没有被处理完之前响应的长度是无法获得的。例如,当需要用从数据库中查询获得的数据生成一个大的HTML表格的时候,或者需要传输大量的图片的时候。一个分块响应形式如下:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
\r\n
Trailer字段
Trailer响应头允许发送方在分块消息的末尾包含额外的字段,以提供可能在发送消息体时动态生成的元数据,如消息完整性检查、数字签名或后处理状态。
Note: The TE request header needs to be set to “trailers” to allow trailer fields.
语法
Trailer: header-names
说明:
- header-names:将出现在HTTP头中分块消息的尾部部分。这些头字段是运行的:
- 消息帧头(例如,
Transfer-Encoding
和Content-Length
), - 路由报头(例如,
Host
), - 请求修饰符(如Cache-Control,Max-Forwards,或TE)
- 认证头(例如,Authorization或Set-Cookie),
- 或 Content-Encoding, Content-Type, Content-Range和它自己(Trailer)
- 消息帧头(例如,
示例
使用Trailer的分块传输编码
在这个例子中,Expires报头被使用在分块消息的末尾,作为一个尾随报头。
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
Trailer: Expires
7\r\n
Mozilla\r\n
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n
Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n
\r\n
抓包
头部字段的:
前面和后面可以带有空格吗?
下面是两个最简单的 HTTP 请求,第一个在“:”后有多个空格,第二个在“:”前有空格。
GET /09-1 HTTP/1.1
Host: www.chrono.com
GET /09-1 HTTP/1.1
Host : www.chrono.com
第一个可以正确获取服务器的响应报文,而第二个得到的会是一个“400 Bad Request”,表示请求报文格式有误,服务器无法正确处理:
2
3
HTTP/1.1 400 Bad Request
Server: openresty/1.15.8.1
Connection: close
问题:
(1) 如果拼 HTTP 报文的时候,在头字段后多加了一个CRLF,导致出现了一个空行,会发生什么?
头字段后多了一个CRLF,会被当做body处理
(2) 讲头字段时说“:”后的空格可以有多个,那为什么绝大多数情况下都只使用一个空格呢?
节省资源