# http

* 超文本传输协议 *(HTTP)是一个用于传输超媒体文档(例如 HTML)的 [应用层] 协议。它是为 Web 浏览器与 Web 服务器之间的通信而设计的,但也可以用于其他目的。HTTP 遵循经典的 [客户端 — 服务端模型],客户端打开一个连接以发出请求,然后等待直到收到服务器端响应。HTTP 是 [无状态协议],这意味着服务器不会在两个请求之间保留任何数据(状态)。

HTTP 是一种能够获取如 HTML 这样的网络资源的 [protocol]。它是在 Web 上进行数据交换的基础,是一种 client-server 协议,也就是说,请求通常是由像浏览器这样的接受方发起的。一个完整的 Web 文档通常是由不同的子文档拼接而成的,像是文本、布局描述、图片、视频、脚本等等。

客户端和服务端通过交换各自的消息(与数据流正好相反)进行交互。由像浏览器这样的客户端发出的消息叫做请求(request),被服务端响应的消息叫做响应(response)。

HTTP as an application layer protocol, on top of TCP (transport layer) and IP (network layer) and below the presentation layer.

client

user-agent 就是任何能够为用户发起行为的工具。这个角色通常都是由浏览器来扮演。

浏览器首先发送一个请求来获取页面的 HTML 文档,再解析文档中的资源信息发送其他请求,获取可执行脚本或 CSS 样式来进行页面布局渲染,以及一些其他页面资源(如图片和视频等)。然后,浏览器将这些资源整合到一起,展现出一个完整的文档,也就是网页。浏览器执行的脚本可以在之后的阶段获取更多资源,并相应地更新网页。

server

由 Web Server 来服务并提供客户端所请求的文档。Server 只是虚拟意义上代表一个机器:它可以是共享负载(负载均衡)的一组服务器组成的计算机集群,也可以是一种复杂的软件

# http 特性

1.http 是简单的 : HTTP 大体上还是被设计得简单易读。

2.http 是可扩展的: header 中可以很容易的加入新字段实现新的功能

3.HTTP 是无状态,有会话的:HTTP 是无状态的:在同一个连接中,两个执行成功的请求之间是没有关系的。这就带来了一个问题,用户没有办法在同一个网站中进行连续的交互,比如在一个电商网站里,用户把某个商品加入到购物车,切换一个页面后再次添加了商品,这两次添加商品的请求之间没有关联,浏览器无法知道用户最终选择了哪些商品。而使用 HTTP 的标头扩展,HTTP Cookie 就可以解决这个问题。把 Cookie 添加到标头中,创建一个会话让每次请求都能共享相同的上下文信息,达成相同的状态。HTTP 本质是无状态的,使用 Cookie 可以创建有状态的会话。

4.http 是基于 TCP 的:HTTP 依赖于面向连接的 TCP 进行消息传递。在客户端与服务器能够交互之前,必须在这两者间建立一个 TCP 链接,打开一个 TCP 连接需要多次往返交换消息。

5. 缓存:服务端能告诉代理和客户端哪些文档需要被缓存,缓存多久,而客户端也能够命令中间的缓存代理来加速访问。

6. 同源限制:只有来自于相同来源的网页才能够获取网站的全部信息。一般情况下通过 host 和 origin 来判断是否同源。而这些字段抓包时又是可以修改的。通过 cors,我们可以实现跨域。

7. 认证:使用 Authenticate 相似的标头即可。一些页面能够被保护起来,仅让特定的用户进行访问。

# http 的交换流程

当客户端想要和服务端进行信息交互时,过程表现为下面几步:

  1. 打开一个 TCP 连接:TCP 连接被用来发送一条或多条请求,以及接受响应消息。客户端可能打开一条新的连接,或重用一个已经存在的连接,或者也可能开几个新的 TCP 连接连向服务端。

  2. 发送一个 HTTP 报文:HTTP 报文(在 HTTP/2 之前)是语义可读的。在 HTTP/2 中,这些简单的消息被封装在了帧中,这使得报文不能被直接读取。

    1
    2
    GET / HTTP/1.1
    Host: baidu.com
  3. 读取服务端返回的报文信息:

    1
    2
    3
    4
    5
    6
    HTTP/1.1 200 OK
    Date: Sat, 09 Oct 2023 14:28:02 GMT
    Server: Apache
    Accept-Ranges: bytes
    Content-Length: 29769
    Content-Type: text/html
  4. 关闭连接或者为后续请求重用连接。

# http 报文构成

请求由以下元素组成:

  • 一个 HTTP 的请求方法,经常是由一个动词像 GETPOST 或者一个名词像 OPTIONSHEAD 来定义客户端的动作行为。通常客户端的操作都是获取资源(GET 方法)或者发送 HTML 表单(POST 方法)。
  • 要获取的资源的路径,通常是上下文中就很明显的元素资源的 URL
  • HTTP 协议版本号。
  • 为服务端表达其他信息的可选标头。
  • 对于一些像 POST 这样的方法,报文的主体(body)就包含了发送的资源,这与响应报文的主体类似。

响应报文包含了下面的元素:

  • HTTP 协议版本号。
  • 一个状态码,来告知对应请求执行成功或失败,以及失败的原因。
  • 一个状态信息,这个信息是非权威的状态码描述信息,可以由服务端自行设定。
  • HTTP 标头,与请求标头类似。
  • 可选项,比起请求报文,响应报文中更常见地包含获取资源的主体。

# http 发展

# 1.http 0.9

HTTP/0.9 极其简单:请求由单行指令构成,以唯一可用方法 get。其后跟目标资源的路径(一旦连接到服务器,协议、服务器、端口号这些都不是必须的)。

GET /1.html

响应也极其简单的:只包含响应文档本身。

1
<HTML> response! </HTML>

HTTP/0.9 的响应内容并不包含 HTTP 头。这意味着只有 HTML 文件可以传送,无法传输其他类型的文件。也没有状态码或错误代码。

# 2.http 1.0

协议版本信息现在会随着每个请求发送( HTTP/1.0 被追加到了 GET 行)。

状态码会在响应开始时发送,使浏览器能了解请求执行成功或失败,并相应调整行为(如更新或使用本地缓存)。

引入了 HTTP 标头的概念,无论是对于请求还是响应,允许传输元数据,使协议变得非常灵活,更具扩展性。

在新 HTTP 标头的帮助下,具备了传输除纯文本 HTML 文件以外其他类型文档的能力 (content-type)

1
2
3
4
5
6
7
8
9
10
11
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
一个包含图片的页面
<IMG SRC="/myimage.gif">
</HTML>

# http 1.1

连接可以复用,节省了多次打开 TCP 连接加载网页文档资源的时间。

增加管线化技术,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟。

支持响应分块。

引入额外的缓存控制机制。

引入内容协商机制,包括语言、编码、类型等。并允许客户端和服务器之间约定以最合适的内容进行交换。

凭借 Host 标头,能够使不同域名配置在同一个 IP 地址的服务器上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
GET /en-US/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/en-US/docs/Glossary/Simple_header

200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 20 Jul 2016 10:55:30 GMT
Etag: "547fa7e369ef56031dd3bff2ace9fc0832eb251a"
Keep-Alive: timeout=5, max=1000
Last-Modified: Tue, 19 Jul 2016 00:59:33 GMT
Server: Apache
Transfer-Encoding: chunked
Vary: Cookie, Accept-Encoding

(content)

# https

在此基础上创建了一个额外的加密传输层:SSL。SSL 2.0 及其后继者 SSL 3.0 允许通过加密来保证服务器和客户端之间交换消息的真实性。

# http/2

HTTP/2 是二进制协议而不是文本协议。不再可读,也不可无障碍的手动创建,改善的优化技术现在可被实施。

这是一个多路复用协议。并行的请求能在同一个链接中处理,移除了 HTTP/1.x 中顺序和阻塞的约束。

压缩了标头。因为标头在一系列请求中常常是相似的,其移除了重复和传输重复数据的成本。

其允许服务器在客户端缓存中填充数据,通过一个叫服务器推送的机制来提前请求。

# http/3

HTTP/3 有这与 HTTP 早期版本的相同语义,但在传输层部分使用 QUIC, 而不是 TCP

