HTTP系列-报文篇
2022-10-19·7min
type
Post
summary
status
Published
category
tags
slug
date
Oct 19, 2022
password
icon
由于TCP/IP协议在底层负责了具体的传输工作,HTTP基本不用在这上面操心太多。
单从这一点来看,所谓的“超文本传输协议”其实并不怎么管“传输”这件事,有点名不副实。
那么HTTP的核心是什么呢?答案就是它传输的报文内容。
基于传输的报文,HTTP可以在TCP/IP层之上实现更多灵活的功能,例如缓存管理、连接控制、数据编码、内容协商等。
报文结构
HTTP的报文分为请求报文和响应报文,结构基本相同,由三大部分组成:
- 起始行(start line):描述请求或响应的基本信息。
- 头部字段集合(header):使用key-value形式更详细的说明报文。
- 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。
其中前两部分起始行和头部字段集合经常又合称为“请求头”或“响应头”(header),消息正文又称为“实体”(body)。
HTTP协议规定报文中必须有header,但可以没有body。而且在header之后必须要有一个空行,作为和body的分隔。
HTTP虽然对header的大小没有限制,但各个web服务器都不会允许header过大的情况出现,因为头部太大可能会占用大量服务器资源,影响运行效率。
例如nginx对header的大小限制在8kb。
起始行
起始行分为请求行和状态行两种。
- 请求行:出现在请求报文中,由请求方法、请求目标、HTTP协议版本号三部分组成。
- 状态行:出现在响应报文中,由HTTP协议版本号、响应码、响应描述三部分组成。
头部字段
头部字段是key-value的形式,key和value之间用“:”分隔,最后由换行符表示字段结束。
HTTP头部字段非常灵活,不仅可以使用规范里的已有字段,也可以自定义字段。
使用头部字段要注意以下几点:
- 字段名不区分大小写,但首字母大写可读性更好。
- 字段名不允许出现空格和下划线“_”,可以使用连字符“-”。
- 字段名后面必须紧跟“:”,不能有空格,而“:”后的字段值前可以有多个空格。
- 字段顺序是无意义的,可以任意排列。
- 字段原则上不能重复,除非这个字段本身的语义允许,例如Set-Cookie。
常用头部字段
头部字段按照用途可以分为四种:
- 通用字段:在请求头和响应头里都可以出现。
- 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件。
- 响应字段:仅能出现在响应头里,补充说明响应报文的信息。
- 实体字段:实际上属于通用字段,但专门描述body的额外信息。
注意的是,在请求头中,Host字段是必须出现的,Host的作用是告诉服务器请求应该由哪个主机来处理。
内容协商相关的头部字段
- Accept:客户端期望获得的数据格式,可以用“,”分隔列出多个类型。
- Accept-Encoding:客户端支持的压缩格式,可以用“,”分隔列出多个类型。如果没有这个字段,表示客户端不支持压缩。
- Accept-Language:客户端期望获得的语言类型,可以用“,”分隔列出多个类型。
- Accept-Charset:客户端期望获得的字符集,可以用“,”分隔列出多个类型。
- Content-type:实体数据实际使用的的数据格式。后面的”charset=XXX“表示实际使用的字符集类型。
- Content-Encoding:实体数据实际使用的压缩格式。
- Content-Language:实体数据实际使用的语言类型。
- Vary:服务器在内容协商时参考的请求头字段。
在使用Accept开头的请求头字段时,可以搭配q参数来设定权重。权重的最大值是1,最小值是0.01,0表示拒绝。默认值为1。
例如下面的Accept字段:
表示客户端最期望接受的是html文件,权重是1,其次是xml文件,权重是0.9,然后是任意文件,权重为0.8。
数据传输相关的头部字段
- Transfer-Encoding:chunked。表示数据传输采用了分块传输的方式。
- Content-Length:给出了数据的确切长度,并表示数据是一次性传输的。与 Transfer-Encoding:chunked 互斥,不能同时存在。
- Range:用来范围请求的头部字段,格式是“bytes=x-y”。
- Accept-Ranges: bytes。表示服务端支持范围请求。如果不支持,值为none或者干脆没这个字段。
- Content-Range:实际发送的实体数据的范围,用来回应范围请求。格式是“bytes x-y/length”。
由于视频、音频等形式的文件本身就是高度压缩的,再压缩说不定反而会使体积再增大,所以对于这类文件一般采用分块传输,对于文本文件一般是压缩再传输。
范围请求还支持在Range里同时使用多个“x-y”,一次性获取多个片段数据。
发出多个片段的范围请求后,响应报文会使用一种特殊的 MIME 类型:“multipart/byteranges”,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数“boundary=xxx”给出段之间的分隔标记。
连接相关的头部字段
- Connect:keep-alive。表示使用长连接,由于长连接对于HTTP的性能改善十分显著,所以是默认开启的。也可以设置值为Close,表示本次通信结束后就关闭连接。
- Keep-Alive:timeout=value。表示限定长连接的超时时间,但约束力并不强,通信双方可能不会遵守。
过多的长连接会占用服务器资源,所以服务器会用一些策略有选择地关闭长连接。
例如Nginx里的“keepalive_timeout”和““keepalive_requests””。分别用来限制连接的超时时间和最大请求次数。
重定向相关的头部字段
- Location:必须出现在响应报文中,只有搭配301/302状态码才有意义。该字段会标记服务器要求重定向的URI,浏览器在收到301/302状态码后会检查有无Location字段,有则发起根据该URI发起新的HTTP请求,实现用户无感知的跳转。
- Refresh:表示延时重定向,例如“Refresh: 5;url=xxx”表示告诉浏览器5s后再跳转。
- Referer和Referrer-Policy:前者是个拼写错误,但已将错就错。表示浏览器跳转的来源,也就是引用来源。
Cookie相关的头部字段
由于HTTP是无状态的协议,服务器为了能够客户端的身份,Cookie技术应运而生。Cookie就相当于服务器给客户端贴上的小纸条,上面写了只有服务端才能理解的数据,需要的时候客户端会将Cookie发给服务端,服务端看到Cookie,就能认出谁是谁了。
- Set-Cookie:响应头字段,格式是“key=value”。浏览器看到后就会保存起来。一份响应报文中允许有多个Set-Cookie
- Cookie:请求头字段。浏览器再请求时会自动将已有的cookie放进该字段里,随着请求报文一起发出。多个cookie使用“;”分开。
Cookie的属性
cookie通常会记录用户的关键识别信息,所以需要在“key=value”外再用一些手段来保护cookie,防止外泄或窃取,这些手段就是cookie的属性。
1.Expires:过期时间,绝对时间,可以理解为截止时间。
2.Max-Age:过期时间,相对时间,单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就可以得到失效的绝对时间。
Expires 和 Max-Age 可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用 Max-Age 计算失效期。
3.Domain: cookie 所属的域名.
4.Path: cookie 所属的路径.
浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie。
5.HttpOnly: 表示该cookie只能通过浏览器HTTP协议传输,禁止其他方式访问,例如JS中的document.cookie。
6.SameSite: 可以防范“跨站请求伪造”(XSRF)攻击。设置值为Strict时可以严格限定Cookie不能随着跳转链接跨站发送。值为Lax时会宽松一点,允许GET/HEAD等安全方法。
7.Secure: 表示这个Cookie仅能用HTTPS协议加密传输,明文的HTTP协议会禁止发送。但Cookie本身不是加密的,浏览器里还是以明文的形式存在。
需要注意的是,cookie是由浏览器负责储存的,也就是本浏览器内有效。
因为Cookie并不属于HTTP标准,所以语法上与其他字段不太一致,使用的分隔符是“;”,与 Accept等字段的“,”不同。