curl 命令行手动发送请求并查看完整响应,理解 HTTP/1.1 到 HTTP/3 的演进脉络。前十一篇里,你写了 HTML 定义内容、CSS 控制样式、JavaScript 添加交互——这些都在浏览器里完成。但真实的 Web 应用中,数据存在服务器上,浏览器需要通过网络向服务器"索要"数据。这套通信规则就是 HTTP(HyperText Transfer Protocol,超文本传输协议)。
把 HTTP 想象成一套邮政系统:浏览器是寄信人(客户端),服务器是邮局加收信人。寄信人按固定格式写好信封(请求),投入邮筒;邮局根据地址找到收信人,收信人处理后写一封回信(响应),原路返回。信封上的邮编、加急标记对应 HTTP 头部,信件内容对应请求体,回执单上的签收状态对应状态码——200 签收成功,404 查无此人,500 邮局内部出错。
HTTP 采用严格的"一问一答"模型:客户端发起一个请求,服务器返回一个响应。没有请求就没有响应——服务器不会主动推送数据(WebSocket 和 Server-Sent Events 是另外的协议,后续篇章涉及)。
URL(Uniform Resource Locator,统一资源定位符)就是信封上的地址——告诉 HTTP 该把请求送到哪里。一个完整的 URL 包含多个组成部分:
http 或 https(加密版)
api.example.com
/users 表示用户列表资源
?page=1&limit=10 用 & 分隔多组键值对
?page=1&limit=10 和 ?limit=10&page=1 效果相同。但参数值包含特殊字符(如 &、=、中文)时必须进行 URL 编码,否则服务器解析会出错。理解了 URL 这个"地址",接下来看信件本身。一个 HTTP 请求由四部分组成:请求行、请求头、空行、请求体。下面是一个真实的 GET 请求原始报文:
第一行是请求行,包含三部分:请求方法(GET)、请求路径(/api/users?page=1)、协议版本(HTTP/1.1),用空格分隔。后面几行是请求头,每行一个键值对。空行之后是请求体——GET 请求通常没有body,数据放在 URL 的查询参数里。
请求方法就是"信件类型"——平信、挂号信、快递各有不同用途。HTTP 定义了多种方法,日常开发最常用的有五种:
| 方法 | 含义 | 有请求体 | 典型场景 |
|---|---|---|---|
GET |
获取资源 | 否 | 打开网页、查询列表 |
POST |
创建资源 | 是 | 提交注册表单、发布文章 |
PUT |
替换资源(整体更新) | 是 | 更新用户完整信息 |
PATCH |
修改资源(部分更新) | 是 | 只改用户昵称 |
DELETE |
删除资源 | 通常否 | 删除文章、注销账号 |
POST 请求的请求体通常携带用户提交的数据。下面是一个 POST 请求的原始报文,注意请求体中的 JSON 数据:
Content-Type: application/json 告诉服务器请求体的格式是 JSON。Content-Length: 52 表示请求体有 52 字节。服务器读取完 52 字节后就知道请求体结束了。
请求寄出去了,服务器处理完会回一封信——HTTP 响应。结构和请求类似,也分四部分:状态行、响应头、空行、响应体。关键区别在第一行:请求行变成了状态行,包含状态码和状态描述。
第一行 HTTP/1.1 200 OK 是状态行:协议版本、状态码(200)、状态描述(OK)。状态码是回执单上最重要的信息——一眼就知道请求成功了没有。
| 范围 | 类别 | 含义 |
|---|---|---|
| 1xx | 信息性 | 请求已接收,继续处理(少见) |
| 2xx | 成功 | 请求被成功接收和处理 |
| 3xx | 重定向 | 需要进一步操作才能完成请求 |
| 4xx | 客户端错误 | 请求有语法错误或无法完成 |
| 5xx | 服务器错误 | 服务器处理时出错 |
| 状态码 | 描述 | 真实含义 |
|---|---|---|
200 |
OK | 请求成功,响应体包含数据 |
201 |
Created | POST 创建资源成功 |
204 |
No Content | 成功但无返回内容(DELETE 常用) |
301 |
Moved Permanently | 资源永久重定向到新地址 |
304 |
Not Modified | 资源未修改,用浏览器缓存即可 |
400 |
Bad Request | 请求语法错误(如 JSON 格式不对) |
401 |
Unauthorized | 未登录,需要身份认证 |
403 |
Forbidden | 已登录但无权限访问该资源 |
404 |
Not Found | 请求的资源不存在 |
500 |
Internal Server Error | 服务器代码崩溃或出错 |
502 |
Bad Gateway | 网关/代理从上游收到无效响应 |
503 |
Service Unavailable | 服务器过载或维护中,暂不可用 |
401;已登录但非管理员 → 返回 403
请求和响应都携带"头部"——相当于信封上的附加信息。头部是键值对形式,每行一个,用冒号分隔。它告诉对方"我是谁""我要什么格式""数据有多长"等元信息。掌握常用头部是调试网络问题的基本功。
| 请求头 | 作用 | 示例值 |
|---|---|---|
Host |
目标服务器域名 | api.example.com |
Content-Type |
请求体的数据格式 | application/json |
Authorization |
身份认证凭证 | Bearer eyJhbGc... |
Accept |
期望的响应格式 | application/json, */* |
User-Agent |
客户端身份标识 | Mozilla/5.0 (Macintosh) |
| 响应头 | 作用 | 示例值 |
|---|---|---|
Content-Type |
响应体的数据格式 | application/json; charset=utf-8 |
Content-Length |
响应体字节数 | 87 |
Set-Cookie |
设置 Cookie(后续篇章详解) | session=abc123; HttpOnly |
Cache-Control |
缓存策略指令 | max-age=3600 |
Location |
重定向目标地址(3xx 配合) | /users/1 |
Content-Type 是前后端协作中最容易出问题的头部。前端发 JSON 但没设 Content-Type: application/json,后端按默认的 application/x-www-form-urlencoded 解析,结果拿到的是空数据。这是 400 Bad Request 最常见的根因之一。浏览器帮你封装了 HTTP 请求,看不到原始报文。curl 是命令行下的 HTTP 客户端工具,macOS 和 Linux 自带,能让你手动发送请求并查看完整响应——调试 API 时比浏览器灵活得多。
执行后,终端会输出完整的 HTTP 响应(包含状态行、响应头和响应体):
-i 标志让 curl 输出响应头(默认只显示响应体)。httpbin.org 的 /get 端点会把收到的请求信息原样返回——你能看到服务器"收到"了哪些头部。
三个关键标志:-X POST 指定方法(GET 可省略,其他需显式指定)、-H 添加请求头、-d 携带请求体数据。httpbin.org 的 /post 端点会把收到的数据回显在 "json" 字段中。
| 标志 | 作用 | 示例 |
|---|---|---|
-X |
指定 HTTP 方法 | -X DELETE |
-H |
添加请求头 | -H "Authorization: Bearer xxx" |
-d |
携带请求体数据 | -d '{"key":"val"}' |
-i |
显示响应头 | curl -i URL |
-v |
显示完整通信过程 | curl -v URL |
-o |
输出保存到文件 | -o result.json |
-v(verbose)是调试 HTTP 的杀手锏——它同时显示请求头(以 > 开头)和响应头(以 < 开头),连 TLS 握手过程都能看到。遇到请求不生效时,先 curl -v 看看实际发了什么。curl 适合主动发起请求,但很多时候你需要观察页面加载时浏览器自动发了哪些请求——比如 AJAX 接口返回了 500,或者图片加载失败。这时用浏览器开发者工具的 Network 面板更方便。
F12 或 Cmd+Option+I(Mac)打开开发者工具
Cmd+R),Network 面板会列出所有 HTTP 请求
Network 面板的关键信息列:
| 列名 | 显示内容 |
|---|---|
| Name | 请求的文件名或路径 |
| Status | 状态码(200/304/404 等) |
| Type | 资源类型(document/js/css/xhr/img) |
| Size | 传输大小(显示"from cache"表示走了缓存) |
| Time | 请求耗时(毫秒) |
HTTP 协议从 1991 年诞生至今经历了多次迭代。理解版本差异不需要深入细节,但知道每个版本解决了什么问题,有助于你在面试和性能优化时做出正确判断。
| 版本 | 年份 | 核心改进 | 传输层 |
|---|---|---|---|
| HTTP/1.1 | 1997 | 持久连接(Keep-Alive),管线化 | TCP |
| HTTP/2 | 2015 | 多路复用、头部压缩、服务器推送 | TCP |
| HTTP/3 | 2022 | 基于 QUIC 协议,解决队头阻塞 | UDP |
HTTP/1.1 的核心问题是队头阻塞:一个 TCP 连接上多个请求必须按顺序处理,前面的慢了后面的全等。浏览器为了绕过这个限制,会对同一域名开 6 个并行连接,但每个连接仍存在队头阻塞。
HTTP/2 引入多路复用——一个 TCP 连接上同时跑多个请求/响应,互不阻塞。还加了 HPACK 头部压缩(减少重复头部传输量)和服务器推送(主动推送 CSS/JS 等资源)。
HTTP/3 更激进——直接抛弃 TCP,改用基于 UDP 的 QUIC 协议。TCP 层的队头阻塞在 HTTP/2 中仍存在(一个包丢了,整个连接的所有流都卡住),QUIC 在 UDP 上自己实现可靠性,每个流独立重传,彻底解决了这个问题。现代浏览器和主流 CDN 已广泛支持 HTTP/3。
curl -v 观察请求,响应状态行可能显示 HTTP/2 200 而非 HTTP/1.1 200——说明服务器已升级到 HTTP/2。curl 向 https://httpbin.org/get 发送 GET 请求,并使用 -i 标志查看响应头。确认状态码是否为 200,并找到响应头中 content-type 的值。curl 向 https://httpbin.org/status/404 发送请求,观察返回的状态码。然后再向 https://httpbin.org/status/500 发送请求。解释这两个状态码的含义差异,以及分别属于哪个类别。304 Not Modified 的请求,解释为什么服务器没有返回完整内容而是 304。提示:关注请求头中的 If-None-Match 或 If-Modified-Since,以及响应头中的 ETag 或 Last-Modified,搜索关键词"HTTP 条件请求"了解原理。