简介

HTTP 协议构成了互联网的基础。它不仅负责传输网页、图片、视频等各种类型的资源,还为会话管理(Cookie)、认证(JWT)、缓存控制(Cache-Control)以及部分数据传输(Content-Range)提供支持。简单来说,HTTP 协议就像一个邮递系统:文件和代码作为信件,通过 HTTP 报文(信封)被封装,从服务器出发,经由多个代理和网络层层传递,最终到达用户设备,并在浏览器中呈现出来。对于后端开发者来说,理解 HTTP 协议不仅有助于编写 API,还能优化系统性能和安全性。

本文主要介绍 HTTP 协议的基础知识,包括协议的工作流程、报文格式、MIME 类型以及请求和响应的构成。至于下层网络栈和 HTTP 历史的更多细节,读者可以参考 Wikipedia 上关于 HTTP 和 TCP/IP 的条目。

HTTP 协议的工作流程

HTTP 协议在客户端-服务器模型中实现请求和响应功能,分为以下过程:

  1. 请求发起: 浏览器(客户端)向服务器发送 HTTP 请求。请求中包含请求行(方法、URL、协议版本)、请求头(例如 Host、User-Agent、Accept 等)以及可选的请求体(如在 POST 或 PUT 请求中)。

  2. 服务器处理: 服务器接收请求,解析请求报文,确定所需资源或操作。根据请求内容,服务器处理业务逻辑、检索数据或执行相应操作。

  3. 响应返回: 服务器生成 HTTP 响应,包括状态行(协议版本、状态码、原因短语)、响应头(例如 Content-Type、Content-Length、Cache-Control 等)和响应体(实际数据)。

  4. 客户端处理: 响应通过网络回传给客户端,浏览器对响应体进行解析和渲染,从而展现给用户。

此外,HTTP 协议作为应用层协议,在网络协议栈中处于顶层。下层协议(如 TCP/IP)负责数据传输的封装和路由,而 HTTP 协议只关注如何描述和传递资源及元数据。

为了更直观地理解,可以将整个过程比作寄送信件:

  • 正文:代表网页内容或数据。

  • 信封:即 HTTP 报文,其中包含地址(Host 字段)、发送方式(方法)以及其他辅助信息。

  • 邮局:比喻为网络协议栈(TCP/IP),负责将信件从寄件人传递到收件人。

HTTP 发展历程

  1. HTTP/0.9: 极其简化,仅支持GET方法,传输HTML文档,无头部和状态码,用于早期简单的Web信息获取。

  2. HTTP/1.0: 引入了头部和状态码,支持POST和HEAD方法,可传输更多内容类型,但默认短连接。

  3. HTTP/1.1: 确立持久连接为默认,加入分块传输、内容协商、缓存控制等关键特性,方法和状态码大幅扩展,成为现代Web的基础。

  4. HTTP/2: 以性能为核心,采用二进制分帧和多路复用,加入头部压缩和服务器推送,显著提升效率和降低延迟,但仍基于TCP。

  5. HTTP/3: 革新传输层,基于QUIC协议,彻底解决队头阻塞,实现0-RTT连接、连接迁移等特性,进一步优化移动网络环境下的性能,方法和核心语义与HTTP/2基本一致。

HTTP协议报文

HTTP 报文是 HTTP 协议传输信息的基本单位,分为请求报文和响应报文,两者都由 起始行头部(Headers) 以及 消息体(Body) 构成。

MIME 类型

当浏览器或其他客户端收到来自服务器的响应时,它们需要知道如何处理响应体中的数据。MIME 类型提供了这种信息。例如,如果 Content-Type 是 text/html,浏览器就知道这是一个 HTML 文档,需要将其渲染成网页。如果 Content-Type 是 image/jpeg,浏览器就知道这是一个 JPEG 图像,需要将其显示为图片。

在 HTTP 中,MIME 类型主要用于两个地方:

  • Content-Type 响应头: 服务器在响应中通过 Content-Type 头告诉客户端响应体中数据的 MIME 类型。
  • Accept 请求头: 客户端在请求中通过 Accept 头告诉服务器自己可以接受的 MIME 类型。

MIME 类型通常采用 type/subtype 的形式,其中:

  • type:表示数据类型的大类,例如 text(文本)、image(图像)、audio(音频)、video(视频)、application(应用程序)、multipart(多个部分)。
  • subtype:表示更具体的数据格式,例如 text/htmlimage/jpegapplication/json

MIME 类型还可以包含可选的参数,用于提供关于数据的更多信息,例如字符编码。参数以分号 ( ;) 分隔,格式为 parameter=value。例如:text/html; charset=utf-8 表示这是一个 HTML 文档,使用 UTF-8 字符编码。

常见的MIME:

MIME 文件类型
text/html HTML 文档
image/jpeg JPEG 图像
audio/wav WAV 音频
application/json JSON 数据
application/xml XML 数据
multipart/form-data 用于表单提交,可以包含文件
multipart/byteranges 用于HTTP 范围请求

HTTP请求