QUIC 旨在为 HTTP 连接设计更低的延迟。类似于 HTTP/2,它是一个多路复用协议,但是 HTTP/2 通过单个 TCP 连接运行,所以在 TCP 层处理的数据包丢失检测和重传可以阻止所有流。QUIC 通过 UDP 运行多个流,并为每个流独立实现数据包丢失检测和重传,因此如果发生错误,只有该数据包中包含数据的流才会被阻止。

# http 消息

HTTP 请求和响应具有相似的结构,由以下部分组成:

  1. 一行起始行用于描述要执行的请求,或者是对应的状态,成功或失败。这个起始行总是单行的。
  2. 一个可选的 HTTP 标头集合指明请求或描述消息主体(body)。
  3. 一个空行指示所有关于请求的元数据已经发送完毕。
  4. 一个可选的包含请求相关数据的主体(比如 HTML 表单内容),或者响应相关的文档。主体的大小有起始行的 HTTP 头来指定。

起始行和 HTTP 消息中的 HTTP 头统称为请求头,而其有效负载被称为消息主体。

Requests and responses share a common structure in HTTP

# 请求:

# 起始行

HTTP 请求是由客户端发出的消息,用来使服务器执行动作。起始行(start-line)包含三个元素:

一个 HTTP 方法,一个动词(像 GET、PUT 或者 POST)或者一个名词(像 HEAD 或者 OPTIONS),描述要执行的动作。例如,GET 表示要获取资源,POST 表示向服务器推送数据(创建或修改资源,或者产生要返回的临时文件)。

请求目标(request target),通常是一个 URL,或者是协议、端口和域名的绝对路径,通常以请求的环境为特征。请求的格式因不同的 HTTP 方法而异。它可以是:

  • POST / HTTP/1.1
  • GET /background.png HTTP/1.0
  • HEAD /test.html?query=alibaba HTTP/1.1
  • OPTIONS /anypage.html HTTP/1.0
  • GET http://uuu.org/en-US/docs/Web/HTTP/Messages HTTP/1.1
  • OPTIONS * HTTP/1.1

# 标头

来自请求的 HTTP 标头遵循和 HTTP 标头相同的基本结构:不区分大小写的字符串,紧跟着的冒号(’:’)和一个结构取决于标头的值。整个标头(包括值)由一行组成,这一行可以相当长。

通用标头(General header),例如 Via,适用于整个消息。

请求标头(Request header),例如 User-Agent、Accept-Type,通过进一步的定义(例如 Accept-Language)、给定上下文(例如 Referer)或者进行有条件的限制(例如 If-None)来修改请求。

表示标头(Representation header),例如 Content-Type 描述了消息数据的原始格式和应用的任意编码(仅在消息有主体时才存在)。

Example of headers in an HTTP request

# 主体

请求的最后一部分是它的主体。不是所有的请求都有一个主体:例如获取资源的请求,像 GETHEADDELETEOPTIONS ,通常它们不需要主体。有些请求将数据发送到服务器以便更新数据:常见的的情况是 POST 请求(包含 HTML 表单数据)。

# 响应:

HTTP 响应的起始行被称作状态行(status line),包含以下信息:

# 状态行

协议版本,通常为 HTTP/1.1。

状态码(status code),表明请求是成功或失败。常见的状态码是 200、404 或 302。

状态文本(status text)。一个简短的,纯粹的信息,通过状态码的文本描述,帮助人们理解该 HTTP 消息。

# 标头

通用标头(General header),例如 Via,适用于整个消息。

响应标头(Response header),例如 Vary 和 Accept-Ranges,提供有关服务器的其他信息,这些信息不适合状态行。

表示标头(Representation header),例如 Content-Type 描述了消息数据的原始格式和应用的任意编码(仅在消息有主体时才存在)。

Example of headers in an HTTP response

# 主体

响应的最后一部分是主体。不是所有的响应都有主体:具有状态码(如 201 或 204)的响应,通常不会有主体。

# URL

一般情况下,资源的名称和位置由同一个 URL(统一资源定位符,它是 URI 的一种)来标识。它也被称为 Web 地址

1
2
3
4
https://a.baidu.org
https://a.baidu.org/en-US/docs/Learn/
https://a.baidu.org/en-US/search?q=URL
http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument

# URL 的语法

http://www.example.com:80/path/to/my.html?key1=value1&key2=value2#SomeWhere

协议:

http:// 告诉浏览器使用何种协议。对于大部分 Web 资源,通常使用 HTTP 协议或其安全版本,HTTPS 协议。另外,浏览器也知道如何处理其他协议。例如, mailto: 协议指示浏览器打开邮件客户端; ftp: 协议指示浏览器处理文件传输。常见的方案有:

image-20230324100318367

主机:

www.example.com 既是一个域名,也代表管理该域名的机构。它指示了需要向网络上的哪一台主机发起请求。当然,也可以直接向主机的 IP address 地址发起请求。但直接使用 IP 地址的场景并不常见。

端口:

:80 是端口。它表示用于访问 Web 服务器上资源的种类。如果访问的该 Web 服务器使用 HTTP 协议的标准端口(HTTP 为 80,HTTPS 为 443)授予对其资源的访问权限,则通常省略此部分。否则端口就是 URI 必须的部分。

路径:

/path/to/myfile.html 是 Web 服务器上资源的路径。在 Web 的早期,类似这样的路径表示 Web 服务器上的物理文件位置。现在,它主要是由没有任何物理实体的 Web 服务器抽象处理而成的。

参数:

?key1=value1&key2=value2 是提供给 Web 服务器的额外参数。这些参数是用 & 符号分隔的键 / 值对列表。Web 服务器可以在将资源返回给用户之前使用这些参数来执行额外的操作。每个 Web 服务器都有自己的参数规则。

锚点:

#SomewhereInTheDocument 是资源本身的某一部分的一个锚点。锚点代表资源内的一种 “书签”,它给予浏览器显示位于该 “加书签” 点的内容的指示。例如,在 HTML 文档上,浏览器将滚动到定义锚点的那个点上;在视频或音频文档上,浏览器将转到锚点代表的那个时间。 # 号后面的部分,也称为片段标识符,永远不会与请求一起发送到服务器。

# mime 类型

mime 即响应头中的 content-type 字段的取值

image-20230324101213596

主要的 mime 类型

  • text/plain 表示文本文件的默认值。一个文本文件应当是人类可读的,并且不包含二进制数据。
  • application/octet-stream 表示所有其他情况的默认值。一种未知的文件类型应当使用此类型。浏览器在处理这些文件时会特别小心,试图防止、避免用户的危险行为。
  • image/gif 表示图片格式为 gif 的 content-type
  • image/jpeg
  • text/html
  • image/svg+xml

# http 状态码

HTTP 响应状态码用来表明特定 HTTP 请求是否成功完成。 响应被归为以下五大类:

信息响应 (100–199)

1
2
3
4
5
6
7
8
100 Continue
这个临时响应表明,迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。

101 Switching Protocols
该代码是响应客户端的 Upgrade (en-US) 请求头发送的,指明服务器即将切换的协议。允许将一个已建立的连接升级成新的、不相容的协议。

102 Processing (en-US) (WebDAV)
此代码表示服务器已收到并正在处理该请求,但当前没有响应可用。

成功响应 (200–299)

1
2
3
4
5
6
7
200 OK

201 Created
该请求已成功,并因此创建了一个新的资源。这通常是在 POST 请求,或是某些 PUT 请求之后返回的响应。

202 Accepted
请求已经接收到,但还未响应,没有结果。意味着不会有一个异步的响应去表明当前请求的结果,预期另外的进程和服务去处理请求或者批处理。

重定向消息 (300–399)

1
2
3
4
5
6
7
8
300 Multiple Choice
请求拥有多个可能的响应。用户代理或者用户应当从中选择一个。(没有标准化的方法来选择其中一个响应,但是建议使用指向可能性的 HTML 链接,以便用户可以选择。)

301 Moved Permanently
请求资源的 URL 已永久更改。在响应中给出了新的 URL。

