专栏:AI 全栈开发|06
|----------------------------------------------------------------------------------------------------------------------------|
| 上一章我们把 frontend/ 和 backend/ 分开了。现在真正要看懂它们中间那条线:浏览器里的一次 fetch,为什么能跑到 FastAPI?这一篇只建立完整链路,不提前把 HTTP 报文、SSE、WebSocket、并发全部塞进来。 |
一、先从我们自己的 AI Chat 页面出发
假设页面已经打开,用户在输入框里写"你好",然后点击发送。前端代码里可能只有一句:
const res = await fetch("https://api.example.com/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "你好" }),
});
看起来只是一个函数调用,但浏览器要真的把这段数据送到服务器,至少要解决几个问题:服务器在哪?怎么建立连接?数据用什么规则表达?服务器处理完以后,响应又怎么回来?
图 1 一行 fetch 背后的基础路线:找地址 → 建连接 → 发请求 → 服务端处理 → 收响应
二、第一步:浏览器先得知道"api.example.com 在哪"
代码里写的是域名 `api.example.com`,网络通信最终需要找到目标主机的网络地址。DNS 就是负责"名字 → 地址"这件事的系统。
浏览器/操作系统会优先利用已有缓存;没有可用结果时,才继续向 DNS 解析体系查询。
对小白来说,这一篇不需要背递归、迭代和权威 DNS 的完整过程。先记住:
域名不是服务器本身,它只是一个方便人记的名字;请求要发出去,系统必须先知道这个名字应该去哪里。
三、第二步:找到地址以后,还要建立一条可用的连接
如果使用常见的 HTTPS + HTTP/1.1 或 HTTP/2,浏览器通常会在 TCP 连接之上建立 TLS 加密连接。
TCP 负责可靠传输;TLS 负责加密通信并验证服务器身份。
但这里要纠正一种非常常见的"固定流程背诵":
|------------------------------------------------------------------------------------------------------------------|
| 不是每一次 fetch 都一定重新做 DNS → TCP → TLS。浏览器会缓存 DNS 结果,也会尽量复用已经建立的连接。现代 HTTP/3 还使用 QUIC,不再是传统的"TCP + 独立 TLS 握手"这套连接方式。 |
所以这篇真正要建立的是"职责地图",不是死记每次请求一定发生几次握手。
四、第三步:连接有了,HTTP 才开始表达"我想要什么"
现在浏览器终于可以发送 HTTP 请求。
我们的代码想表达的是:
• 目标:`/api/chat`
• 动作:POST
• 内容类型:JSON
• 正文:`{"message":"你好"}`
至于 Method、URL、Header、Body、Status Code 分别是什么,下一篇 07 会专门拆。这里先把 HTTP 理解成:
在已经可通信的连接上,客户端和服务器约定一套"请求什么、返回什么"的应用层规则。
五、第四步:请求到的"服务器"不一定直接就是 FastAPI
线上部署时,域名后面可能先经过 CDN、负载均衡器、云网关或 Nginx,再转发到真正的 FastAPI 进程。
但无论中间有多少层,最终都会把一个 HTTP 请求交给某个应用入口。FastAPI 根据路径和方法找到对应路由:
from fastapi import FastAPI
app = FastAPI()
@app.post("/api/chat")
async def chat():
return {"reply": "你好"}
这一刻才真正进入我们平时写的后端业务代码。
|-------------------------------------------------------------------------------------------|
| 所以"请求慢"不一定等于 FastAPI 代码慢。DNS、连接、TLS、网关、应用、下游模型等很多位置都可能耗时。后面做 Observability 时,会把这些耗时逐层串起来。 |
六、第五步:服务器返回响应,浏览器拿到以后做什么
FastAPI 返回的数据会沿着网络路径回到浏览器。
如果这是一个普通 JSON API,前端 JavaScript 会读取响应,再更新页面状态:
const data = await res.json();
setReply(data.reply);
然后 React 根据新的 State 更新需要变化的 UI。
这和"第一次打开网页"并不完全一样。

