MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

网络编程-HTTP协议详解及版本演进

2023-02-075.8k 阅读

HTTP 协议基础概念

HTTP(Hyper - Text Transfer Protocol),即超文本传输协议,是用于在万维网(WWW)上进行数据传输的应用层协议。它定义了客户端(如浏览器)如何向服务器请求资源,以及服务器如何响应这些请求。

HTTP 的工作模式

HTTP 采用客户端 - 服务器(C/S)模式。客户端发起请求,服务器接收请求并返回响应。例如,当你在浏览器中输入一个网址并回车时,浏览器作为客户端会向对应的服务器发送请求,服务器处理请求后将网页内容(如 HTML、CSS、JavaScript 文件等)作为响应返回给浏览器。

HTTP 消息结构

HTTP 消息分为请求消息和响应消息,它们都由起始行、头部字段和消息体(可选)组成。

请求消息

请求方法 请求 URL HTTP 版本
头部字段 1: 值 1
头部字段 2: 值 2
...

(空行)
消息体(可选)

例如,一个简单的 GET 请求获取网页:

GET /index.html HTTP/1.1
Host: example.com
User - Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

这里 GET 是请求方法,/index.html 是请求的 URL,HTTP/1.1 是 HTTP 版本,HostUser - Agent 是头部字段。

响应消息

HTTP 版本 状态码 状态描述
头部字段 1: 值 1
头部字段 2: 值 2
...

(空行)
消息体(可选)

比如,服务器对上述请求的响应可能是:

HTTP/1.1 200 OK
Content - Type: text/html
Content - Length: 1234

<!DOCTYPE html>
<html>
<head>
    <title>Example Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

其中 HTTP/1.1 是版本,200 是状态码,OK 是状态描述,Content - TypeContent - Length 是头部字段,后面的 HTML 内容是消息体。

HTTP 请求方法

HTTP 定义了多种请求方法,每种方法用于特定类型的操作。

GET 方法

GET 方法用于从服务器获取资源。请求的参数附加在 URL 后面,以 ? 分隔,多个参数之间用 & 连接。例如:http://example.com/api?param1=value1&param2=value2

以下是一个使用 Python 的 requests 库发送 GET 请求的示例:

import requests

response = requests.get('http://httpbin.org/get?param1=value1&param2=value2')
print(response.json())

在这个示例中,httpbin.org 是一个用于测试 HTTP 请求的网站,requests.get 发送 GET 请求并返回响应,response.json() 将响应内容解析为 JSON 格式(如果响应内容是 JSON 类型)。

POST 方法

POST 方法用于向服务器提交数据,通常用于创建新资源。数据包含在请求的消息体中,而不是 URL 中。这使得它更适合传输敏感或大量的数据。

以下是使用 requests 库发送 POST 请求的示例:

import requests

data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post('http://httpbin.org/post', data=data)
print(response.json())

这里通过 requests.post 发送 POST 请求,data 参数指定了要发送的数据。

PUT 方法

PUT 方法用于更新服务器上的资源。如果资源不存在,它可能会创建一个新资源。例如,在 RESTful API 中,可以使用 PUT 方法更新用户信息:

import requests

data = {'name': 'New Name', 'email': 'new@example.com'}
response = requests.put('http://example.com/api/users/1', data=data)
print(response.status_code)

在这个示例中,假设 http://example.com/api/users/1 是特定用户的资源 URL,PUT 请求更新该用户的信息。

DELETE 方法

DELETE 方法用于删除服务器上的资源。例如,删除一个文件或数据库中的一条记录:

import requests

response = requests.delete('http://example.com/api/files/123')
print(response.status_code)

这里发送 DELETE 请求到指定的资源 URL 来删除资源。

HTTP 状态码

HTTP 状态码是服务器响应中的三位数字代码,用于表示请求的处理结果。

1xx(信息性状态码)

这类状态码表示服务器已经接收了请求,正在继续处理。例如 100 Continue,当客户端发送了一个带 Expect: 100 - continue 头部的请求时,如果服务器愿意继续处理,会返回 100 Continue,客户端接收到这个状态码后再发送请求的其余部分。

2xx(成功状态码)

200 OK:表示请求成功,服务器已成功处理请求并返回了请求的资源。这是最常见的成功状态码。

201 Created:用于表示请求已成功,并且服务器已经创建了新的资源。通常在使用 POSTPUT 方法创建资源时返回这个状态码,同时响应的 Location 头部会包含新创建资源的 URL。

3xx(重定向状态码)

301 Moved Permanently:表示请求的资源已被永久移动到新的 URL。客户端应该使用新的 URL 进行后续请求。例如,一个网站的域名发生了变更,服务器可能会返回 301 状态码,同时在 Location 头部给出新的域名。

302 Found:与 301 类似,但表示资源只是临时移动。客户端在后续请求中仍应使用原来的 URL。