302 Found
此响应代码表示所请求资源的 URI 已 暂时 更改。未来可能会对 URI 进行进一步的改变。因此,客户机应该在将来的请求中使用这个相同的 URI。

客户端错误响应 (400–499)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
400 Bad Request
由于被认为是客户端错误(例如,错误的请求语法、无效的请求消息帧或欺骗性的请求路由),服务器无法或不会处理请求。

401 Unauthorized
虽然 HTTP 标准指定了"unauthorized",但从语义上来说,这个响应意味着"unauthenticated"。也就是说,客户端必须对自身进行身份验证才能获得请求的响应。

402 Payment Required 实验性
此响应代码保留供将来使用。创建此代码的最初目的是将其用于数字支付系统,但是此状态代码很少使用,并且不存在标准约定。

403 Forbidden
客户端没有访问内容的权限;也就是说,它是未经授权的,因此服务器拒绝提供请求的资源。与 401 Unauthorized 不同,服务器知道客户端的身份。

404 Not Found
服务器找不到请求的资源。在浏览器中,这意味着无法识别 URL。在 API 中,这也可能意味着端点有效,但资源本身不存在。服务器也可以发送此响应,而不是 403 Forbidden,以向未经授权的客户端隐藏资源的存在。这个响应代码可能是最广为人知的,因为它经常出现在网络上。

服务端错误响应 (500–599)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
500 Internal Server Error
服务器遇到了不知道如何处理的情况。

501 Not Implemented
服务器不支持请求方法,因此无法处理。服务器需要支持的唯二方法(因此不能返回此代码)是 GET and HEAD.

502 Bad Gateway
此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。

503 Service Unavailable
服务器没有准备好处理请求。常见原因是服务器因维护或重载而停机。请注意,与此响应一起,应发送解释问题的用户友好页面。这个响应应该用于临时条件和如果可能的话,HTTP 标头 Retry-After 字段应该包含恢复服务之前的估计时间。网站管理员还必须注意与此响应一起发送的与缓存相关的标头,因为这些临时条件响应通常不应被缓存。

504 Gateway Timeout
当服务器充当网关且无法及时获得响应时,会给出此错误响应。

# http header

HTTP 标头(header)允许客户端和服务器通过 HTTP 请求(request)或者响应(response)传递附加信息。一个 HTTP 标头由它的名称(不区分大小写)后跟随一个冒号( : ),冒号后跟随它具体的值。

根据不同的消息上下文,标头可以分为:

  • 请求标头包含有关要获取的资源或客户端或请求资源的客户端的更多信息。
  • 响应标头包含有关响应的额外信息,例如响应的位置或者提供响应的服务器。
  • 表示标头包含资源主体的信息,例如主体的 MIME 类型或者应用的编码 / 压缩方案。
  • 有效负荷标头包含有关有效载荷数据表示的单独信息,包括内容长度和用于传输的编码。

端到端(End-to-end)标头

这类标头必须被传输到最终的消息接收者:请求的服务器或者响应的客户端。中间的代理必须重新转发这些未经修改的标头,并且必须缓存它们。

逐跳(Hop-by-hop)标头

这类标头仅对单次传输连接有意义,并且不得由代理重传或者缓存。注意,只能使用 Connection 标头来设置逐跳标头。

# 缓存 header

[ Age ]

对象在代理缓存中的时间(以秒为单位)。

[ Cache-Control ]

请求和响应中缓存机制的指令。

[ Clear-Site-Data ]

清除与请求网站相关联的浏览器数据(例如 cookie、storage、cache)。

[ Expires ]

响应被视为过时的日期 / 时间。

[ Pragma ]

特定于实现的标头可能会在请求 - 响应链(request-response chain)的任何地方产生各种影响。用于向后兼容 Cache-Control 标头尚不存在的 HTTP/1.0 缓存。

# 用户代理客户端提示

[用户代理客户端提示] 是请求标头,其提供有关用户代理、它运行的平台 / 架构以及在用户代理或平台上设置的用户首选项信息:

Sec-CH-Prefers-Reduced-Motion (en-US)
用户代理的减少动画运动首选项设置。

Sec-CH-UA
用户代理的品牌(brand)和版本。

Sec-CH-UA-Arch
用户代理的底层平台架构。

Sec-CH-UA-Bitness
用户代理的底层 CPU 架构位数(例如 “64” 位)。

Sec-CH-UA-Full-Version
用户代理的完整语义版本字符串。

Sec-CH-UA-Full-Version-List
用户代理品牌(brand)列表中每个品牌的完整版本。

Sec-CH-UA-Mobile (en-US)
用户代理是否在手机设备上运行,或者更一般地说,更偏好 “手机” 用户体验。

Sec-CH-UA-Model (en-US)
用户代理的设备模型。

Sec-CH-UA-Platform (en-US)
用户代理的底层操作系统 / 平台。

Sec-CH-UA-Platform-Version (en-US)
用户代理的底层操作系统版本。

1
2
3
sec-ch-ua: "Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

# 条件

Last-Modified
资源的最后修改日期,用于比较同一个资源的多个版本。它不如 ETag 准确,但在某些环境中更容易计算。使用 If-Modified-Since 和 If-Unmodified-Since 的条件请求可以使用此值来更改请求的行为。

ETag
标识资源版本的唯一字符串。使用 If-Match 和 If-None-Match 的条件请求使用此值来更改请求的行为。

If-Match
使请求有条件,并且仅当存储的资源与给定的 ETag 之一匹配时才应用该方法。

If-None-Match
使请求有条件,并且仅当存储的资源与给定的 ETag 都不匹配时才应用该方法。这用于更新缓存(用于安全请求),或防止在资源已存在时上传新资源。

If-Modified-Since
使请求有条件,并期望只有在给定日期后修改资源时才请求传输资源。仅当缓存过期时才用于传输数据。

If-Unmodified-Since
使请求有条件,并期望只有在给定日期后资源未被修改时才请求传输资源。这确保了特定范围的新片段与先前片段的一致性,或者在修改现有文档时实现乐观的(optimistic)并发控制系统。

Vary
确定如何匹配请求标头以决定是否可以使用缓存的响应而不是从源服务器请求新的响应。

1
2
3
4
etag: W/"fbedb4e6bbd9a63237f481b1d785dd6b"
vary: Accept-Encoding
if-modified-since: Fri, 24 Mar 2023 00:59:26 GMT
if-none-match: W/"fbedb4e6bbd9a63237f481b1d785dd6b"

# 连接管理

Connection
控制当前事务完成后网络连接是否保持打开状态。

Keep-Alive
控制持久连接应保持打开状态的时间。

# 内容协商

Accept
通知服务器可以发回的数据类型。

Accept-Encoding
编码算法,通常是压缩算法,用于返回的资源。

Accept-Language
通知服务器有关服务器预期返回的人类语言。这是一个提示,不一定在用户的完全控制之下

Cookie
包含先前由服务器使用 Set-Cookie 标头发送存储的 HTTP cookie。

Set-Cookie
将 cookie 从服务器发送到用户代理。

1
cookie: _ga=GA1.2.2039740383.1663417057; _gid=GA1.2.901079682.1679623065; preferredlocale=zh-CN

# 消息主体类型

Content-Length
资源的大小,以十进制字节数表示。

Content-Type
指示资源的媒体类型。

Content-Encoding
用于指定压缩算法。

Content-Language
描述面向受众的人类语言,以便用户可以根据自己的首选语言进行区分。

Content-Location
指示返回数据的备用位置。

1
2
3
content-encoding: gzip
content-language: zh-CN
content-type: text/html;charset=utf-8

# 代理

Forwarded
包含来自代理服务器面向客户端的信息,当请求路径中涉及代理时,这些信息会被更改或丢失。

X-Forwarded-For 非标准
标识通过 HTTP 代理或负载均衡器(load balancer)连接到 Web 服务器的客户端的原始 IP 地址。

X-Forwarded-Host 非标准
标识请求客户端用于连接到你的代理或负载均衡器(load balancer)的原始主机。

X-Forwarded-Proto 非标准
标识客户端用于连接到你的代理或负载均衡器(load balancer)的协议(HTTP 或 HTTPS)。

