走进 HTTP 协议| 青训营笔记

67 阅读10分钟

介绍

HTTP(超文本传输协议,英语:HyperText Transfer Protocol)是一个用于传输超媒体文档(例如 HTML)的应用层协议,是万维网的数据通信的基础。

版本

  1. 1999年6月公布的 RFC 2616,定义了 HTTP 协议中现今广泛使用的一个版本 HTTP 1.1。
  2. 2015年5月以 RFC 7540 正式发布 HTTP/2 标准,取代 HTTP 1.1 成为 HTTP 的实现标准。
  3. 2022年6月6日标准化为 RFC9114 的最新版本 HTTP/3,抛弃使用 TCP,通过 UDP 上使用 QUIC 来承载应用层数据。

通信过程

  1. 使用 TCP 协议,通过网页浏览器、网络爬虫或者其它的工具,客户端(user agent,用户代理程序)发起一个 HTTP 请求到服务器上指定端口(默认端口为80)。
  2. 应答的服务器(origin server)上存储着一些资源,比如 HTML 文件和图像,服务器在那个端口监听客户端的请求。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。
  3. 一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。
  4. 每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。HTTP/2 中的连接具有复用性,即每个目标地址建立连接后,可以永久被利用,所以每个来源仅需要一个连接。

请求方法

它们都实现了不同的语义,但根据共同的特征由可以分类为:safe(安全), idempotent(幂等), 或 cacheable(可缓存)。

  • GET 的请求应该只被用于获取数据。
  • HEAD 方法请求一个与 GET 请求的响应相同的响应,但没有响应体。
  • POST 方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用。
  • PUT 方法用请求有效载荷替换目标资源的所有当前表示。
  • DELETE 方法删除指定的资源。
  • CONNECT 方法建立一个到由目标资源标识的服务器的隧道。
  • OPTIONS 方法用于描述目标资源的通信选项。
  • TRACE 方法沿着到目标资源的路径执行一个消息环回测试。
  • PATCH 方法用于对资源应用部分修改。

Safe(安全)

  1. 指这是个不会修改服务器的数据的方法,也就是说,这是一个对服务器只读操作的方法。
  2. 浏览器调用安全的方法不用考虑会给服务端造成什么危害,这样,服务端就能允许客户端预加载资源。
  3. 这些方法是安全的:GET,HEAD 和 OPTIONS。所有安全的方法都是幂等的,但并非所有幂等方法都是安全的,例如,PUT 和 DELETE 都是幂等的,但不是安全的。

Idempotent(幂等)

  1. 指的是同样的请求被执行一次与连续执行多次,客户端接收到的结果都是一样的,服务器的状态也是一样的,也就是说,幂等方法不应该具有副作用(统计用途除外)。
  2. 在正确实现的条件下, GET、HEAD、OPTIONS、PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的 safe 方法也都是幂等的。例如下面调用多次 POST 方法,就会增加多行记录:
bash
复制代码
POST /add_row HTTP/1.1
POST /add_row HTTP/1.1   -> Adds a 2nd row
POST /add_row HTTP/1.1   -> Adds a 3rd row

下面即使请求多次 DELETE 方法接收到的状态码不一样,但也是幂等的:

sql
复制代码
DELETE /idX/delete HTTP/1.1   -> Returns 200 if idX exists
DELETE /idX/delete HTTP/1.1   -> Returns 404 as it just got deleted
DELETE /idX/delete HTTP/1.1   -> Returns 404

Cacheable(可缓存)

  1. 可以被缓存的 HTTP 响应,将被存储以供以后检索和使用。
  2. 请求中使用的方法本身是可缓存的,即一个 GET 或一个 HEAD 方法。如果指示新鲜度并设置了 Content-Location 标头,也可以缓存对 POST 或 PATCH 请求的响应,但这很少实现。其他方法如 PUT 或 DELETE 不可缓存,它们响应的结果也无法缓存。
  3. 应用程序缓存可以根据响应的状态代码,认为它是可缓存的。以下状态代码是可缓存的:200、203、204、206、300、301、404、405、410、414 和 501。
  4. 如果响应中有特定的标头,如 Cache-Control,可防止缓存。

HTTP 标头(header)

  1. HTTP 标头是用于 HTTP 请求或响应的字段,它传递关于请求或者响应的额外上下文和元数据。
  2. 例如,请求消息可以使用标头表明它首选的媒体格式,而响应可以使用标头表明返回主体的媒体格式。
  3. 标头是不区分大小写,开始于行首,后面紧跟着一个 ':' 和与之相关的值。字段值在一个换行符(CRLF)前或者整个消息的末尾结束。
  4. 根据不同的消息上下文,标头可以分为:
  • 请求标头:包含要获取的资源或者客户端自身的更多信息。例如,Accept-* 标头指示响应的允许格式和首选格式。其他标头可用于提供身份验证凭据(Authorization、Token 授权等)、控制缓存或获取有关用户代理(user agent)或引荐来源网址(referrer)等的信息。
  • 响应标头:包含有关响应的额外信息,例如响应的位置(Location)、响应时间(Date)、最后更新时间(Last-Modified)或者关于服务器自身的信息(Server,包括名字、版本等)。
  • 表示标头:包含消息主体中资源的元数据(例如,编码、MIME 媒体类型、压缩方案等)。包括 Content-Type、Content-Encoding、Content-Language 和 Content-Location。
  • 有效负荷标头:包含有关有效载荷数据表示的单独信息,包括内容长度和用于传输的编码。包括 Content-Length、Content-Range、Trailer 和 Transfer-Encoding。
  1. 并非所有可以出现在请求中的标头都被规范称为请求标头,并非所有出现在响应中的标头都根据规范将其归类为响应标头。例如,Content-Type 就是一个表示标头,在请求中 (如 POST 或 PUT),客户端告诉服务器实际发送的数据类型;在响应中,Content-Type 标头告诉客户端实际返回的内容的内容类型。