304 Not Modified:当客户端发送了带 If - Modified - SinceIf - None - Match 等条件的请求时,如果服务器判断资源没有被修改,会返回 304 状态码,告诉客户端可以使用本地缓存的资源,从而节省数据传输。

4xx(客户端错误状态码)

400 Bad Request:表示客户端发送的请求存在语法错误,服务器无法理解。例如,请求参数格式不正确。

401 Unauthorized:表示客户端需要进行身份验证,但没有提供有效的认证信息。常用于需要登录的场景。

403 Forbidden:表示服务器理解请求,但拒绝执行。通常是因为客户端没有足够的权限访问资源,即使已经通过了身份验证。

404 Not Found:这是大家非常熟悉的状态码,表示服务器找不到请求的资源。可能是 URL 输入错误或资源已被删除。

5xx(服务器错误状态码)

500 Internal Server Error:表示服务器在处理请求时发生了内部错误。这通常是服务器端代码的问题,如代码中的未处理异常等。

502 Bad Gateway:当服务器作为网关或代理,从上游服务器接收到无效的响应时返回此状态码。例如,代理服务器向另一个服务器请求资源,但收到了无法理解的响应。

HTTP 头部字段

HTTP 头部字段用于在客户端和服务器之间传递额外的信息。

通用头部字段

Cache - Control:用于指定缓存策略。例如,Cache - Control: no - cache 表示不使用缓存,每次都从服务器获取最新资源;Cache - Control: max - age = 3600 表示缓存有效期为 3600 秒(1 小时)。

Connection:用于控制客户端和服务器之间的连接。常见的值有 keep - aliveclosekeep - alive 表示保持连接,以便在同一个 TCP 连接上可以发送多个请求和响应,提高效率;close 表示请求完成后关闭连接。

请求头部字段

Host:指定请求的目标服务器的主机名和端口号。在一个服务器可能托管多个网站的情况下,Host 头部字段告诉服务器客户端请求的是哪个网站。例如,Host: example.com:80

User - Agent:包含客户端的信息,如浏览器类型、版本、操作系统等。服务器可以根据 User - Agent 来提供不同版本的内容,以适配不同的客户端。例如,User - Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

响应头部字段

Content - Type:指定响应内容的类型,如 text/html 表示 HTML 文档,application/json 表示 JSON 数据,image/jpeg 表示 JPEG 图像等。这有助于客户端正确解析响应内容。

Content - Length:表示响应消息体的长度(字节数)。客户端可以根据这个字段来确定何时接收完所有数据。

HTTP/1.0 与 HTTP/1.1

HTTP/1.0

HTTP/1.0 是 HTTP 协议的早期版本。它的主要特点如下:

  1. 短连接:每个请求 - 响应完成后,连接就会关闭。如果客户端需要再次请求资源,就需要重新建立 TCP 连接。这增加了建立连接的开销,特别是对于需要多次请求资源的网页(如包含多个图片、脚本的网页)。
  2. 缓存机制简单:只支持简单的 Expires 头部来设置缓存过期时间。这种机制不够灵活,因为它依赖于服务器和客户端的时钟同步。

以下是一个简单的使用 Python 的 http.client 模块模拟 HTTP/1.0 请求的示例:

import http.client

conn = http.client.HTTPConnection('example.com')
conn.request('GET', '/index.html', headers={'Connection': 'close'})
response = conn.getresponse()
print(response.read())
conn.close()

在这个示例中,手动设置 Connection: close 来模拟 HTTP/1.0 的短连接行为。

HTTP/1.1

HTTP/1.1 对 HTTP/1.0 进行了许多重要的改进:

  1. 持久连接(长连接):默认情况下,HTTP/1.1 使用持久连接,通过 Connection: keep - alive 头部实现。这意味着在一个 TCP 连接上可以发送多个请求 - 响应,减少了连接建立的开销,提高了性能,特别是对于包含多个资源的网页。
  2. 改进的缓存机制:引入了 Cache - Control 头部,提供了更灵活的缓存控制策略,如 no - cacheno - storemax - age 等。同时,ETagIf - None - Match 等机制用于更精确的缓存验证。
  3. 支持分块传输编码:当服务器不知道响应内容的长度时,可以使用分块传输编码。响应被分成多个块进行传输,每个块都有自己的长度标识,最后以一个长度为 0 的块结束。这使得服务器可以在生成响应数据的同时开始传输,而不需要等待所有数据都准备好。

以下是使用 http.client 模块模拟 HTTP/1.1 持久连接请求的示例:

import http.client

conn = http.client.HTTPConnection('example.com')
conn.request('GET', '/index.html')
response = conn.getresponse()
print(response.read())

# 可以在同一个连接上继续发送其他请求
conn.request('GET', '/other.html')
response = conn.getresponse()
print(response.read())

conn.close()

在这个示例中,没有显式设置 Connection: close,默认使用 HTTP/1.1 的持久连接,可以在同一个连接上发送多个请求。

HTTP/2