Via
由代理添加,包括正向和反向代理,并且可以出现在请求标头和响应标头中。

1
2
via: 1.1 af9d66efe7802df1efbc8106c86a13e6.cloudfront.net (CloudFront)
x-forwarded-for: 127.0.0.1

# reloacation

Location
指示要将页面重定向到的 URL。

Refresh
指示浏览器重新加载页面或重定向到另一个页面。采用与带有 http-equiv=“refresh” 的 meta 元素相同的值。

# 请求上下文

Host
指定服务器的域名(用于虚拟主机)和(可选)服务器侦听的 TCP 端口号。

Referer
前一个网页的地址,表示从该网页链接(进入)到当前请求的页面。

Referrer-Policy
管理 Referer 标头中发送的哪些引用信息应包含在发出的请求中。

User-Agent
包含一个特征字符串,允许网络协议对端识别发起请求的用户代理软件的应用程序类型、操作系统、软件供应商或软件版本。

# 安全

Cross-Origin-Embedder-Policy(COEP)
允许服务器为给定文档声明嵌入器(embedder)策略。

Cross-Origin-Embedder-Policy(COEP)
允许服务器为给定文档声明嵌入器(embedder)策略。

Cross-Origin-Embedder-Policy(COEP)
允许服务器为给定文档声明嵌入器(embedder)策略。

X-Frame-Options (XFO)
指示是否应允许浏览器在 <frame><iframe><embed> <object> 中呈现页面。

X-XSS-Protection
启用跨站点脚本过滤。

1
2
3
4
x-cache: Hit from cloudfront
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 1; mode=block

# fetch 元数据请求标头

这允许服务器根据请求的来源和资源将如何使用来决定是否允许请求。

Sec-Fetch-Site
它是一个请求标头,指示请求发起者的源与其目标源之间的关系。它是一个结构化标头(Structured Header),其值是一个标记,可能的值有 cross-site、same-origin、same-site 和 none。

Sec-Fetch-Mode
它是一个请求标头,向服务器指示请求的模式。它是一个结构化标头(Structured Header),其值是一个标记,可能的值有 cors、navigate、no-cors、same-origin 和 websocket。

Sec-Fetch-User
它是一个请求标头,指示导航请求是否由用户激活触发。它是一个结构化标头(Structured Header),其值为布尔值,因此可能的值为?0 表示 false,?1 表示 true。

Sec-Fetch-Dest
它是一个请求标头,指示请求到服务器的目的地。它是一个结构化标头(Structured Header),其值为具有可能值的标记 audio、audioworklet、document、embed、empty、font、image、manifest、object、paintworklet、report、script、serviceworker、sharedworker、style、track、video、worker 和 xslt。

Service-Worker-Navigation-Preload (en-US)
在 service worker 启动期间以抢占式请求发送到 fetch () 资源的请求标头。该值由 NavigationPreloadManager.setHeaderValue () (en-US) 设置,可用于通知服务器应返回与正常 fetch () 操作不同的资源。

1
2
3
4
sec-fetch-dest: document
sec-fetch-mode: navigate
sec-fetch-site: same-origin
sec-fetch-user: ?1

# http 请求方法

HTTP 定义了一组请求方法,以表明要对给定资源执行的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
GET
GET 方法请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。

HEAD
HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。

POST
POST 方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。

PUT
PUT 方法用请求有效载荷替换目标资源的所有当前表示。

DELETE
DELETE 方法删除指定的资源。

CONNECT
CONNECT 方法建立一个到由目标资源标识的服务器的隧道。

OPTIONS
OPTIONS 方法用于描述目标资源的通信选项。

TRACE
TRACE 方法沿着到目标资源的路径执行一个消息环回测试。

PATCH
PATCH 方法用于对资源应用部分修改。

# http 连接管理

# 短连接

HTTP 最早期的模型和 HTTP/1.0 的默认模型,是短连接。每一个 HTTP 请求都由它自己独立的连接完成;这意味着发起每一个 HTTP 请求之前都会有一次 TCP 握手,而且是连续不断的。

这是 HTTP/1.0 的默认模型(如果没有指定 Connection 协议头,或者是值被设置为 close )。而在 HTTP/1.1 中,只有当 Connection 被设置为 close 时才会用到这个模型。

# 长连接

一个长连接会保持一段时间,重复用于发送一系列请求,节省了新建 TCP 连接握手的时间,还可以利用 TCP 的性能增强能力。当然这个连接也不会一直保留着:连接在空闲一段时间后会被关闭(服务器可以使用 Keep-Alive 协议头来指定一个最小的连接保持时间)。

长连接也还是有缺点的;就算是在空闲状态,它还是会消耗服务器资源,而且在重负载时,还有可能遭受 DoS 攻击。这种场景下,可以使用非长连接,即尽快关闭那些空闲的连接,也能对性能有所提升。

HTTP/1.0 里默认并不使用长连接。把 Connection 设置成 close 以外的其他参数都可以让其保持长连接,通常会设置为 retry-after

# 流水线

默认情况下,HTTP 请求是按顺序发出的。下一个请求只有在当前请求收到响应过后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。

流水线是在同一条长连接上发出连续的请求,而不用等待应答返回。这样可以避免连接延迟。理论上讲,性能还会因为两个 HTTP 请求有可能被打包到一个 TCP 消息包中而得到提升。

并不是所有类型的 HTTP 请求都能用到流水线:只有比如 GETPUTDELETE 能够被安全地重试。

Compares the performance of the three HTTP/1.x connection models: short-lived connections, persistent connections, and HTTP pipelining.

要注意的一个重点是 HTTP 的连接管理适用于两个连续节点之间的连接,它是逐跳(Hop-by-hop)标头,而不是端到端(End-to-end)标头。当模型用于从客户端到第一个代理服务器的连接和从代理服务器到目标服务器之间的连接时(或者任意中间代理)效果可能是不一样的。HTTP 协议头受不同连接模型的影响,比如 Connection 和 Keep-Alive,就是逐跳标头标头,它们的值是可以被中间节点修改的。

# tcp 保活机制

如果一个给定的连接在两个小时之内没有任何动作,则服务器就向客户发送一个探查报文段。客户主机必须处于以下 4 个状态之一。

  • 状态 1:客户主机依然正常运行,并从服务器可达。客户的 TCP 响应正常,而服务器也知道对方是正常工作的。服务器在两小时以后将保活定时器复位。如果在两个小时定时器到时间之前有应用程序的通信量通过此连接,则定时器在交换数据后的未来 2 小时再复位。
  • 状态 2:客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的 TCP 都没有响应。服务器将不能够收到对探查的响应,并在 75 秒后超时。服务器总共发送 10 个这样的探查,每个间隔 75 秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
  • 状态 3:客户主机崩溃并已经重新启动。这时服务器将收到一个对其保活探查的响应,但是这个响应是一个复位,使得服务器终止这个连接。
  • 状态 4:客户主机正常运行,但是从服务器不可达。这与状态 2 相同,因为 TCP 不能够区分状态 4 与状态 2 之间的区别,它所能发现的就是没有收到探查的响应。

Linux 相关的 TCP 保活参数

  • tcp_keepalive_time ,单位:秒,表示发送的探测报文之前的连接空闲时间,默认是 7200s。
  • tcp_keepalive_intvl ,单位:秒,表示两次探测报文之间的间隔时间,默认是 75s
  • tcp_keepalive_probes ,单位,秒,表示探测的次数,默认是 9

# 一些乱七八糟的问题

长连接的情况怎么区分数据包属于哪一个请求 - content-length

流水线怎么知道消息属于哪个请求:HTTP2 引入二进制分帧,用 stream id 标识帧和请求的对应关系。

http1.1 长连接存在哪些问题:队头阻塞

详细解释:

客户端和服务器端发现对方一段时间内没有活动,就可以主动关闭连接。不过规范的做法是,客户端在最后一个请求时,发送 Connection: close,明确要求服务器关闭 TCP 连接。

对同一个域名,大多数浏览器允许同时建立 6 个持久连接。

