从输入网址到看到页面:HTTP 请求的旅程
2025-08-09
“在浏览器输入网址并回车,到页面显示出来,中间发生了什么?”这大概是最经典的面试题,也是我最喜欢拿来串联网络知识的题目。这篇文章按时间顺序走一遍整个旅程,并把 HTTP 报文、状态码、缓存、版本演进和常用排查命令一并讲清楚。
整体流程
在浏览器输入网址并回车后,大致会发生这些事:
- 解析网址,得到协议、域名、路径。
- DNS 解析,把域名变成 IP。
- 与服务器建立 TCP 连接;如果是 HTTPS,再进行 TLS 握手。
- 浏览器发送 HTTP 请求,服务器返回响应。
- 浏览器解析 HTML,继续请求 CSS、JS、图片等资源并渲染。
下面逐步展开。
第一步:解析网址
一个完整的网址长这样:https://www.example.com:443/posts/http.html?lang=zh#top。它由几部分组成:协议(https)、主机名(www.example.com)、端口(443,https 的默认端口,通常省略)、路径(/posts/http.html)、查询字符串(?lang=zh)和片段(#top)。要注意的是,片段只在浏览器本地使用,用来定位页面内的位置,不会发送给服务器。
浏览器在真正发请求之前,还会先看看有没有可用的缓存、有没有强制 HTTPS 的记录(HSTS),所以有时你输入 http 开头的地址,浏览器自己就悄悄改成了 https。
第二步:DNS 解析
机器之间通信认的是 IP 地址,域名只是给人看的“名字”。浏览器会依次查浏览器缓存、操作系统缓存、hosts 文件,然后询问配置的递归解析器,由它去逐级查询权威服务器,最终拿到 IP。这一步可能花几毫秒也可能更久,细节我单独写了一篇:DNS 是如何工作的。可以用 dig example.com 亲自看一下返回的记录。
第三步:建立连接
拿到 IP 后,浏览器与服务器的 80 或 443 端口建立 TCP 连接,也就是经典的三次握手,过程见 TCP/IP 那篇。如果是 HTTPS,TCP 连接建立后还要进行 TLS 握手,协商协议版本、密码套件,校验服务器证书,并生成会话密钥,之后所有的 HTTP 内容都会被加密传输。TLS 的细节同样单独写过:HTTPS 与证书。
可以看出,在发出第一个字节的 HTTP 数据之前,已经付出了 DNS、TCP、TLS 好几个往返的时间。这就是为什么“减少新连接”“复用连接”是性能优化里的老生常谈。
请求与响应
HTTP 是文本形式的请求—响应协议(HTTP/1.1 里直接是明文文本,HTTP/2 之后改为二进制帧,但语义保持不变)。一个最小的请求长这样:
GET /index.html HTTP/1.1 Host: www.example.com
第一行叫请求行,包含方法、路径和协议版本;后面是若干请求头;如果有请求体(比如 POST 提交表单),会空一行后跟在后面。HTTP/1.1 里 Host 头是必须的,因为一台服务器上往往托管着多个站点,要靠它区分。常见的请求头还有 User-Agent、Accept、Accept-Language、Cookie 等。
服务器返回的响应则由状态行、响应头和响应体组成,例如:
HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 1256 Cache-Control: max-age=3600
常用请求方法
- GET:获取资源,不应该有副作用,参数放在网址里。
- POST:提交数据,创建资源或触发操作,参数放在请求体里。
- PUT:整体替换某个资源;PATCH:局部修改。
- DELETE:删除资源。
- HEAD:和 GET 相同,但只返回响应头,不返回响应体。
- OPTIONS:询问服务器支持哪些方法,浏览器的跨域预检请求就用它。
GET、HEAD、PUT、DELETE 在语义上是幂等的,也就是重复执行多次与执行一次效果相同,而 POST 通常不是,所以浏览器刷新 POST 结果页时会提示是否重新提交。
状态码
响应的第一行是状态码,常见的有:
- 200 成功;301/308 永久重定向;302/307 临时重定向。
- 403 无权限;404 找不到;429 请求太频繁。
- 500 服务器内部错误;502/504 网关错误或超时。
状态码按首位数字分为五类:1xx 表示信息性响应,2xx 成功,3xx 重定向,4xx 客户端错误,5xx 服务器错误。再补充几个常见的:204 表示成功但无内容;304 表示资源未修改,可以使用本地缓存;400 表示请求本身有问题;401 表示需要身份认证;405 表示方法不被允许;503 表示服务暂时不可用,常见于维护或过载。
几个容易混淆的状态码
- 301 与 308:都是永久重定向。区别在于 301 历史上允许客户端把 POST 改成 GET 重新请求,而 308 明确要求保持原来的方法和请求体不变。302 与 307 的关系同理,307 是不改变方法的临时重定向。
- 401 与 403:401 的意思是“你没有证明你是谁”,通常带上认证信息重试就可能成功;403 的意思是“我知道你是谁,但你无权访问”。
- 502 与 504:二者都出现在有网关或反向代理的场景。502 是网关从上游收到了无效的响应或连接失败,504 是网关等待上游超时。排查时前者多看上游服务是否崩溃,后者多看上游是否处理太慢。
缓存与条件请求
浏览器会尽量避免重复下载。服务器通过响应头告诉浏览器怎么缓存,最常用的是 Cache-Control: max-age=3600,表示一小时内可以直接使用本地副本,无需再问服务器。过期之后,浏览器可以带上 If-None-Match(配合响应里的 ETag)或 If-Modified-Since(配合 Last-Modified)去询问服务器:如果资源没变,服务器只回一个 304,不带响应体,省下了传输的流量。反过来,Cache-Control: no-store 表示不要保存,no-cache 则是“可以保存,但每次使用前必须向服务器验证”,名字容易让人误解。更多关于缓存的内容,可以看 CDN 那篇。
Cookie 与无状态
HTTP 本身是无状态的,服务器默认不记得你上一次请求。为了实现登录态,服务器会用 Set-Cookie 响应头下发一个标识,浏览器之后在符合条件的请求里自动带上 Cookie 头。Cookie 可以配上 Secure(只在 HTTPS 下发送)、HttpOnly(脚本不可读取)、SameSite(限制跨站携带)等属性来提升安全性。涉及登录的站点,这几个属性我基本都会打开。
第五步:渲染页面
浏览器拿到 HTML 后边下载边解析,构建 DOM 树;遇到 CSS 就构建样式规则,合并后得到渲染树,再经过布局和绘制显示出来;遇到脚本、图片、字体就继续发起新的请求。一个网页往往要请求几十个资源,所以请求数量、资源体积、缓存命中率都会影响加载速度。
HTTP 版本
HTTP/1.1 一个连接上请求要排队;HTTP/2 支持多路复用;HTTP/3 基于 QUIC(UDP),在弱网和切换网络时表现更好。
- HTTP/1.1:默认复用连接(keep-alive),但同一连接上响应必须按顺序返回,前面的慢了后面都得等,这叫队头阻塞。浏览器因此会对同一域名开多个并行连接来缓解。
- HTTP/2:采用二进制分帧,一个连接上可以同时传输多个流,互不阻塞;还支持头部压缩(HPACK)。不过它仍然跑在 TCP 上,一旦 TCP 层丢包,所有流都要等待重传,这属于 TCP 层的队头阻塞。主流浏览器只在 HTTPS 上使用 HTTP/2。
- HTTP/3:把传输层换成 QUIC,基于 UDP,每个流独立处理丢包,TLS 1.3 内置在 QUIC 里,建立连接所需的往返更少;连接用连接 ID 标识,因此手机从 Wi-Fi 切到蜂窝网络时有机会保持连接不断。
需要强调的是,版本升级改变的是传输方式,请求方法、状态码、头部这些语义基本保持一致,所以应用开发者通常不需要因为换了版本而改业务代码。
用 curl 观察整个过程
用 curl -I https://example.com 可以快速查看响应头。下面是几个我常用的命令:
curl -I https://example.com
curl -v https://example.com
curl -L -o /dev/null -s -w "%{http_code} %{url_effective}\n" http://example.com
curl --http2 -I https://example.com
curl --resolve example.com:443:203.0.113.10 https://example.com
curl -H "Accept-Language: en" -i https://example.com
第二条的 -v 会显示详细过程,包括 DNS 解析结果、TCP 连接、TLS 握手和完整的请求与响应头,是学习用的最佳选择。第三条中的 -L 表示跟随重定向,-w 可以输出最终的状态码和网址。--resolve 非常实用:它让你绕过 DNS,强制把某个域名指向指定的 IP,用来测试新服务器或单台节点(这里的 203.0.113.10 是文档示例地址)。此外,浏览器自带的开发者工具里的“网络”面板,也能看到每个请求的耗时拆分:DNS、连接、SSL、等待、下载。
常见问题与排查思路
- 打不开页面:先用
dig确认域名能解析;再用curl -v看卡在连接、TLS 还是等响应。卡在连接,多半是网络或防火墙问题;卡在 TLS,多是证书或协议版本问题。 - 返回 502/504:不要只盯着网关,去看上游服务的日志和负载,也检查网关的超时设置是否过短。
- 重定向循环(ERR_TOO_MANY_REDIRECTS):常见于反向代理后面的应用不知道外层已经是 HTTPS,一直要求跳转到 HTTPS;检查 X-Forwarded-Proto 之类的配置。
- 更新了页面但看到的还是旧的:多半是缓存。检查 Cache-Control,强制刷新,或者给静态资源使用带哈希的文件名。
- 跨域报错:这是浏览器的同源策略在起作用,需要服务端返回正确的 CORS 响应头,而不是前端“绕过”。
总结
一次访问的旅程可以概括为:解析网址、DNS 查 IP、TCP 建连、(HTTPS 时)TLS 加密握手、发送 HTTP 请求、接收响应、解析并渲染、继续拉取子资源。状态码按首位数字分类,缓存头决定了能否少跑一趟,HTTP/2 和 HTTP/3 则在传输效率上不断改进。遇到问题时,把这条链路一环一环对过去,再配合 dig、curl -v、浏览器开发者工具,大部分故障都能定位到具体的环节。