图 2 打开网页会经历 HTML/CSS/JS 的加载与渲染;页面里的 API 请求通常只是拿数据,再由前端更新局部 UI
第一次访问网页时,浏览器拿到 HTML 后还要解析,并继续请求 CSS、JavaScript、图片等资源。页面脚本运行以后,点击"发送"通常只是再次发 API 请求,并不会重新加载整张页面。
七、把整条链路压成一张小白能记住的图
|--------------------|---------------|----------------|
| 阶段 | 它解决的问题 | 你以后会在哪里深入 |
| URL / DNS | 服务器在哪里 | 域名与部署专题 |
| 连接:TCP/TLS 或 QUIC | 怎么安全、可靠地通信 | 部署 / 性能专题 |
| HTTP | 请求和响应怎么表达 | 07~10 |
| Server / FastAPI | 请求由哪段后端代码处理 | 41~54 |
| Browser / Frontend | 数据回来后 UI 怎么变化 | 25~40 |
| Streaming | 结果还没结束时怎样持续返回 | 11~12、19、47~49 |
现在不需要掌握每一个协议细节。只要能指出"问题大概出在哪一层",F06 就达到目的了。
八、请求失败时,先别一句话都归结成"后端挂了"
图 3 常见报错落在不同层:先定位层,再处理具体问题
例如:
• 域名解析失败:FastAPI 甚至还没机会收到请求。
• connection refused:通常说明地址/端口可达性或服务监听有问题。
• 证书错误:连接卡在 TLS 层。
• 404 / 500:说明 HTTP 请求通常已经到达某个服务器,接下来才看路由或业务。
• 浏览器报跨域,而 curl 正常:请求可能到了服务器,但浏览器的同源/CORS 策略限制了前端读取响应。CORS 后面安全章节会专门讲。
• 响应已经拿到,页面却不变:要回到前端 State 和渲染逻辑检查。
九、用浏览器 DevTools 真正看一次
这篇最值得做的练习,不是自己手写 TCP 客户端,而是打开浏览器开发者工具的 Network 面板。
在聊天页面点击一次发送,然后点开 `/api/chat` 请求。你会看到 URL、Method、Status、Request/Response Headers、Payload、Timing 等信息。
现在先不用把每个字段看懂,只做一件事:
把"我代码里的一次 fetch"与"Network 面板里的一条真实请求"对应起来。
下一篇学习 HTTP 请求和响应时,我们就直接在这条真实请求上继续拆。
十、几个原稿里需要纠正的说法
• "一次请求固定经过 DNS → TCP → TLS"过于绝对。缓存和连接复用会跳过重复步骤;HTTP/3 的连接栈也不同。
• HTTP/2 并没有消除 TCP 层的队头阻塞;它主要解决 HTTP/1.1 层面多个请求在连接使用方式上的限制,而底层 TCP 丢包仍可能影响同连接的数据。
• "浏览器同域名最多 6 个连接"只能作为常见 HTTP/1.1 行为的近似印象,不应该当成所有浏览器和所有协议的固定标准。
• "HTTPS = 在 HTTP 和 TCP 之间插一层 TLS"适合解释 HTTP/1.1/2,但不能拿它概括 HTTP/3。
• 页面拿到 HTML 不等于页面已经可见可用;但 API 请求拿到 JSON 后,也不需要重新走完整页面解析流程。
十一、这一篇只记住 5 句话
• 域名先要被解析到可通信的目标地址。
• 浏览器需要一个可用的网络连接,HTTPS 还涉及安全连接。
• HTTP 负责表达请求和响应的应用层语义。
• 请求可能经过网关等中间层,最后才进入 FastAPI 路由。
• 响应回来以后,页面导航和 API 调用的处理方式不同;AI Chat 主要是前端拿数据并更新 UI。
十二、下一篇
下一篇 07《HTTP 请求和响应到底由什么组成?》会直接打开刚才 Network 面板里的那条 `/api/chat`:Method、URL、Header、Body、Status Code、Content-Type 分别是什么,它们为什么构成了前后端通信最基础的契约。