以前发送请求后需要等待并接收响应,才能发送下一个请求。管线化技术出现后,不用等待响应即可直接发送下一个请求,这样就能够做到同时并行发送多个请求,而不需要一个接一个的等待响应了,与挨个连接相比,用持久连接可以让请求更快结束。而管线化技术则比持久连接要快的多,请求数越多,时间差就越明显。

一个 TCP 连接可以传回多个响应,势必就要有一种机制,区分数据包是属于哪一个响应的,这就是 Content-Length 字段的作用,声明本次回应的数据长度。

上面的代码告诉浏览器,本次响应的长度是 3495 个字节,后面的字节就属于下一个回应了。在 1.0 版本中,Content-Length 字段不是必须的,因为浏览器发现服务器关闭了 TCP 连接,就表明收到的数据包已经全了。

虽然 HTTP1.1 版本允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按照次序进行的,所以服务器只有处理完一个响应,才会进行下一个响应,如果前面的响应特别慢,后面就会有许多请求排队等待着,这就称之为队头阻塞。

流水线怎么知道哪个消息属于哪个请求

但是 HTTP2 引入二进制分帧,用 stream id 标识帧和请求的对应关系。

# 协议升级

如果服务器决定升级这次连接,就会返回一个 101 Switching Protocols 响应状态码,和一个要切换到的协议的标头字段 Upgrade。如果服务器没有(或者不能)升级这次连接,它会忽略客户端发送的 Upgrade 标头字段,返回一个常规的响应:例如一个 200 OK ).

在发送 101 状态码之后,服务器可以使用新协议,并根据需要执行任何额外的特定于协议的握手。实际上,一旦这次升级完成了,连接就变成了双向管道。并且可以通过新协议完成启动升级的请求。

客户端使用 Upgrade (en-US) 标头字段请求服务器,以降序优先的顺序切换到其中列出的一个协议。

因为 Upgrade 是一个逐跳(Hop-by-hop)标头,它还需要在 Connection 标头字段中列出。这意味着包含 Upgrade 的典型请求类似于:

1
2
3
4
GET /index.html HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: example/1, foo/2

# http 身份验证

RFC 7235 定义了一个 HTTP 身份验证框架,服务器可以用来质询(challenge)客户端的请求,客户端则可以提供身份验证凭据。

质询与响应的工作流程如下:

服务器端向客户端返回 401(Unauthorized,未被授权的)响应状态码,并在 WWW-Authenticate 响应标头提供如何进行验证的信息,其中至少包含有一种质询方式。

之后,想要使用服务器对自己身份进行验证的客户端,可以通过包含凭据的 Authorization 请求标头进行验证。

通常,客户端会向用户显示密码提示,然后发送包含正确的 Authorization 标头的请求。

img

# nginx 访问限制和 basic 认证

在 nginx 配置中,你需要指定一个要保护的 location 并且 auth_basic 指令提供密码保护区域的名称。

auth_basic_user_file 指令指定包含加密的用户凭据 .htpasswd 文件

1
2
3
4
location /status {
auth_basic "Access to the staging site";
auth_basic_user_file /etc/apache2/.htpasswd;
}

# http 数据压缩

数据压缩是提高 Web 站点性能的一种重要手段。

在实际应用时,web 开发者不需要亲手实现压缩机制,浏览器及服务器都已经将其实现了,不过他们需要确保在服务器端进行了合理的配置。

数据压缩会在三个不同的层面发挥作用:

  • 首先某些格式的文件会采用特定的优化算法进行压缩,
  • 其次在 HTTP 协议层面会进行通用数据加密,即数据资源会以压缩的形式进行端到端传输,
  • 最后数据压缩还会发生在网络连接层面,即发生在 HTTP 连接的两个节点之间。

# 文件格式压缩

每一种文件类型都会存有冗余,也就是浪费的空间。不同于文本文件,这些其他类型的媒体文件占据的空间也更大,所以很早就出现了回收这些浪费的空间的需求。

用于文件的压缩算法可以大致分为两类:无损压缩、有损压缩

有损压缩通常会比无损压缩效率更高一些。

# 端到端压缩

对于各种压缩手段来说,端到端压缩技术是 Web 站点性能提升最大的地方。端到端压缩技术指的是消息主体的压缩是在服务器端完成的,并且在传输过程中保持不变,直到抵达客户端。不管途中遇到什么样的中间节点,它们都会使消息主体保持原样。

服务器通过网络节点向客户端发送一个压缩的 HTTP 主体。该主体直到到达客户端之前,不会在网络中的任何一跳之间进行解压缩。

今只有两种算法有着举足轻重的地位: gzip 应用最广泛, br 则是新的挑战者。

客户端使用“Accept-Encoding:br, gzip”标头请求内容。服务器使用 Brotli 算法压缩的主体以及所需的“Content-Encoding”和“Vary”标头进行响应。

# 逐跳压缩

即这里的压缩指的不是对源头服务器上的资源的压缩,而是对客户端与服务器端之间的任意两个节点之间传递的消息的主体的压缩。在两个相邻的中间节点之间的连接上,可能会应用不同的压缩方式。

客户端从没有压缩相关标头的服务器请求内容。服务器会使用未经压缩的主体进行响应。该主体在到达客户端之前,由网络上的节点进行压缩和解压缩。

在实际应用中,逐跳压缩对于服务器和客户端来说是不可见的,并且很少使用。

HTTP Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储 cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器 —— 如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。

Cookie 主要用于以下三个方面:

  • 会话状态管理

    如用户登录状态、购物车、游戏分数或其他需要记录的信息

  • 个性化设置

    如用户自定义设置、主题和其他设置

  • 浏览器行为跟踪

    如跟踪分析用户行为等

# 创建 cookie

服务器收到 HTTP 请求后,服务器可以在响应标头里面添加一个或多个 Set-Cookie 选项。浏览器收到响应后通常会保存下 Cookie,并将其放在 HTTP Cookie 标头内,向同一服务器发出请求时一起发送。你可以指定一个过期日期或者时间段之后,不能发送 cookie。

服务器使用 Set-Cookie 响应头部向用户代理(一般是浏览器)发送 Cookie 信息。

1
2
3
4
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

现在,对该服务器发起的每一次新请求,浏览器都会将之前保存的 Cookie 信息通过 Cookie 请求头部再发送给服务器。

1
2
3
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

# cookie 生命周期

Cookie 的生命周期可以通过两种方式定义:

  • 会话期 Cookie 会在当前的会话结束之后删除。浏览器定义了 “当前会话” 结束的时间,一些浏览器重启时会使用会话恢复。这可能导致会话 cookie 无限延长。

  • 持久性 Cookie 在过期时间( Expires )指定的日期或有效期( Max-Age )指定的一段时间后被删除。

    1
    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2023 07:28:00 GMT

# 限制 cookie 访问

有两种方法可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问: Secure 属性和 HttpOnly 属性。

标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。它永远不会使用不安全的 HTTP 发送(本地主机除外),这意味着中间人攻击者无法轻松访问它。不安全的站点(在 URL 中带有 http:)无法使用 Secure 属性设置 cookie。但是,Secure 不会阻止对 cookie 中敏感信息的访问。

JavaScript Document.cookie API 无法访问带有 HttpOnly 属性的 cookie;此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS)攻击。

1
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

# samesite

SameSite 属性允许服务器指定是否 / 何时通过跨站点请求发送

这提供了一些针对跨站点请求伪造攻击的保护。它采用三个可能的值: StrictLaxNone

使用 Strict ,cookie 仅发送到它来源的站点。 Lax 与 Strict 相似,只是在用户导航到 cookie 的源站点时发送 cookie。例如,通过跟踪来自外部站点的链接。 None 指定浏览器会在同站请求和跨站请求下继续发送 cookie,但仅在安全的上下文中(即,如果 SameSite=None ,且还必须设置 Secure 属性)。如果没有设置 SameSite 属性,则将 cookie 视为 Lax

1
Set-Cookie: mykey=myvalue; SameSite=Strict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1.Cookie 的工作原理
(1)浏览器端第一次发送请求到服务器端

(2)服务器端创建 Cookie,该 Cookie 中包含用户的信息,然后将该 Cookie 发送到浏览器端

(3)浏览器端再次访问服务器端时会携带服务器端创建的 Cookie