响应状态码

用来表明特定 HTTP 请求是否成功完成,归为以下五大类:

  1. 信息响应 (100–199)
  2. 成功响应 (200–299)
  3. 重定向消息 (300–399)
  4. 客户端错误响应 (400–499)
  5. 服务端错误响应 (500–599)

Cookie

Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据。HTTP 是无状态协议,这意味着服务器不会在两个请求之间保留任何数据(状态),而 Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能。浏览器会存储 Cookie 并在下次向同一服务器再发起请求时携带并发送到服务器上。

工作机制如下:

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

xml
复制代码
Set-Cookie: <cookie-name>=<cookie-value>

这指示服务器发送标头告知客户端存储一对 Cookie:

ini
复制代码
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[页面内容]

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

ini
复制代码
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

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

  1. 标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。它永远不会使用不安全的 HTTP 发送(本地主机除外),这意味着中间人攻击者无法轻松访问它。不安全的站点(http)无法使用 Secure 属性设置 Cookie。但是,Secure 不会阻止对 Cookie 中敏感信息的访问。例如,有权访问客户端硬盘(如果未设置 HttpOnly 属性,则有权访问 JavaScript)的人可以读取和修改它。
  2. JavaScript Document.cookie 无法访问带有 HttpOnly 属性的 Cookie,此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS)攻击。

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

  • 会话状态管理 - 如用户登录状态、购物车、游戏分数或其它需要记录的信息
  • 个性化设置 - 如用户自定义设置、主题和其他设置
  • 浏览器行为跟踪 - 如跟踪分析用户行为等

Cookie 曾一度用于客户端数据的存储,因当时并没有其它合适的存储办法而作为唯一的存储手段,但现在推荐使用现代存储 API。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(localStorage 和 sessionStorage)或 IndexedDB。

Cookie vs Session vs Token

由于服务器指定 Cookie 后,浏览器的每次请求都会携带 Cookie 数据,会带来额外的性能开销(尤其是在移动环境下)。

以购物车为例,需要有一个机制记录每个连接的关系,这样我们就知道加入购物车的商品到底属于谁了,每次浏览器请求后 server 都会将本次商品 id 存储在 Cookie 中返回给客户端,客户端会将 Cookie 保存在本地,下一次再将上次保存在本地的 Cookie 传给 server,这样每个 Cookie 都保存着用户信息和商品 id。

Session

由于用户的购物车信息都会保存在 Server 中,所以在 Cookie 里只要保存能识别用户身份的信息,知道是谁发起了加入购物车操作即可,这样每次请求后只要在 Cookie 里带上用户的身份信息,请求体里也只要带上本次加入购物车的商品 id,大大减少了 cookie 的体积大小,我们把这种能识别哪个请求由哪个用户发起的机制称为 Session(会话机制),生成的能识别用户身份信息的字符串称为 sessionId。

Tokentoken(JSON Web Token,JWT)主要由三部分组成:

  • header:指定了签名算法。
  • payload:可以指定用户 id,过期时间等非敏感数据。
  • Signature: 签名,server 根据 header 知道它该用哪种签名算法,再用密钥根据此签名算法对 head + payload 生成签名,这样一个 token 就生成了。

其中,header, payload 是以 base64 的形式存在的。

  1. 首先请求方输入自己的用户名,密码,然后 server 据此生成 token,客户端拿到 token 后会保存到本地(服务端没有存储),之后向 server 请求时在请求头带上此 token 即可。
  2. 当 server 收到浏览器传过来的 token 时,它会首先取出 token 中的 header + payload,根据密钥生成签名,然后再与 token 中的签名比对,如果成功则说明签名是合法的,即 token 是合法的。
  3. 只要 server 保证密钥不泄露,那么生成的 token 就是安全的,因为如果伪造 token 的话在签名验证环节是无法通过的。
  4. 鉴权 - session 会根据 sessionId 找到 userid 呢,token 如何知道是哪个用户?
  • token 中的 payload 中存有我们的 userId,所以拿到 token 后直接在 payload 中就可获取 userid,避免了像 session 那样要从 redis 去取的开销。

可以看到通过这种方式有效地避免了 token 必须保存在 server 的弊端,实现了分布式存储。