HTTP 请求由以下几个部分组成:

  1. 请求行 (Request Line): 1. 方法 (Method): 指示要执行的操作。常见的 HTTP 方法包括:

    • GET: 获取资源。
    • POST: 提交数据(例如,表单提交)。
    • PUT: 更新资源(完整替换)。
    • PATCH: 更新资源(部分修改)。
    • DELETE: 删除资源。
    • HEAD: 获取资源的元信息(类似于 GET,但不返回响应体)。
    • OPTIONS: 获取服务器支持的请求方法。
    • TRACE: 回显服务器收到的请求,主要用于测试或诊断。
    • CONNECT: 建立一个到由请求目标标识的服务器的隧道。 2. URL (Uniform Resource Locator): 要请求的资源的地址。例如:https://www.example.com/page1.html 3. HTTP 版本: 通常是 HTTP/1.1,也可能是 HTTP/2HTTP/3
  2. 请求头 (Request Headers): - 包含关于请求的附加信息,如客户端类型、接受的内容类型、语言偏好等。 - 常见的请求头:

    • Host: 指定服务器的域名和端口号。
    • User-Agent: 标识客户端类型(浏览器、操作系统等)。
    • Accept: 告知服务器客户端可以接受的内容类型。
    • Accept-Language: 告知服务器客户端偏好的语言。
    • Cookie: 包含之前服务器设置的 Cookie。
    • Content-Type: 当请求体存在时,指定请求体的类型(如 application/json)。
    • Content-Length: 请求体的长度(字节)。
    • Authorization: 携带认证凭据
    • If-Modified-SinceIf-None-Match: 用于缓存验证
  3. 请求体 (Request Body): - 可选,包含要发送给服务器的数据。 - 通常在 POSTPUT 请求中使用。 - 可以是各种格式,如 JSON、XML、表单数据等。

HTTP 请求方法的特性

安全方法(Safe Methods)

如果一个请求方法不会对服务器产生预期的副作用,则称其为安全方法。常见的安全方法包括 GET、HEAD、OPTIONS 和 TRACE。

虽然安全方法理论上只用于读取数据,但它们仍可能在后台产生诸如写入日志或计费等副作用。

与安全方法相反,POST、PUT、DELETE、CONNECT 和 PATCH 方法通常会修改服务器状态或产生其他效果,因此不被认为是安全的。

幂等性 (Idempotent Methods)

如果多次对同一资源发送相同请求,服务器的状态变化与只发送一次请求时相同,则称该方法为幂等方法。

GET、HEAD、OPTIONS、TRACE 本身就是安全的,因此天然是幂等的;PUT 和 DELETE 方法也被定义为幂等方法,因为重复相同的 PUT 或 DELETE 操作不会进一步改变服务器状态。

与此相对,POST、CONNECT 和 PATCH 方法通常不具备幂等性,重复提交可能会导致多次状态改变或产生重复操作(例如重复发送邮件)

缓存性 (Cacheable Methods)

如果一个请求方法的响应可以被缓存以供将来复用,则称其为可缓存方法。

GET、HEAD 以及部分情况下的 POST 是可缓存的;而 PUT、DELETE、CONNECT、OPTIONS、TRACE 和 PATCH 方法通常不缓存响应。

HTTP 响应

3. HTTP 响应 (Response)

服务器收到请求后,会返回一个 HTTP 响应,它由以下部分组成:

  • 状态行 (Status Line):

    • HTTP 版本: 与请求中的版本相同。
    • 状态码 (Status Code): 一个三位数的数字,表示请求的结果。常见的状态码:
      • 200 OK: 请求成功。
      • 301 Moved Permanently: 永久重定向。
      • 302 Found: 临时重定向。
      • 304 Not Modified: 资源未修改(用于缓存)。
      • 400 Bad Request: 客户端请求错误。
      • 401 Unauthorized: 需要身份验证。
      • 403 Forbidden: 服务器拒绝访问。
      • 404 Not Found: 资源未找到。
      • 500 Internal Server Error: 服务器内部错误。
      • 503 Service Unavailable: 服务器暂时不可用。
    • 原因短语 (Reason Phrase): 对状态码的简短描述(例如,OKNot Found)。
  • 响应头 (Response Headers):

    • 包含关于响应的附加信息,如服务器类型、内容类型、长度、缓存控制等。
    • 常见的响应头:
      • Content-Type: 指定响应体的类型(如 text/htmlapplication/json)。
      • Content-Length: 响应体的长度(字节)。
      • Server: 服务器软件的名称和版本。
      • Date: 响应生成的时间。
      • Set-Cookie: 设置 Cookie。
      • Location: 用于重定向(3xx 状态码)。
      • Cache-Control, Expires, ETag, Last-Modified: 缓存控制相关的头部
  • 响应体 (Response Body):

    • 包含服务器返回的实际数据。
    • 可以是 HTML、JSON、XML、图片、视频等。

HTTP 报文示例

以浏览器访问本站为例

HTTP Request

当进入页面时,浏览器先和blog.huangyj.me的服务器建立TCP连接,进行TLS握手,最后向https://blog.huangyj.me发起GET请求:

GET / HTTP/3
Host: blog.huangyj.me
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.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, zstd
DNT: 1
Sec-GPC: 1
Alt-Used: blog.huangyj.me
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Priority: u=0, i
TE: trailers