(4)服务器端通过 Cookie 中携带的数据区分不同的用户

————————————————

2.Session 的工作原理

(1)浏览器端第一次发送请求到服务器端,服务器端创建一个 Session,同时会创建一个特殊的 Cookie(name 为 JSESSIONID 的固定值,value 为 session 对象的 ID),然后将该 Cookie 发送至浏览器端

(2)浏览器端发送第 N(N>1)次请求到服务器端,浏览器端访问服务器端时就会携带该 name 为 JSESSIONID 的 Cookie 对象

(3)服务器端根据 name 为 JSESSIONID 的 Cookie 的 value (sessionId), 去查询 Session 对象,从而区分不同用户。

name 为 JSESSIONID 的 Cookie 不存在(关闭或更换浏览器),返回 1 中重新去创建 Session 与特殊的 Cookie

name 为 JSESSIONID 的 Cookie 存在,根据 value 中的 SessionId 去寻找 session 对象

value 为 SessionId 不存在 **(Session 对象默认存活 30 分钟)**,返回 1 中重新去创建 Session 与特殊的 Cookie

value 为 SessionId 存在,返回 session 对象

————————————————

(1) cookie 数据存放在客户的浏览器上,session 数据放在服务器上,但是服务端的 session 的实现对客户端的 cookie 有依赖关系的;

(2) cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗,如果主要考虑到安全应当使用 session

(3) session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用 COOKIE

(4) 单个 cookie 在客户端的限制是 3K,就是说一个站点在客户端存放的 COOKIE 不能 3K。

(5) 所以:将登陆信息等重要信息存放为 SESSION; 其他信息如果需要保留,可以放在 COOKIE 中

# cookie 生成方式

1. 服务端 set-cookie

2. 程序创建,servlet,JavaScript

# http 重定向

URL 重定向(也称为 URL 转发)是一种为页面、表单或者整个 Web 站点 / 应用提供多个 URL 地址的技术。HTTP 对此操作有一种特殊类型的响应,称为 HTTP 重定向(HTTP redirect)。

在 HTTP 协议中,重定向操作由服务器向请求发送特殊的重定向响应而触发。重定向响应包含以 3 开头的状态码,以及 Location 标头,其保存着重定向的 URL。

浏览器在接收到重定向时,它们会立刻加载 Location 标头中提供的新 URL。除了额外的往返操作中会有一小部分性能损失之外,重定向操作对于用户来说是不可见的。

初始请求从客户端发送到服务器。服务器以 301:moved permanently 响应,并带有重定向的 URL。客户端对服务器返回的新 URL 发出 GET 请求,服务端返回 200 OK 响应。

# 永久重定向

这种重定向操作是永久性的。它表示原 URL 不应再被使用,而选用新的 URL 替换它。搜索引擎机器人、RSS 阅读器以及其他爬虫将更新资源原始的 URL。

image-20230325104707183

# 临时重定向

有时候请求的资源无法从其标准地址访问,但是却可以从另外的地方访问。在这种情况下,可以使用临时重定向。

搜索引擎和其他爬虫不会记录新的、临时的 URL。在创建、更新或者删除资源的时候,临时重定向也可以用于显示临时性的进度页面。(即不缓存)

image-20230325104651818

# 特殊重定向

304(Not Modified)会使页面跳转到本地的缓存副本中(可能已过时),而 300(Multiple Choice)则是一种手动重定向:将消息主体以 Web 页面形式呈现在浏览器中,列出了可能的重定向链接,用户可以从中进行选择。

image-20230325104918653

image-20230406103340378

# <meta> 标签重定向

HTTP 重定向是创建重定向的最佳方式,但是有时候你并不能控制服务器。针对这些特定的应用情景,可以尝试在页面的 中添加一个 元素,并将其 http-equiv 属性的值设置为 refresh。当显示页面的时候,浏览器会检测该元素,然后跳转到指定的页面。

1
2
3
<head>
<meta http-equiv="Refresh" content="0; URL=http://example.com/" />
</head>

content 属性的值开头是一个数字,指示浏览器在等待该数字表示的秒数之后再进行跳转。建议始终将其设置为 0 来获取更好的无障碍体验。

显然,该方法仅适用于 HTML 页面(或类似的页面),然而并不能应用于图片或者其他类型的内容。

# javascript 重定向

在 JavaScript 中,重定向机制的原理是设置 window.location 的属性值,然后加载新的页面。

1
window.location = "https://example.com/";

与 HTML 重定向机制类似,这种方式并不适用于所有类型的资源,并且显然只有在执行 JavaScript 的客户端上才能使用。另外一方面,它也提供了更多的可能性:比如在只有满足了特定的条件的情况下才可以触发重定向机制的场景。

# http,meta,javascript 重定向优先级

HTTP 协议的重定向机制永远最先触发 —— 它们甚至在没有传输页面的情况下就已经存在。

HTML 的重定向机制 () 会在没有任何 HTTP 协议重定向的情况下触发。

JavaScript 的重定向机制总是作为最后手段,并且只有在客户端开启了 JavaScript 的情况下才起作用。

# 应用场景

扩大站点的用户覆盖面
一个常见的场景是,假如站点位于 www.example.com 域名下,那么通过 example.com 也应该可以访问到。这种情况下,可以建立从 example.com 的页面到 www.example.com 的重定向。此外还可以提供你域名常见的同义词,或者该域名容易导致的拼写错误的别称来重定向到你的网站。

迁移到新的域名
例如,公司改名后,你希望用户在搜索旧名称的时候,依然可以访问到应用了新名称的站点。

强制使用 HTTPS
对你网站的 http:// 版本的请求将重定向到你网站的 https:// 版本。

# 在 nginx 中配置重定向

1
2
3
4
5
6
server {
listen 80;
server_name example.com;
return 301 $scheme://www.example.com$request_uri;
}

要将重定向应用于目录或者仅是部分页面,请使用 rewrite 指令:

1
2
rewrite ^/images/(.*)$ http://images.example.com/$1 redirect;
rewrite ^/images/(.*)$ http://images.example.com/$1 permanent;

# 重定向死锁

当后续的重定向路径重复之前的路径的时候,重定向循环就产生了。换句话说,就是陷入了无限循环当中,不会有一个最终的页面返回。

大多数情况下,这属于服务器端错误。如果服务器检测不到,就会返回 500 Internal Server Error 。假如你在修改了服务器配置不久就出现了这个问题,八成是遇到了重定向循环。

有时候,服务器端无法对其进行检测:重定向循环发生于多台服务器之间,对于每一台服务器来说,都无法获得一个全景图。在这种情况下,浏览器会负责进行检测,然后返回错误信息。

Chrome 则会呈现如下信息:

该网页将您重定向的次数过多。

# 304 Not modify

304 Not Modified (服务器端资源未改变,可直接使用客户端未过期的缓存)

对 304 作出的解释是:“该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回 304。304 状态码返回时,不包含任何响应的主体部分 (带头不带体,减小报文体积,加大传输效率) 。304 虽然被划分在 3XX 中,但是和重定向没有关系”

在浏览器第一次给服务器发送请求后,服务器会在响应头中加上 Last-Modified 这个字段表示该资源将缓存到客户端。客户端再请求一个文件的时候,发现自己缓存的文件有 Last-Modified ,那么在请求头携带 If-Modified-Since 字段,值就是缓存文件的 Last-Modified 。此时服务端只要判断 If-Modified-Since 的值和资源最后修改时间就可以确定是返回 304 还是 200。

当浏览器准备向服务器发起请求时,首先会通过校验强缓存是否可用,如果可用则直接使用 (此时请求依旧会返回 200 状态,但并无与服务端交互)。否则进入协议缓存,即发送 http 请求。

# 1. 强缓存

强缓存阶段是不需要发送 http 请求的,这个阶段只需对上次请求的响应头字段进行判断,如果资源还在可用期,直接复用缓存资源。

在 HTTP/1.0 时代,使用的是 Expires,而 HTTP/1.1 使用的是 Cache-Control。

# 1.1 Expires

请求头会带一个 Expires 字段,表示资源过期时间,下次请求时,只需将当前时间与 Expires 比对,即可获知缓存是否可用

