报文结构

HTTP 是基于 TCP/IP 的,那么传输的事情应该是 TCP/IP 完成的,那么 HTTP 称为超文本传输协议是不是有点不妥了,因为它不负责传输,那这个传输又是怎么回事,答案就在它的传输的报文内容,HTTP 协议在规范文档里详细定义了报文的格式,规定了组成部分,解析规则,还有处理策略。

HTTP 协议是一个“纯文本”的协议,而 HTTP2/3 后就是二进制协议,http/1 纯文本的好处是方便易读,对人类友好,而 http/2 正相反,易于机器解析。

HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:

  1. 起始行(start line):描述请求或响应的基本信息;

  2. 头部字段集合(header):使用 key-value 形式更详细地说明报文;

  3. 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。

通俗的理解就是“header+body”。

HTTP 协议规定报文必须有 header,但可以没有 body,而且在 header 之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”。

这个使用 fiddler 抓取 HTTP 包就能看得出来,如:

  • 请求报文:
1
2
3
4
5
6
7
8
9
POST https://array701.prod.do.dsp.mp.microsoft.com/join/ HTTP/1.1
Connection: Keep-Alive
Accept: */*
User-Agent: Microsoft-Delivery-Optimization/10.0
MS-CV: edXwuc8Ok0mKLY9H.1.2.3.1.7.2.6.1.10
Content-Length: 682
Host: array701.prod.do.dsp.mp.microsoft.com

{"ContentId":"d91f96082d4a8db9f1bdccaeb06a29cb6c1341b7","AltCatalogId":"http://11.au.download.windowsupdate.com/d/msdownload/update/software/secu/2020/08/windows10.0-kb4576484-x64-ndp48_d91f96082d4a8db9f1bdccaeb06a29cb6c1341b7.cab","PeerId":"2614138130a28042b021b5b0c0add9c400000000","ReportedIp":"192.168.1.54","SubnetMask":"255.255.255.0","Ipv6":"","IsBackground":"1","ClientCompactVersion":"10.0.18362.959","Uploaded":"0","Downloaded":"8388608","DownloadedCdn":"8388608","DownloadedDoinc":"0","Left":"0","JoinRequestEvent":"3","RestrictedUpload":"0","PeersWanted":"50","GroupId":"","Scope":"2","UploadedBPS":"0","DownloadedBPS":"0","Profile":"0","ConnAttempts":"1:96;","Seq":"9"}
  • 响应报文:
1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/10.0
x-content-type-options: nosniff
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 10 Sep 2020 16:04:09 GMT
Content-Length: 287

{"FailureReason":null,"NextJoinTimeIntervalInMs":231680,"Complete":0,"Incomplete":0,"Rediscover":false,"KVVersion":"1E81C22A406C2F10A31B4149E8C96946EB35AD4CA26974B2145E165F2762B22B","GeoVersion":"E6B6DF02CFC90F2720E5594DF177BA33ABDAE4CCBA432D08DDEAD11123552399","Peers":[],"Leave":false}

报文头与报文体之间有一行空行

请求行

请求行就是起始行

它简要地描述了客户端想要如何操作服务器端的资源。

三部分构成:

  1. 请求方法:是一个动词,如 GET/POST,表示对资源的操作;

  2. 请求目标:通常是一个 URI,标记了请求方法要操作的资源;

  3. 版本号:表示报文使用的 HTTP 协议版本。

这三个部分通常使用空格来分隔,最后要用 CRLF 换行表示结束。

如:

1
POST https://array701.prod.do.dsp.mp.microsoft.com/join/ HTTP/1.1

请求方法为POST,请求目标为https://array701.prod.do.dsp.mp.microsoft.com/join/,版本号为HTTP/1.1

而响应的起始行就是状态行

也是由三部分构成:

  1. 版本号:表示报文使用的 HTTP 协议版本;

  2. 状态码:一个三位数,用代码的形式表示处理的结果,比如 200 是成功,500 是服务器错误;

  3. 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。

如:

1
HTTP/1.1 200 OK

版本号为HTTP/1.1,状态码200,原因OK

头部字段

请求头和响应头的结构是基本一样的,唯一的区别是起始行

头部字段是 key-value 的形式,key 和 value 之间用“:”分隔,最后用 CRLF 换行表示字段结束

HTTP 头字段非常灵活可以任意添加自定义头,这就给 HTTP 协议带来了无限的扩展可能

不过使用头字段需要注意下面几点:

  1. 字段名不区分大小写,例如“Host”也可以写成“host”,但首字母大写的可读性更好;

  2. 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。例如,“test-name”是合法的字段名,而“test name”“test_name”是不正确的字段名;

  3. 字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格;

  4. 字段的顺序是没有意义的,可以任意排列不影响语义;

  5. 字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie。

HTTP/1.1 里唯一要求必须提供的头字段是 Host,它必须出现在请求头里,标记虚拟主机名。

标准请求方法

目前 HTTP/1.1 规定了八种方法,单词都必须是大写的形式

  1. GET:获取资源,可以理解为读取或者下载数据;

  2. HEAD:获取资源的元信息;

  3. POST:向资源提交数据,相当于写入或上传数据;

  4. PUT:类似 POST;

  5. DELETE:删除资源;

  6. CONNECT:建立特殊的连接隧道;

  7. OPTIONS:列出可对资源实行的方法;

  8. TRACE:追踪请求 - 响应的传输路径。

请求方法是客户端发出的、要求服务器执行的、对资源的一种操作;
请求方法是对服务器的“指示”,真正应如何处理由服务器来决定;
最常用的请求方法是 GET 和 POST,分别是获取数据和发送数据;

响应状态码

1××

1×× 类状态码属于提示信息,是协议处理的中间状态,实际能够用到的时候很少。

2××

2×× 类状态码表示服务器收到并成功处理了客户端的请求,这也是客户端最愿意看到的状态码。

“200 OK”是最常见的成功状态码,表示一切正常,服务器如客户端所期望的那样返回了处理结果

3××

3×× 类状态码表示客户端请求的资源发生了变动,客户端必须用新的 URI 重新发送请求获取资源,也就是通常所说的“重定向”,包括著名的 301、302 跳转。

301 和 302 都会在响应头里使用字段 Location 指明后续要跳转的 URI,最终的效果很相似,浏览器都会重定向到新的 URI。两者的根本区别在于语义,一个是“永久”,一个是“临时”,所以在场景、用法上差距很大。

“304 Not Modified” 是一个比较有意思的状态码,它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”

301、302 和 304 分别涉及了 HTTP 协议里重要的“重定向跳转”和“缓存控制”

4××

4×× 类状态码表示客户端发送的请求报文有误,服务器无法处理,它就是真正的“错误码”含义了。

  • “400 Bad Request”是一个通用的错误码,表示请求报文有错误

  • “403 Forbidden”实际上不是客户端的请求出错,而是表示服务器禁止访问资源。

  • “404 Not Found”、资源在本服务器上未找到,所以无法提供给客户端。

  • 405 Method Not Allowed:不允许使用某些方法操作资源,例如不允许 POST 只能 GET;

5××

5×× 类状态码表示客户端请求报文正确,但服务器在处理时内部发生了错误,无法返回应有的响应数据,是服务器端的“错误码”。

HTTP 特性

HTTP 最凸出的优点是「简单、灵活和易于扩展、应用广泛和跨平台」。

简单

HTTP 基本的报文格式就是 header + body,头部信息也是 key-value 简单文本的形式,易于理解,降低了学习和使用的门槛。

灵活可扩展

HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充。

同时 HTTP 由于是工作在应用层( OSI 第七层),则它下层可以随意变化。

HTTPS 也就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层,HTTP/3 甚至把 TCP 层换成了基于 UDP 的 QUIC。

应用广泛和跨平台

互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应用片地开花,同时天然具有跨平台的优越性。

无状态

“状态”其实就是客户端或者服务器里保存的一些数据或者标志,记录了通信过程中的一些变化信息。

而无状态这个特性在不同的场景,体现出现的价值不一样,是一把双刃剑。

  • 好处:
    如在不需要客户端或者服务器里保存的一些数据或者标志来记录通信过程中的一些变化信息时,可以减轻服务器的负担,提高性能。

  • 坏处:
    如果需要知道每次操作的请求是不是同一个人时需要记录一些数据,那么要完成这个关联性的操作时会非常麻烦,因为每个请求都是独立的。

例如登录->添加购物车->下单->结算->支付,这系列操作都要知道用户的身份才行。但服务器不知道这些请求是有关联的,每次都要问一遍身份信息。

  • 解决方法
    对于无状态的问题,解法方案有很多种,其中比较简单的方式用 Cookie 技术。

Cookie 通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。

相当于,在客户端第一次请求后,服务器会下发一个装有客户信息的「小贴纸」,后续客户端请求服务器的时候,带上「小贴纸」,服务器就能认得了了,

明文

明文意味着在传输过程中的信息,是可方便阅读的,通过浏览器的 F12 控制台或 Wireshark 抓包都可以直接肉眼查看,为我们调试工作带了极大的便利性。

但是这正是这样,HTTP 的所有信息都暴露在了光天化日下,相当于信息裸奔。在传输的漫长的过程中,信息的内容都毫无隐私可言,很容易就能被窃取,如果里面有你的账号密码信息,那你号没了。

不安全

HTTP 比较严重的缺点就是不安全:

  1. 通信使用明文(不加密),内容可能会被窃听。比如,账号信息容易泄漏,那你号没了。
  2. 不验证通信方的身份,因此有可能遭遇伪装。比如,访问假的淘宝、拼多多,那你钱没了。
  3. 无法证明报文的完整性,所以有可能已遭篡改。比如,网页上植入垃圾广告,视觉污染,眼没了。

HTTP 的安全问题,可以用 HTTPS 的方式解决,也就是通过引入 SSL/TLS 层,使得在安全上达到了极致。

性能

HTTP 协议是基于 TCP/IP,并且使用了「请求 - 应答」的通信模式,所以性能的关键就在这两点里。

长连接

早期 HTTP/1.0 性能上的一个很大的问题,那就是每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无谓的 TCP 连接建立和断开,增加了通信开销。

为了解决上述 TCP 连接问题,HTTP/1.1 提出了长连接的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。

持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。

管道网络传输

HTTP/1.1 采用了长连接的方式,这使得管道网络传输成为了可能。

即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

举例来说,客户端需要请求两个资源。以前的做法是,在同一个 TCP 连接里面,先发送 A 请求,然后等待服务器做出回应,收到后再发出 B 请求。管道机制则是允许浏览器同时发出 A 请求和 B 请求。

但是服务器还是按照顺序,先回应 A 请求,完成后再回应 B 请求。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为「队头堵塞」。

队头阻塞

「请求 - 应答」的模式加剧了 HTTP 的性能问题。

因为当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一同被阻塞了,会招致客户端一直请求不到数据,这也就是「队头阻塞」。好比上班坐公交车和别的公交车并行行驶,但驶入公交站时大家串行排队等待前面的车辆的人下完后驶出才到你坐的公交车驶入站下车。

总之 HTTP/1.1 的性能一般般,后续的 HTTP/2 和 HTTP/3 就是在优化 HTTP 的性能。