HTTP/2 的主要特性

  1. 二进制分帧层:HTTP/2 不再像 HTTP/1.x 那样以文本形式传输数据,而是采用二进制格式。它将消息分解为更小的帧,并对这些帧进行二进制编码。这样做的好处是解析速度更快,并且可以更有效地控制和优化数据传输。例如,HTTP/2 可以在一个 TCP 连接上同时发送多个请求和响应的帧,而不会像 HTTP/1.x 那样存在线头阻塞(Head - of - line blocking)问题。
  2. 多路复用:通过二进制分帧层,HTTP/2 实现了多路复用。在一个 TCP 连接上可以同时进行多个请求和响应,每个请求和响应都可以通过帧中的流标识符进行区分。这意味着客户端可以并行发送多个请求,服务器也可以并行发送多个响应,大大提高了传输效率。例如,一个网页包含多个图片、脚本和样式文件,在 HTTP/2 下可以同时请求这些资源,而不需要像 HTTP/1.x 那样按顺序逐个请求。
  3. 头部压缩:HTTP/1.x 的头部字段往往比较冗长,每次请求都需要重复发送一些相同的头部信息,这增加了传输开销。HTTP/2 使用 HPACK 算法对头部进行压缩。它通过建立一个静态和动态的字典,对重复的头部字段进行索引,从而减少头部的大小。例如,常见的 User - Agent 头部字段,在多次请求中如果内容相同,通过 HPACK 压缩后只需要传输一个索引值,而不是整个头部内容。
  4. 服务器推送:服务器可以主动向客户端推送资源,而不需要客户端先请求。例如,当客户端请求一个 HTML 文件时,服务器知道这个 HTML 文件会引用一些 CSS 和 JavaScript 文件,服务器可以直接将这些文件推送给客户端,减少客户端再次请求这些资源的延迟。

Python 中使用 HTTP/2 的示例

在 Python 中,可以使用 aiohttp 库来进行 HTTP/2 相关的操作。首先需要安装 aiohttp

pip install aiohttp

以下是一个简单的使用 aiohttp 发送 HTTP/2 请求的示例:

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=True)) as session:
        html = await fetch(session, 'https://example.com')
        print(html)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

在这个示例中,aiohttp 默认支持 HTTP/2(在支持的服务器上),asyncio 用于异步操作,提高效率。

HTTP/3

HTTP/3 的背景与改进

HTTP/3 是基于 UDP 协议的 HTTP 版本,它的出现主要是为了解决 TCP 协议在某些场景下的性能问题,特别是在高延迟、高丢包的网络环境中。

  1. 基于 UDP:HTTP/3 使用 UDP 作为传输层协议,并在其上构建了 QUIC(Quick UDP Internet Connections)协议。UDP 相比 TCP 更加轻量级,没有 TCP 连接建立的三次握手过程,减少了延迟。同时,QUIC 协议在 UDP 的基础上实现了可靠传输、流量控制和拥塞控制等功能,弥补了 UDP 不可靠的缺点。
  2. 减少连接建立延迟:由于没有 TCP 的三次握手,HTTP/3 可以更快地建立连接。在移动网络等网络环境变化频繁的场景下,重新建立连接的速度更快,提高了用户体验。
  3. 前向纠错(FEC):HTTP/3 引入了前向纠错机制。当数据包丢失时,接收方可以通过冗余信息恢复丢失的数据,而不需要像 TCP 那样等待重传。这在高丢包率的网络中可以显著提高传输效率。

与 HTTP/2 的比较

与 HTTP/2 相比,HTTP/3 在连接建立延迟和应对网络拥塞方面有明显优势。HTTP/2 虽然通过多路复用等技术提高了性能,但仍然受限于 TCP 协议的特性。例如,在网络拥塞时,TCP 的拥塞控制机制可能会导致所有连接的传输速度下降,而 HTTP/3 的 QUIC 协议可以针对不同的流进行更细粒度的拥塞控制。

目前,虽然 HTTP/3 还没有像 HTTP/1.1 和 HTTP/2 那样广泛部署,但随着网络技术的发展和对性能要求的提高,HTTP/3 有望在未来得到更广泛的应用。

总结 HTTP 协议版本演进

从 HTTP/1.0 到 HTTP/1.1,主要解决了连接效率和缓存机制的问题,通过持久连接和更灵活的缓存控制提高了性能。HTTP/2 则在二进制分帧、多路复用、头部压缩和服务器推送等方面进行了重大改进,进一步提升了传输效率。而 HTTP/3 基于 UDP 和 QUIC 协议,针对高延迟、高丢包网络场景进行了优化,减少连接建立延迟并提高了应对网络拥塞的能力。每个版本的演进都是为了更好地满足不断增长的网络应用需求,提供更高效、更快速的网络数据传输服务。在实际的后端开发中,了解这些版本的特性和差异,有助于我们选择合适的技术方案,优化应用的性能和用户体验。同时,随着网络技术的不断发展,我们也期待 HTTP 协议未来能有更多的创新和改进。