1
2
Expires: Wed, 22 Nov 2019 08:41:00 GMT
复制代码

但是存在一个问题就是,Expires 是与客户端时间比对,故会存在客户端与服务端时间不一致的情况。故此方法在 HTTP/1.1 被废弃了

# 1.2 Cache-Control

在 HTTP1.1 中,采用了一个非常关键的字段:Cache-Control。这个字段也是存在于服务器返回的响应头中。

它和 Expires 本质的不同在于它并没有采用 “具体的过期时间点” ,而是采用过期时长来控制缓存。对应的字段是 max-age。比如:

1
2
//表示在3600秒内 可直接使用缓存
Cache-Control:max-age=3600

除了 max-age 外,它还存在其他属性:

  • public:表示客户端和代理服务器都可以缓存。因为通常一个请求可能给要经过不同的代理服务器最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存
  • private:这种情况下就是只有浏览器才能缓存,中间的代理服务器不能缓存
  • no-cache:跳过当前的强缓存,发送 http 请求,直接进入协商缓存阶段
  • no-store:不进行任何形式的缓存
  • s-maxage:这和 max-age 长得比较像,但是区别在于 s-maxage 是针对代理服务器的缓存时间
  • must-revalidate:加上这个字段,一旦缓存过期。就必须回到源服务器验证 (因为 HTTP 规范是允许客户端在某些特殊情况下直接使用过期缓存的,比如校验请求发送失败的时候,还比如有配置一些特殊指令( stale-while-revalidatestale-if-error 等)的时候)
1
当Expires和Cache-Control同时存在的时候,Cache-Control会优先考虑

当个强缓存失效了,那么就进入下一阶段 —— 协商缓存

# 2. 协商缓存

当强缓存失效后,即会想服务器发送请求。除首次发送请求外,后续的请求会在请求头带上一个 缓存 tag ,用来校验客户端缓存是否可用

缓存 tag 大致可分为两种:Last-Modified 和 ETag

# 2.1 Last-Modified

客户端首次发送请求的时候,服务端会在响应头里带上 Last-Modified,该字段表示资源最后修改时间。当客户端再次请求时,会在请求头带上 If-Modified-Since 字段,这个字段也就是上次请求返回回来的 Last-Modified 的值

服务端在拿到 If-Modified-Since 字段后,与服务器中资源最后修改时间进行比对:

  • 如果 If-Modified-Since 值小于服务器资源最后修改时间,证明资源已经更新。此时将最新的资源与最新的修改时间返回
  • 否则,返回 304,告诉客户端缓存可用
# 2.2 ETag

ETag 是根据文件内容,生成一个标识 ( 类似文件哈希 ),并且将此标识作为文件资源是否更新的依据。

客户端接收到 ETag 的值,会在下次请求时,将这个值作为 If-None-Match 这个字段的内容,并放到请求头中,然后发给服务器。

服务器接收到 If-None-Match 后,会跟服务器上该资源的 ETag 进行比对:

  • 如果两者不一样,说明要更新了。服务器返回新的资源,跟常规的 HTTP 请求响应的流程一样
  • 否则,返回 304,告诉客户端缓存可用

在精度上,ETag 优于 Last-Modified。因为 ETag 是按照内容给资源上标识,所以能够准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:

  • 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失败
  • Last-Modified 能够感知的单位是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改

在性能上,Last-Modified 优于 Etag, 原因也很简单,Last-Modified 仅仅只是记录一个时间点,而 ETag 需要根据文件的具体内容生成哈希值

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

# http 请求范围

HTTP 协议范围请求允许服务器只发送 HTTP 消息的一部分到客户端。范围请求在传送大的媒体文件,或者与文件下载的断点续传功能搭配使用时非常有用。

假如在响应中存在 Accept-Ranges 首部(并且它的值不为 “none”),那么表示该服务器支持范围请求。例如,你可以使用 cURL 发送一个 HEAD 请求来进行检测。

1
2
3
4
5
6
7
curl -I http://i.imgur.com/z4d4kWk.jpg

HTTP/1.1 200 OK
...
Accept-Ranges: bytes
Content-Length: 146515

在上面的响应中, Accept-Ranges: bytes 表示界定范围的单位是 bytes。这里 Content-Length 也是有效信息,因为它提供了要检索的图片的完整大小。

如果站点未发送 Accept-Ranges 首部,那么它们有可能不支持范围请求。一些站点会明确将其值设置为 “none”,以此来表明不支持。在这种情况下,某些应用的下载管理器会将暂停按钮禁用。

假如服务器支持范围请求的话,你可以使用 Range 首部来生成该类请求。该首部指示服务器应该返回文件的哪一或哪几部分。

在这个例子中,是用 Range 首部来请求图片文件的前 1024 个字节

1
curl http://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023"

服务器端会返回状态码为 206 artial Content 的响应:

1
2
3
4
5
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/146515
Content-Length: 1024
...
(binary content)

在这里,Content-Length 首部现在用来表示先前请求范围的大小(而不是整张图片的大小)。Content-Range 响应首部则表示这一部分内容在整个资源中所处的位置。

条件时范围请求

当(中断之后)重新开始请求更多资源片段的时候,必须确保自从上一个片段被接收之后该资源没有进行过修改。

The If-Range 请求首部可以用来生成条件式范围请求:假如条件满足的话,条件请求就会生效,服务器会返回状态码为 206 Partial 的响应,以及相应的消息主体。假如条件未能得到满足,那么就会返回状态码为 200 OK 的响应,同时返回整个资源。该首部可以与 Last-Modified 验证器或者 ETag 一起使用,但是二者不能同时使用。

1
If-Range: Wed, 21 Oct 2015 07:28:00 GMT

在请求成功的情况下,服务器会返回 206 Partial Content 状态码。

在请求的范围越界的情况下(范围值超过了资源的大小),服务器会返回 416 Requested Range Not Satisfiable (请求的范围无法满足)状态码。

在不支持范围请求的情况下,服务器会返回 200 OK 状态码。

# HTTP 缓存

HTTP 缓存会存储与请求关联的响应,并将存储的响应复用于后续请求。

可复用性有几个优点。首先,由于不需要将请求传递到源服务器,因此客户端和缓存越近,响应速度就越快。最典型的例子是浏览器本身为浏览器请求存储缓存。

此外,当响应可复用时,源服务器不需要处理请求 —— 因为它不需要解析和路由请求、根据 cookie 恢复会话、查询数据库以获取结果或渲染模板引擎。这减少了服务器上的负载。

# 私有缓存

私有缓存是绑定到特定客户端的缓存 —— 通常是浏览器缓存。由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应。

如果响应包含个性化内容并且你只想将响应存储在私有缓存中,则必须指定 private 指令。

1
Cache-Control: private

# 共享缓存

共享缓存位于客户端和服务器之间,可以存储能在用户之间共享的响应。共享缓存可以进一步细分为代理缓存托管缓存

# 代理缓存

除了访问控制的功能外,一些代理还实现了缓存以减少网络流量。这通常不由服务开发人员管理,因此必须由恰当的 HTTP 标头等控制。

# 托管缓存

托管缓存由服务开发人员明确部署,以降低源服务器负载并有效地交付内容。示例包括反向代理、CDN 和 service worker 与缓存 API 的组合。

# chunk

当客户端向服务器请求一个静态页面或者一张图片时,服务器可以很清楚的知道内容大小,然后通过 Content-Length 消息首部字段告诉客户端需要接收多少数据。但是如果是动态页面等时,服务器是不可能预先知道内容大小,这时就可以使用 Transfer-Encoding:chunk 模式来传输数据了。即如果要一边产生数据,一边发给客户端,服务器就需要使用 "Transfer-Encoding: chunked" 这样的方式来代替 Content-Length。

在进行 chunked 编码传输时,在回复消息的头部有 Transfer-Encoding: chunked

编码使用若干个 chunk 组成,由一个标明长度为 0 的 chunk 结束。每个 chunk 有两部分组成,第一部分是该 chunk 的长度,第二部分就是指定长度的内容,每个部分用 CRLF 隔开。在最后一个长度为 0 的 chunk 中的内容是称为 footer 的内容,是一些没有写的头部内容。