关键字段:

  1. GET / HTTP/3:GET方法,请求根目录,使用HTTP3协议
  2. Host,User-Agent: 请求的是那个网址,使用的是什么浏览器
  3. Accept:接受的MIME类型
  4. Accept-Encoding: 接受的头部编码方式
  5. Alt-Used:指示备用使用的主机名,通常由 CDN 添加,用于识别请求关联的原始域名或优化路由。
  6. Upgrade-Insecure-Requests:告知服务器客户端是否偏好安全连接,建议将 HTTP 请求升级为 HTTPS 请求。
  7. Sec-Fetch-Dest:请求的目标类型,document代表HTML页面

HTTP Response

服务器在收到请求之后,找到对应的文件,向浏览器发送 HTTP 响应,并发送网站的内容:

HTTP/3 200 
date: Fri, 07 Feb 2025 05:31:06 GMT
content-type: text/html; charset=utf-8
cf-ray: 90e0fde6df51f702-NRT
server: cloudflare
access-control-allow-origin: *
cache-control: public, max-age=0, must-revalidate
referrer-policy: strict-origin-when-cross-origin
x-content-type-options: nosniff
content-encoding: br
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=Jp1u2y5xi5qqgRBTMzbTm4SiYnneRgWUb%2F8A%2BAraRwM6td2vk9RJHEGIzZqvuhHbrE%2FJF%2BIiOL2QFNCU9OUbfoqzYjLUB%2FqSLUt5yjdRNMzW13CTb3DAeMveVLBtSa7G0mw%3D"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
vary: Accept-Encoding
cf-cache-status: DYNAMIC
alt-svc: h3=":443"; ma=86400
  1. data, content-type:日期,MIME类型
  2. access-control-allow-origin:CORS跨域访问控制,值为*表示所有来自所有网页的请求都能够跨域访问。
  3. cache-control:用于控制资源的缓存策略。服务器通过该字段告知客户端或中间缓存服务器如何缓存响应资源,例如设定缓存有效期(max-age)、禁止缓存(no-cache/no-store)、以及在使用缓存前是否需要验证(must-revalidate)等。
  4. x-content-type-options:控制客户端对于MIME类型的处理,nosniff`表示客户端不需要猜测MIME类型,使用响应中声明的类型即可。

其他和 HTTP 协议有关的内容

HTTP 会话管理

HTTP 协议是一种无状态协议,即每个请求都是独立的,不会自动保存客户端状态。为实现用户认证、购物车和个性化服务,服务器采用会话管理机制。Cookie 是实现会话管理的重要手段,其本质是 HTTP 报文头中的一组键值对。 服务器在响应登录或首次请求时,会在 HTTP 响应报文中通过“Set-Cookie”头发送 Cookie(例如包含唯一的 Session ID),浏览器收到后存储并在后续请求中自动在“Cookie”头中携带。这样,服务器就能通过解析 HTTP 请求报文中的 Cookie,关联多个请求,从而保持会话状态。这种方式简单高效,但也需要防范如会话劫持等安全风险。

HTTP 请求报文提交Cookie:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml
Cookie: sessionid=abc123; theme=light; lang=zh

HTTP 响应报文设置Cookie:

HTTP/1.1 200 OK
Date: Sun, 09 Feb 2025 13:00:00 GMT
Server: Apache
Content-Type: text/html; charset=UTF-8
Set-Cookie: sessionid=abc123; Expires=Mon, 10 Feb 2025 13:00:00 GMT; Path=/; HttpOnly

CORS跨域访问

浏览器的同源策略要求,只有当协议、域名和端口均相同时,网页才能访问对应资源;这旨在防止恶意脚本跨域窃取数据。然而,实际应用中,前后端分离或调用第三方 API 等场景常需跨域访问。

CORS(跨源资源共享)正是为了解决这一问题而设计的。服务器通过在 HTTP 响应报文中添加特定的 CORS 标头(如 Access-Control-Allow-Origin、Access-Control-Allow-Methods 等)来授权跨域请求。当请求较复杂时,浏览器会先发送预检请求(OPTIONS 方法),确认服务器允许后再发送正式请求。这样既满足跨域数据传输需求,又在一定程度上确保了安全性,避免了任意跨域访问带来的风险。

请求端字段:

  • Origin:浏览器自动添加,表示请求发起源(协议、域名、端口)。
  • Access-Control-Request-Method:在预检请求(OPTIONS 请求)中指定实际请求将使用的方法。
  • Access-Control-Request-Headers:在预检请求中列出实际请求中将使用的自定义头部字段。 响应端字段:
  • Access-Control-Allow-Origin:指定允许跨域访问的来源,可以是具体域名或 *(但携带凭证时不能用 *)。
  • Access-Control-Allow-Methods:列出允许的 HTTP 方法(如 GET、POST、PUT 等)。
  • Access-Control-Allow-Headers:列出允许客户端在实际请求中发送的自定义头部。
  • Access-Control-Allow-Credentials:指明是否允许携带凭证(例如 Cookie),值为 true 表示允许。

参考资料