超文本传输协议 (HTTP)是一个用于传输超媒体文档(例如 HTML)的应用层协议。它是为 Web 浏览器与 Web 服务器之间的通信而设计的,但也可以用于其他目的。HTTP 遵循经典的客户端---服务端模型,客户端打开一个连接以发出请求,然后等待直到收到服务器端响应。HTTP 是无状态协议,这意味着服务器不会在两个请求之间保留任何数据(状态)。
版本
1. http/0.9---单行协议
最初版本的 HTTP 协议并没有版本号,后来它的版本号被定位在 0.9 以区分后来的版本。HTTP/0.9 极其简单:请求由单行指令构成,以唯一可用方法 GET 开头,其后跟目标资源的路径(一旦连接到服务器,协议、服务器、端口号这些都不是必须的)。
2. http/1.0---构建可拓展性
由于 HTTP/0.9 协议的应用十分有限,浏览器和服务器迅速扩展内容使其用途更广:
- 协议版本信息现在会随着每个请求发送(HTTP/1.0 被追加到了 GET 行)。
- 状态码会在响应开始时发送,使浏览器能了解请求执行成功或失败,并相应调整行为(如更新或使用本地缓存)。
- 引入了 HTTP 标头的概念,无论是对于请求还是响应,允许传输元数据,使协议变得非常灵活,更具扩展性。
- 在新 HTTP 标头的帮助下,具备了传输除纯文本 HTML 文件以外其他类型文档的能力(Content-Type)
3. http/1.1---标准化协议
HTTP/1.1 消除了大量歧义内容并引入了多项改进:
- 连接可以复用,节省了多次打开 TCP 连接加载网页文档资源的时间。
- 增加管线化技术,允许在第一个应答被完全发送之前就发送第二个请求,以降低通信延迟。
- 支持响应分块。
- 引入额外的缓存控制机制。
- 引入内容协商机制,包括语言、编码、类型等。并允许客户端和服务器之间约定以最合适的内容进行交换。
- 凭借 Host 标头,能够使不同域名配置在同一个 IP 地址的服务器上。
4. http/2
HTTP/2 在 HTTP/1.1 有几处基本的不同:
- HTTP/2 是二进制协议而不是文本协议。不再可读,也不可无障碍的手动创建,改善的优化技术现在可被实施。
- 这是一个多路复用协议。并行的请求能在同一个链接中处理,移除了 HTTP/1.x 中顺序和阻塞的约束。
- 压缩了标头。因为标头在一系列请求中常常是相似的,其移除了重复和传输重复数据的成本。
- 其允许服务器在客户端缓存中填充数据,通过一个叫服务器推送的机制来提前请求。
- 对 Alt-Svc 的支持允许了给定资源的位置和资源鉴定,允许了更智能的 CDN 缓冲机制。
- 客户端提示(client hint)的引入允许浏览器或者客户端来主动交流它的需求,或者是硬件约束的信息给服务端。
- 在 Cookie 标头中引入安全相关的前缀,现在帮助保证一个安全的 Cookie 没被更改过。
5. http/3---基于QUIC的Http
HTTP 的下一个主要版本,HTTP/3 有着与 HTTP 早期版本的相同语义,但在传输层部分使用 QUIC(en-US) 而不是 TCP。到 2022 年 10 月,26% 的网站正在使用 HTTP/3。
QUIC 旨在为 HTTP 连接设计更低的延迟。类似于 HTTP/2,它是一个多路复用协议,但是 HTTP/2 通过单个 TCP 连接运行,所以在 TCP 层处理的数据包丢失检测和重传可以阻止所有流。QUIC 通过 UDP 运行多个流,并为
http/1.x 的连接管理
在 HTTP/1.x 里有多种模型:短连接 、长连接 和 HTTP 流水线。
连接模型,它会保持连接去完成多次连续的请求,减少了不断重新打开连接的时间。然后是 HTTP 流水线模型,它还要更先进一些,多个连续的请求甚至都不用等待立即返回就可以被发送,这样就减少了耗费在网络延迟上的时间(疑问:丢包的可能性是否变大)。
HTTP 的连接管理适用于两个连续节点之间的连接,它是逐跳(Hop-by-hop)标头,而不是端到端(End-to-end)标头。
1. 短连接
HTTP 最早期的模型和 HTTP/1.0 的默认模型,是短连接。每一个 HTTP 请求都由它自己独立的连接完成;这意味着发起每一个 HTTP 请求之前都会有一次 TCP 握手,而且是连续不断的。
这是 HTTP/1.0 的默认模型(如果没有指定 Connection 协议头,或者是值被设置为 close)。而在 HTTP/1.1 中,只有当 Connection 被设置为 close 时才会用到这个模型。
2. 长连接
一个长连接会保持一段时间,重复用于发送一系列请求,节省了新建 TCP 连接握手的时间,还可以利用 TCP 的性能增强能力。当然这个连接也不会一直保留着:连接在空闲一段时间后会被关闭(服务器可以使用 Keep-Alive 协议头来指定一个最小的连接保持时间)。
长连接也还是有缺点的;就算是在空闲状态,它还是会消耗服务器资源,而且在重负载时,还有可能遭受 DoS 攻击。这种场景下,可以使用非长连接,即尽快关闭那些空闲的连接,也能对性能有所提升。
HTTP/1.0 里默认并不使用长连接。把 Connection 设置成 close 以外的其他参数都可以让其保持长连接,通常会设置为 retry-after。
在 HTTP/1.1 里,默认就是长连接的,不再需要标头(但我们还是会把它加上,万一某个时候因为某种原因要退回到 HTTP/1.0 呢)。
3. 流水线连接
流水线已被 HTTP/2 中更好的算法------多路复用(multiplexing)所取代。
默认情况下,HTTP 请求是按顺序发出的。下一个请求只有在当前请求收到响应过后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
流水线是在同一条长连接上发出连续的请求,而不用等待应答返回。这样可以避免连接延迟。理论上讲,性能还会因为两个 HTTP 请求有可能被打包到一个 TCP 消息包中而得到提升。就算 HTTP 请求不断的继续,尺寸会增加,但设置 TCP 的最大分段大小(MSS)选项,仍然足够包含一系列简单的请求。
并不是所有类型的 HTTP 请求都能用到流水线:只有幂等方式,比如 GET、HEAD、PUT 和 DELETE 能够被安全地重试。如果有故障发生时,流水线的内容要能被轻易的重试。
今天,所有遵循 HTTP/1.1 标准的代理和服务器都应该支持流水线,虽然实际情况中还是有很多限制:一个很重要的原因是,目前没有现代浏览器默认启用这个特性。
协议升级
http/2.0不允许使用该机制,该机制仅提供http/1.1使用
HTTP/1.1 协议提供了一种使用 Upgrade(en-US) 标头字段的特殊机制,这一机制允许将一个已建立的连接升级成新的、不相容的协议。
因为 Upgrade 是一个逐跳(Hop-by-hop)标头,它还需要在 Connection 标头字段中列出。这意味着包含 Upgrade 的典型请求类似于:
GET /index.html HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: example/1, foo/2
至今为止,最经常会需要升级一个 HTTP 连接的场合就是使用 WebSocket,它总是通过升级 HTTP 或 HTTPS 连接来实现,手动升级协议的请求类似于:
Connection: Upgrade
Upgrade: websocket
在WebStocket升级中,除了以上必须出现的标头外,还有一些可选标头,提供其他功能
|-------------------------|----------------------------------------------|
| 标头 | 含义 |
| Sec-WebSocket-Extension | 用于指定一个或多个请求服务器使用的协议级 WebSocket 扩展 |
| Sec-WebSocket_Key | 该标头向服务器提供确认客户端有权请求升级到 WebSocket 的所需信息 |
| Sec-WebSocket-Protocol | 按优先顺序指定你希望用的一个或者多个 WebSocket 协议 |
| Sec-WebSocket-Version | 指定客户端希望使用的 WebSocket 协议版本,以便服务器可以确认其是否支持该版本。 |
http身份验证
HTTP 提供一个用于权限控制和认证的通用框架。服务器可以用来质询(challenge)客户端的请求,客户端则可以提供身份验证凭据。
认证方式
http认证方式有多种,以下介绍我认为比较需要了解的三种,basic身份验证、session验证、token验证
basic身份验证
basic这种身份验证,就相当于你去一些地方,它只认你的身份证,你必须每次携带身份证,然后它在后台验证你是你之后,你才可以进去。
这种认证方法的优点是简单,容易理解。
缺点有:
- 不安全:认证身份信息用明文传送,因此需结合 https 使用。
- 效率低:服务端处理请求时,每次都需要验证身份信息,如用户名和密码。
大致的流程理解如下:
-
客户端向浏览器发起如下请求,其中protected_docs是一个受限制的资源
GET/ protected_docs HTTP/1.1
-
服务端发现该资源受限,给客户端返回401,要求验证其身份(对于代理来说,响应的状态码是407)
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm=protected_docs //用户
Proxy-Authenticate: Basic realm=protected_docs //代理
响应首部中,通过WWW-Authenticate或者Proxy-Authenticate告知客户端,认证的方案是Basic。同时以realm告知认证的范围。
- 客户端进行用户名以及密码的登录(证明用户代理身份的凭据)
Authorization请求首部中,包含了用户填写的用户名、密码。
GET /protected_docs HTTP/1.1
Authorization: *** Y2h5aW5ncDoxMjM0NTY=
Proxy-Authorization: *** Y2h5aW5ncDoxMjM0NTY=
- 服务端再次验证请求
服务端收到用户的认证请求后,对请求进行验证。验证包含如下步骤:
- 根据用户请求资源的地址,确定资源对应的realm。
- 解析 Authorization 请求首部,获得用户名、密码。
- 判断用户是否有访问该realm的权限。
- 验证用户名、密码是否匹配。
一旦上述验证通过,则返回请求资源。如果验证失败,则返回401要求重新认证,或者返回403(Forbidden)。
session验证
session验证这种方法,可以理解为你有身份证,但是不需要随时携带,到达某些地方只需要报身份证号,然后后台就可以验证
优点:
- 较安全:客户端每次请求时无需发送身份信息,只需发送 SessionID。
- 较高效:服务端无需每次处理请求时都要验证身份信息,只需通过 SessionID 查询 Session 对象。
缺点:
- 扩展性差,Session 对象保存在服务端,如果是保存在多个服务器上,有一致性问题,如果保存在单个服务器上,无法适应用户增长。
- 基于 Cookie 的 SessionID 不能跨域共享,同一用户的多个客户端(如浏览器客户端和 APP)不能共享 SessionId。
- 基于 Cookie 的 SessionID 易被截获生成 CSRF 攻击。
- 其交互过程如下:
- 客户端在登录页面输入身份信息,如用户名/密码。
- 服务端验证身份信息,通过后生成一个 Session 对象,保存到服务端,并将 SessionID 值以 Cookie 形式返回给客户端。
- 客户端将接收到的 SessionID 保存到 Cookie 中,并且之后每次请求都在请求头中携带 SessionID Cookie。
- 服务端从请求的 Cookie 中获取 SessionID,并查询其对应的 Session 对象,从而获得身份信息。
- 客户端退出本次会话后,客户端删除 SessionID 的 Cookie,服务端删除 Session 对象。
- 如果客户端之后要重新登录,需重新生成 Session 对象和 SessionID。
token验证
token认证就相当于你去公司上班,公司给你发了一个工牌,你进入公司只需要出示工牌,但是并不需要查你的信息。
是一种 SPA 应用和 APP 经常使用的认证方法。它是一种无状态的认证方法。
客户端首先将用户信息发送给服务端,服务端根据用户信息+私钥生成一个唯一的 Token 并返回给客户端。Token 只保存在客户端,之后客户端的每个请求头中都携带 Token,而服务端只通过运算(无需查询)来验证用户。
大致的流程理解如下:
-
客户端向浏览器发出发起请求,请求一个受限制的资源
GET/ protected_docs HTTP/1.1
-
服务端发现该资源受限,给客户端返回401,要求验证其身份
HTTP/1.1 401 Unauthorized
WWW-Authenticate: token -
客户端发送token验证给服务端
GET /protected_docs HTTP/1.1
Authorization: token fe23hbihio2jnuoxcds -
服务端对token进行运算
cookie
HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据。浏览器会存储 cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器------如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。
但现在推荐使用现代存储 API
创建cookie
服务器收到 HTTP 请求后,服务器可以在响应标头里面添加一个或多个 Set-Cookie 选项。浏览器收到响应后通常会保存下 Cookie,并将其放在 HTTP Cookie 标头内,向同一服务器发出请求时一起发送。你可以指定一个过期日期或者时间段之后,不能发送 cookie
Set-Cookie: <cookie-name>=<cookie-value>
cookie生命周期
Cookie 的生命周期可以通过两种方式定义:
- 会话期 Cookie 会在当前的会话结束之后删除。浏览器定义了"当前会话"结束的时间,一些浏览器重启时会使用会话恢复。这可能导致会话 cookie 无限延长。
- 持久性 Cookie 在过期时间(Expires)指定的日期或有效期(Max-Age)指定的一段时间后被删除。
cookie安全
有两种方法可以确保 Cookie 被安全发送,并且不会被意外的参与者或脚本访问:Secure 属性和 HttpOnly 属性。
标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。它永远不会使用不安全的 HTTP 发送(本地主机除外),这意味着中间人攻击者无法轻松访问它。不安全的站点(在 URL 中带有 http:)无法使用 Secure 属性设置 cookie。但是,Secure 不会阻止对 cookie 中敏感信息的访问。例如,有权访问客户端硬盘(或,如果未设置 HttpOnly 属性,则为 JavaScript)的人可以读取和修改它。
JavaScript Document.cookie API 无法访问带有 HttpOnly 属性的 cookie;此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS)攻击。
cookie发送的位置
Domain 和 Path 标识定义了 Cookie 的作用域:即允许 Cookie 应该发送给哪些 URL。
Domain 指定了哪些主机可以接受 Cookie。如果不指定,该属性默认为同一 host 设置 cookie,不包含子域名。如果指定了 Domain,则一般包含子域名
Path 属性指定了一个 URL 路径,该 URL 路径必须存在于请求的 URL 中,以便发送 Cookie 标头。以字符 %x2F ("/") 作为路径分隔符,并且子路径也会被匹配。