HTTP 是什么
这一课你将能做到
- 用自己的话说明一次「打开网页」里谁是客户端、谁是服务器,并说出请求与响应各包含哪几块(请求行 / 请求头 / 空行 / body)
- 在本机跑起一个最小 Spring Boot 接口,用
curl -v 抓出完整的请求与响应原文,逐行指出每个部分
- 在浏览器 DevTools Network 面板找到同一条请求,对照 curl 与浏览器看到的差异,说清「无状态」和「Content-Type」各指什么
为什么要学
后端写 @GetMapping、前端写 fetch、运维配 Nginx,底层都在说同一种「信封格式」——HTTP。接口报 401、CORS 拦截、缓存不失效,本质都是「信封上某个字段不对」。不会读请求/响应,排错只能靠猜;会读之后,DevTools 里一行就能定位是方法错了、状态码不对还是头缺了。
面试里「讲讲从输入 URL 到页面展示发生了什么」是出现频率最高的问题之一,答案的骨架就是 HTTP 请求/响应模型。本课不堆 RFC 条文,只建立日常开发够用的心智模型:无状态、文本头 + 可选 body、客户端主动发起。
先建立图像
把 HTTP 想成寄包裹:
客户端(浏览器 / curl / 手机 App)
│ 写好「请求信封」:方法 + 路径 + 头 +(可选)正文
▼
网络(生产常经 Nginx 等中间层)
│
▼
服务器(Spring Boot / Next.js / 静态文件服务)
│ 回「响应信封」:状态码 + 头 +(可选)正文
▼
客户端拆开使用(渲染 HTML / 解析 JSON / 存 Cookie)
一条 HTTP/1.1 报文的解剖图:
GET /api/tracks HTTP/1.1 ← 请求行:方法 + 路径 + 协议版本
Host: coderoadmap.cn ← 请求头(键值对,到空行为止)
Accept: application/json
(空行:头与 body 的分界线)
(GET 通常没有 body)
HTTP/1.1 200 OK ← 状态行:协议版本 + 状态码 + 短语
Content-Type: application/json ← 响应头
Content-Length: 57
[{"slug":"http-protocol-essentials","title":"HTTP 必修"}] ← 响应 body
| 名词 | 人话 | 例子 |
|---|
| 请求 | 客户端发出的那一包 | GET /api/tracks |
| 响应 | 服务器回的那一包 | 200 + JSON 数组 |
| 无状态 | 协议本身不记得「上一包是谁」 | 登录态要靠 Cookie/Token 每次自己带 |
| 资源 | URL 指向的「东西」 | /api/tracks 这张课表 |
协议栈位置(记住分工即可,不必背层号):
应用层 HTTP ← 本 track 全部内容
传输层 TCP(可靠字节流)→ TLS(加密,HTTPS 课细讲)
网络层 IP(寻址)
生产环境常见拓扑:浏览器 ──TLS──► Nginx:443 → 内网明文 HTTP → Spring Boot:8080。
跟着做
步骤 1:在本机跑一个最小 Spring Boot 接口
用 Spring Initializr 建一个只勾 Spring Web 的项目(本站同款技术栈),加一个控制器:
@RestController
public class HelloController {
@GetMapping("/api/hello")
public Map<String, String> hello() {
return Map.of("msg", "hello java-nav");
}
@PostMapping("/api/echo")
public Map<String, Object> echo(@RequestBody Map<String, Object> body) {
return Map.of("received", body, "at", System.currentTimeMillis());
}
}
mvn spring-boot:run
# 看到 Tomcat started on port 8080 即成功
步骤 2:用 curl -v 看信封原文
curl -v http://localhost:8080/api/hello
-v(verbose)会把所有细节打出来,> 开头的是客户端发出的,< 开头的是服务器返回的:
> GET /api/hello HTTP/1.1 ← 请求行
> Host: localhost:8080 ← curl 自动补的头
> User-Agent: curl/8.x
> Accept: */*
>
< HTTP/1.1 200 ← 状态行
< Content-Type: application/json
< Transfer-Encoding: chunked
<
{"msg":"hello java-nav"} ← body
对照「先建立图像」的解剖图,把四块在输出里圈出来。这一步做完,HTTP 报文就不再是抽象概念。
步骤 3:发一条带 body 的 POST
# -H 设置请求头;-d 设置请求体;POST JSON 必须声明 Content-Type
curl -v -X POST http://localhost:8080/api/echo \
-H "Content-Type: application/json" \
-d '{"name":"java-nav","type":"track"}'
观察两点:请求侧多了 Content-Type: application/json 和一行 body;响应仍是 JSON。GET 和 POST 的差别在报文上就是「有没有 body、方法写什么」。
步骤 4:浏览器 Network 面板对照
- 打开任意网站(如
https://coderoadmap.cn/tracks),按 F12 → Network 面板
- 勾选 Preserve log,按 Ctrl+Shift+R 硬刷新
- 点第一行 document 请求,右侧 Headers 分栏里就是浏览器版的报文视图
- 对比:浏览器自动补了更多头(
sec-ch-ua、Accept-Language 等),但骨架和 curl 看到的一模一样
注意同一动作往往触发几十条请求(HTML、CSS、JS、API 各一条)——一次「打开网页」= 一串独立 HTTP 交换,不是一条请求搞定整站。
步骤 5:故意看一次失败形状
# 请求一个不存在的路径
curl -i http://localhost:8080/api/no-such-path
会看到 404(Spring Boot 默认还有一段 JSON 错误体)。养成习惯:先看状态码,再读 body。状态码是摘要,body 是细节。
步骤 6:对照生产链路
浏览器 → Nginx:443(TLS 在这里结束)
→ /api/* 转发给 Spring Boot:8080 生成 JSON
→ 页面路由转发给前端服务生成 HTML
你 curl 的 localhost:8080 绕过了 Nginx,所以没有 TLS、没有反代追加的头;生产请求则多了一层。排错时要分清「谁收到了这条请求」。
讲清楚要点
- 因为 HTTP 是应用层协议,所以 curl、浏览器、Java HttpClient 看到的是同一套「方法/状态/头/body」模型——换工具不换语法,学会一次到处能用。
- 因为协议默认无状态,所以服务器不会「自动记得你上次登录」——会话必须靠 Cookie、Authorization 等客户端每次自带的凭证(Cookie 课细讲)。
- 因为请求由客户端发起,所以服务端不能凭空「推一条 HTTP」给浏览器——实时推送要靠 SSE/WebSocket 另外的机制;排错永远从「这条请求是谁发起的」问起。
- 因为头是元数据、body 是载荷,所以 Content-Type 错了 body 再对也解析失败——前后端必须对「这是 JSON 还是表单」达成一致,这是新手接口联调第一大坑。
- 因为中间可能有 Nginx、CDN、代理,所以浏览器看到的 IP/端口未必是真正执行业务的那台进程——生产排错要对照 Nginx 访问日志和 Spring 应用日志两处。
- 因为 HTTPS 只是「HTTP 跑在 TLS 加密通道上」,所以方法、状态码、头的语义在 HTTPS 下完全不变——可以分开学,但公网传输必须加密(HTTPS 课细讲)。
你可能会懵的地方
-
「无状态」是不是说服务器完全不能存数据?
不是。MySQL、Redis 随便存。「无状态」指的是协议本身不强制在请求之间维持会话——每个请求必须自带完成它所需的全部信息。会话是应用层自己加的(Cookie + Redis Session 等)。
-
打开一个页面为什么 Network 里冒出几十条请求?
HTML 里还会引用 CSS、JS、字体、图片,页面 JS 还会调 API,每一条都是独立的 HTTP 请求/响应。分析性能按「瀑布图」逐条看,不要当成一坨。
-
HTTP/2 的线上报文是二进制的,我学的文本模型还有用吗?
有用。HTTP/2 改的是传输编码(二进制分帧),应用语义(方法、状态码、头字段、body)完全不变。DevTools 会自动翻译回熟悉的 Headers 视图,学习阶段用 HTTP/1.1 文本模型完全够。
-
为什么 curl 能通、浏览器却报错?
浏览器比 curl 多了同源策略、Cookie 自动管理、混合内容拦截等安全层。curl 没有同源限制,所以「curl 通、浏览器不通」十有八九是 CORS 或 Cookie 问题,后面 CORS 课专讲。
-
HTTP 报文里的「空行」有什么用?
它是头和 body 的强制分界线。没有空行,解析器不知道头到哪里结束。手写原始报文或看抓包时,空行丢了会直接解析失败。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 把「打开网址」当成一次魔法,不分请求/响应 | 排错无从下手 | 先在 Network 找 document,再找它引发的依赖请求 |
| 只看 body 不看状态码和 Content-Type | 把 HTML 错误页当 JSON 解析,报一堆莫名错误 | 排查顺序:状态码 → 头 → body |
| 以为服务器「记得」上次 GET,漏带 Cookie/Token | 接口突然 401 还以为是 Bug | 每个请求自带凭证;无凭证就接受 401 |
| 用浏览器地址栏测 POST 接口 | 地址栏本质是 GET 导航,永远测不到 POST | 用 curl 或接口工具发真实方法 |
| 把 TCP「连接成功」当成「业务成功」 | 连接层通不代表应用层对 | 业务结果看应用层状态码,不看连接是否建立 |
卡点急救
curl: command not found:Windows 10 1809+ 自带 curl(在 PowerShell/CMD 直接用);老系统可从 curl.se 下载,或用 Git Bash 自带的 curl。macOS/Linux 原生自带。
Connection refused:Spring Boot 没启动成功,或端口不是 8080。回启动日志找 Tomcat started on port xxx,把 curl 的端口改成实际端口;用 netstat -ano | findstr 8080(Windows)确认监听是否存在。
- 终端输出中文乱码:Windows CMD 默认 GBK。PowerShell 里先执行
chcp 65001 切 UTF-8,或改用 Windows Terminal / Git Bash;Spring Boot 侧确认 server.servlet.encoding.charset=UTF-8。
面试常问
-
「从输入 URL 到页面展示,中间发生了什么?」
主线:DNS 解析 → TCP 三次握手 →(HTTPS 再加 TLS 握手)→ 浏览器发出 HTTP 请求(方法 + 路径 + 头)→ 服务器(可能经 Nginx 反代)处理后返回状态码 + 头 + body → 浏览器解析 HTML,再对其引用的 CSS/JS/图片/API 发起更多 HTTP 请求 → 渲染。答出「多次 HTTP 交换 + 无状态靠 Cookie 补会话」就超过多数人。
-
HTTP 为什么叫「无状态」?带来什么问题?怎么解决?
协议对事务处理没有记忆能力,每个请求独立。问题是登录态、购物车这类「跨请求上下文」无法天然维持。解决方案:Cookie/Session(服务端存状态,客户端带 session id)、Token/JWT(客户端自包含凭证)、或每次请求显式带鉴权头。
-
HTTP 请求报文和响应报文的结构?
请求:请求行(方法/路径/版本)+ 请求头 + 空行 + 可选 body。响应:状态行(版本/状态码/短语)+ 响应头 + 空行 + 可选 body。关键点:头与 body 之间必须有空行分隔;GET 通常无 body,POST/PUT 常有。
自测三问
- 用四块结构描述一条
GET /api/tracks:请求行、请求头、状态行、响应头各举一个真实例子。
- 为什么说 HTTP 无状态?登录态通常靠什么机制补回来?
- 同一站点里 HTML 页面与 JSON API 的
Content-Type 分别是什么?你在 DevTools 里如何确认?
小结一句话
HTTP 是 Web 的信封格式:客户端发请求、服务器回响应;无状态、文本头 + 可选 body、客户端主动——读懂信封,才谈得上修接口。
延伸