chunk 编码格式如下:

1
2
3
[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]

chunk size是以十六进制的ASCII码表示,比如:头部是3134这两个字节,表示的是1和4这两个ascii字符,被http协议解释为十六进制数14,也就是十进制的20,后面紧跟[\r\n](0d 0a),再接着是连续的20个字节的chunk正文。chunk数据以0长度的chunk块结束,也就是(30 0d 0a 0d 0a)。

分块编码主要应用于如下场景,即要传输大量的数据,但是在请求在没有被处理完之前响应的长度是无法获得的。例如,当需要用从数据库中查询获得的数据生成一个大的 HTML 表格的时候,或者需要传输大量的图片的时候。一个分块响应形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
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

# SNI

Server Name Indication (SNI) 是 TLS 协议(以前称为 SSL 协议)的扩展,该协议在 HTTPS 中使用。它包含在 TLS/SSL 握手流程中,以确保客户端设备能够看到他们尝试访问的网站的正确 SSL 证书。该扩展使得可以在 TLS 握手期间指定网站的主机名或域名 ,而不是在握手之后打开 HTTP 连接时指定。

SNI 通过让客户端发送虚拟域的名称作为 TLS 协商的 ClientHello 消息的一部分来解决此问题。这使服务器可以及早选择正确的虚拟域,并向浏览器提供包含正确名称的证书。

服务器名称指示(SNI)有效负载未加密,因此客户端尝试连接的服务器的主机名对于被动的窃听者是可见的。

在客户端到服务端方向的 ClientHello 包中,提取 SNI 扩展字段中的 SNI_NAME 字段,通过数据包偏移量进行逐步匹配得到 SNI_NAME。对 SNI_NAME 进行规则匹配。

它主要是用来解决一个服务器拥有多个域名的情况。通过 SNI,拥有多虚拟机主机和多域名的服务器就可以正常建立 TLS 连接了。

# http 2.0

HTTP2.0 和 SPDY 的原理很简单,就是仿照 TCP 的拆包解包来解决当前 HTTP 的队头阻塞 (Head-of-Line blocking)的问题,以实现多个请求并发传输、多路复用的效果。

http2.jpg.webp

# 二进制分帧

​ 二进制分帧,算是 HTTP2.0 最重大的改变了,HTTP2.0 的多路复用就是基于这个才得以实现。

二进制分帧是在当前 HTTPS 的 TLS 协议之上,抽象了一层(也就是说,使用 HTTP2.0 的前提是必须使用 HTTPS)。可以在传输的时候把一个请求拆分成多个很小的数据包,多个请求可以同时拆成许多数据包一起发送,到了服务端,服务端再根据数据包的序号进行拼接,得到完整的每一个请求。

这些拆分的请求最小粒度叫 frame ,按照类型可分为两类结构: Headers framedata frameheaders frame 是对请求头做了抽象, data frame 是针对请求体做了抽象。

这些拆分的请求最小粒度叫 frame ,按照类型可分为两类结构: Headers framedata frameheaders frame 是对请求头做了抽象, data frame 是针对请求体做了抽象。

binary_frame.png

除了 frame 结构外,整个二进制分帧层还有 messagestream 两种数据结构,这几种数据结构存在包含关系: frame 最小, message 包含多个 framestream 包含多个 messageframemessagestream 三种数据结构共同构成了 http2.0 的二进制分帧层。三种数据结构的联系和作用分别如下:

  • frame 是最小的传输单位,内部有特殊标识,能够区分此 frame 属于哪个 stream
  • message 是逻辑层面的东西,在具体实现中没有体现,多用于表示是请求 message 还是响应 message ,一个 messsage 包含多个 frame
  • stream 是 HTTP2.0 传输的最大粒度的 "包",它包含唯一性字段和优先级信息,能够包含请求或者相应 message
stream_message_frame.png

http2.0 的所有请求都 只在一条tcp连接中传输的 ,http2.0 会把当前所有请求拆成无数小的 frame (其实这时候已经区分出不同 stream 了)

stream1.png

然后根据各个 frame 中的标识信息 (frame 中标有 stream 标识),组成一个个的 stream,最后把各种的 stream 在一条 tcp 双向管道中进行传输。

stream2.png

虽然二进制分帧协议中有 message 结构,但是,这只是一种逻辑层面的结构,用于区分是请求还是响应信息片段,并不参与真正的协议实现。底层实现仅仅有 streamframe

client 端和 server 端在收到各类 stream 后,根据 steam 的标识,拼出完整的请求或者响应 stream 数组,再根据 stream 数组中的 frame 信息,解析出完整的请求或者响应信息。

这样,就实现了 http 在传输过程中的多路复用。

值得一提的是,在 http2.0 在传输过程中,我们不再使用纯文本,而是把请求的数据都采用二进制 (0 或者 1) 的形式进行传输,这样也减少文本转义带来的额外性能开销;

# 头部压缩

在 http1.1 传输过程中,请求体可以根据 gzip 进行压缩,但是对请求头没有做处理,随着网站请求量的增多,在 http2.0 之后,对请求头也做了压缩处理。

对于一个站点,大部分的请求中请求头的信息都是重复的,不同的仅仅只有少数头部属性。

header_compression.png

为了增加传输速率,http2.0 在传输的时候,会维护一张请求头部信息的哈希表,并同时存储在客户端和服务端,每次传输的时候,如果发现传输的头部信息在哈希表中已经存在,则只传哈希表的 index 值,不再传输具体的内容,这样一来,就极大减少了数据的传输。同时,如果有新的头部字段,这张哈希表也会动态的在客户端以及服务端增加新值,后续再有相同字段的时候,将不会再传输,只会传哈希表的 index 值。

事实上,上文所谓的那张哈希表细分下来是两张表:一张叫静态表,一张叫动态表。静态表是存放 HTTP 协议本身固定的一些常见值:

hashTable.png

动态表存放一些网站特定的属性字段,而且会随着请求中字段的变化而进行增加。

之后,请求头的内容变成了除少量 header 字段外大部分是哈希表 index 值的数据;但是这还没有结束,http2.0 还会将现有的数据内容进行 霍夫曼编码处理 ,再一次进行压缩。

以上便是 http2.0 头部压缩的算法,叫做 HACK 算法。

# 数据推送

不同于 http1.1 的请求 - 响应模式,http2.0 可以由服务端向客户端推送消息,但这里的推送方式又有别于 tcp、或者 websocket 的双向通信,有一定的局限性。

在常见的 http1.1 协议下,client 端和服务端严格按照 “请求 - 响应” 的方式进行通信。这样会出现一种情况:某些请求显得很多余。例如,请求一个网站页面,在返回主要的 html 文件后,html 文件中内联的 css 、js 等文件内容必须通过额外的客户端请求,才能从服务端拿到数据;而这些内联的数据文件,是一定且必须拿到的,这样看来,http1.1 场景下,这些内联的数据文件必须由客户端再次发起请求,才能得到服务端的响应数据;而在 http2.0 的场景下,服务端会根据文件中关联的其他文件,预判并主动推送下次请求中必须的数据。http2.0 的数据推送仅限于此了,不同于 tcp、websocket 的双向通信,要特别注意。

push_imag.png

如果服务端推送数据过来,客户端可以针对推送的数据自行选择放弃或者保存,但是如果客户端将推送来的数据主动放弃,这样其实就白白浪费了一次 http 响应传输;http2.0 还有更好的方式是将客户端已有的缓存信息标识告诉服务端,服务端通过判断之后,只推送不存在的数据信息即可。

搞懂 HTTP 重定向 - 如何优雅地使用 301 - 腾讯云开发者社区 - 腾讯云 (tencent.com)

从 304 浅谈 http 缓存 - 掘金 (juejin.cn)

HTTP 长连接实现原理 - 掘金 (juejin.cn)

你了解 HTTP 长连接吗? - 掘金 (juejin.cn)

http 2.0 一篇就够了 - 掘金 (juejin.cn)

HTTP2 协议解析 - 简书 (jianshu.com)

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

John Doe WeChat Pay

WeChat Pay

John Doe Alipay

Alipay

John Doe PayPal

PayPal