《基础入门》目录

第 1 章

一次请求到底经过了什么——从 DNS 到响应的完整旅程

本章目标:

  1. 建立一次 HTTP 请求的完整地图:DNS、TCP、TLS、nginx 分发、应用处理、响应渲染,每一段都能叫出名字
  2. 会用 dig 和 curl -v 把每一段亲手跑出来看,而不是背概念
  3. 理解 SNI 机制:为什么一台服务器、一个 IP 能挂多个域名和各自独立的证书
  4. 分清静态站和动态站在 nginx 里的两条命运:try_files 与 proxy_pass
  5. 拿走一组排错入口:hosts、证书、502/504、301 缓存

本章基线:nginx + Halo(动态站 blog.qianyecube.cn)+ Astro 纯静态(notes.qianyecube.cn)+ Let’s Encrypt 证书 | 两台站跑在同一台云服务器上,正好当「一 IP 多域名」的活标本 | 命令输出为 2026-11-01 实拍

脱敏声明:服务器 IP 一律写作 203.0.113.10(RFC 5737 专门划给文档的测试网段——写教程不该把真实 IP 送人,这也是你的服务器的第一道习惯),软件版本号已抹去;域名是公开信息,原样保留,你拿自己的域名跑同款命令,输出长得一模一样

6.1 全景:一次请求的九段旅程

在浏览器地址栏敲下 https://notes.qianyecube.cn/knowledge 回车,到页面出现,中间发生了九件事:

sequenceDiagram
    participant B as 浏览器
    participant D as DNS 解析
    participant N as nginx(服务器)
    participant A as 应用(Halo 容器)

    B->>D: ① 查 notes.qianyecube.cn 的 IP
    D-->>B: ② 203.0.113.10(带 TTL 缓存)
    B->>N: ③ TCP 三次握手 :443
    B->>N: ④ TLS 握手(验证书、协商密钥、SNI 报域名)
    B->>N: ⑤ HTTP 请求 GET /knowledge
    N->>N: ⑥ 匹配 server_name,选 server 块
    alt 静态站(notes)
        N->>N: ⑦a try_files 找文件,直接回
    else 动态站(blog)
        N->>A: ⑦b proxy_pass 到 127.0.0.1:8091
        A-->>N: 渲染好的 HTML
    end
    N-->>B: ⑧ 响应 + 状态码 + 缓存头
    B->>B: ⑨ 解析 HTML,再发起新请求(CSS/JS/图)

第 ⑨ 步值得划重点:页面出现不代表只有一次请求——HTML 只是第一张多米诺,CSS、JS、图片、字体各是独立请求。性能优化的一半工作发生在「 subsequent requests」上(合并、缓存、预加载),这也是为什么 6.6 的缓存头值得单独一节。

本章所有环节都可以亲手复现。装备两条命令就够:dig 看 DNS,curl -v 看其余全部。

6.2 第一段:DNS——域名怎么变成 IP

机器之间用 IP 通信,域名是给人用的。DNS 就是把域名翻译成 IP 的全球分布式数据库:

$ dig +short notes.qianyecube.cn
203.0.113.10

这一行答案背后是一条查询链:浏览器先查自己的缓存,没有就问操作系统( hosts 文件和系统缓存在这层),还没有就问递归解析服务器(运营商或公共 DNS,如 223.5.5.5),递归服务器再从根域名服务器一路问到 .cn 权威服务器,最终拿到 notes.qianyecube.cn 在 DNS 记录里的 A 记录值。每一层都带 TTL 缓存——所以改 DNS 记录后「要等一会儿才生效」,等的就是各层缓存的 TTL 到期。

两个实用推论:换服务器 IP 时把 TTL 先调小(比如 300 秒),迁移窗口就不至于被旧缓存钉死;本地开发想「强制域名指到某台机器」,改的是 hosts 文件(操作系统层),浏览器缓存要另清。

6.3 TCP 与 TLS:两条命令看懂握手

DNS 拿到 IP 之后,浏览器和 203.0.113.10 的 443 端口建立 TCP 连接(三次握手),然后在这个加密通道里做 TLS 握手。curl -v 把两段全打出来:

$ curl -v -o /dev/null https://notes.qianyecube.cn/knowledge
*   Trying 203.0.113.10:443...            ← DNS 已解析,开始 TCP
* Connected to notes.qianyecube.cn (203.0.113.10) port 443   ← 三次握手完成
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 ← TLS 1.3,一套加密套件
*  subject: CN=notes.qianyecube.cn         ← 证书发给谁
*  issuer: C=US; O=Let's Encrypt; CN=YE2   ← 谁签发的(Let's Encrypt 中间证书)
> GET /knowledge HTTP/2                    ← TLS 通道里发的第一条 HTTP 请求
< HTTP/2 200                               ← 注意协议是 HTTP/2
< server: nginx
< cache-control: no-cache

读这十行的正确方式:TLS 之前的一切都是明文可见的(DNS 查询、TCP 握手、SNI 里的域名),TLS 之后的一切(包括 URL 路径、请求头、响应体)都是密文。TLS 1.3 相对 1.2 把握手从两次往返压到一次,握手阶段还把证书加密了——这是公网接口都该开的最低配置,nginx 里对应 ssl_protocols TLSv1.2 TLSv1.3。

证书字段顺带解释了浏览器的小锁图标:浏览器验证 subject 里的域名和访问的域名一致、issuer 的签发链能追到受信根证书、有效期没过——三条有一条不过,就是那个「您的连接不是私密连接」。

一个 IP 怎么挂两个域名:SNI

作者的两台站点在同一台服务器、同一个 IP 上,证书却是两张(blog 一张、notes 一张)。TLS 握手时,客户端在明文段里通过 SNI(Server Name Indication)扩展告诉服务器「我要访问的是哪个域名」,nginx 据此选出对应的 server 块、递上对应的证书:

notes.qianyecube.cn  →  A 记录 203.0.113.10  ┐
blog.qianyecube.cn   →  A 记录 203.0.113.10  ┘ 同一台机器
        浏览器 SNI: "notes.qianyecube.cn" → nginx 递 notes 的证书 → 进 notes 的 server 块

没有 SNI,一台 IP 只能服务一张证书,HTTPS 时代的虚拟主机就不成立。老系统对接时如果碰到「证书不对但域名对」,先查对方是不是不支持 SNI 的古董客户端。

6.4 nginx:两条命运的分岔口

TLS 隧道打通,请求进入 nginx。nginx 用 server_name 匹配出该处理它的 server 块——这台服务器上有两份真实配置,正好是静态站和动态站的两条命运。

共同的第一站:HTTP 请求先被 80 端口的 server 块接住,整段 301 到 HTTPS——这是全站唯一入口原则,避免搜索引擎把 http:// 和 https:// 当两个站分权重:

server {
    listen 80;
    server_name notes.qianyecube.cn;
    location /.well-known/acme-challenge/ { root /var/www/html; }  # 证书续期专用
    location / { return 301 https://$host$request_uri; }
}

命运 A——静态站(notes):请求的文件就在磁盘上,try_files 按顺序找:

root  /opt/qianyecube/notes/current;          # current 是软链,指向最新一版发布目录
location / {
    try_files $uri $uri/index.html $uri/ =404;  # 文件 → 目录首页 → 目录跳转 → 404
    add_header Cache-Control "no-cache";
}

/knowledge 这个 URL 磁盘上没有同名文件,落进 $uri/index.html 找到 knowledge/index.html 直接回——nginx 自己就是应用,没有后端参与,这就是静态站的快。

命运 B——动态站(blog):文件不在磁盘上,nginx 转发给真正干活的应用:

location / {
    proxy_pass http://127.0.0.1:8091;         # Halo 应用(容器只绑回环地址)
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

那几行 proxy_set_header 不是仪式:应用在容器里看到的连接全部来自 127.0.0.1,不传 X-Real-IP 它就以为所有访客都是本机;不传 X-Forwarded-Proto,应用生成的跳转链接可能是 http://,又是一轮 301。反代的本质是替后端转述「真实的世界长什么样」。

6.5 响应:状态码与缓存头

请求处理完,响应带着三样东西回来:状态码、响应头、响应体。状态码只记三类高频:200 找到了(curl 实拍里 /knowledge 的响应)、301 永久搬家(本站 80→443、无尾斜杠补齐都用它)、404 没有这个资源(try_files 的 =404 兜底)。5 开头的两兄弟要分清:502 是 nginx 敲应用的门没人应(应用挂了、端口不对),504 是应了但超时没答复(应用活着但太慢)——502 查应用死活,504 查应用为什么慢,排错方向完全不同。

缓存头决定浏览器和中间节点下次还问不问。notes 站的真实分层策略:HTML 用 no-cache(每次发布立即生效,访问者永不看到旧页面),带内容哈希的构建产物 /_astro/ 用 30 天强缓存(文件名里带哈希,内容变则文件名必变,缓存永远不会错):

< cache-control: no-cache                    ← HTML:可缓存但必须回源验证
< expires: Tue, 01 Dec 2026 12:29:55 GMT     ← /_astro/xxx.HASH.webp:30 天强缓存
< cache-control: max-age=2592000

这套「HTML 不缓存、哈希资源长缓存」是静态站发布系统的标准答案,任何一次发布都即时生效,同时老访客的二次加载几乎全走本地缓存。

6.6 排错清单:现象 → 先查什么

  • 改了 DNS 不生效:先 dig +short 域名 看解析到哪;没变是缓存 TTL 没到,变了还不对是浏览器缓存(换无痕窗口排除)。
  • 证书报错但域名对:curl -v 看 subject: 是不是你要的域名——多站点服务器上 SNI 选错 server 块会递错证书;再看 certbot certificates 的有效期。
  • 502 Bad Gateway:应用没起来或端口不对。动态站先 curl 127.0.0.1:8091 从服务器本地敲应用的门,通了再查 nginx 配置。
  • 504 Gateway Timeout:应用活着但太慢,别盯 nginx,去查应用的慢 SQL 和外部依赖。
  • 改了配置没生效还一切正常:你看过的一直是 301 跳转后的缓存。curl -v 直接看响应头里的 location:,别信浏览器地址栏。
  • 本地开发连不上线上接口:先 ping(看 IP 通不通)再 curl -v(看卡在哪一段)—— Trying 后无响应是安全组/防火墙,Connected 后卡住是服务端慢。

6.7 本章小结

段 干什么 一条命令亲眼看
DNS 域名 → IP,层层缓存 dig +short 域名
TCP 三次握手建立连接 curl -v 的 Trying/Connected
TLS 验证书、协商密钥(SNI 选站点) curl -v 的 SSL connection/subject
nginx 分发 server_name 匹配;静态 try_files / 动态 proxy_pass cat 服务器上的两份 conf
应用 容器里真干活的服务 docker ps 看端口映射
响应 状态码 + 缓存头 curl -sI 网址

这张地图的价值不在面试,在排错:知道请求卡在哪一段,就知道该去哪台机器看哪份日志。下面三个练习把地图变成手感。

6.8 练习题

  1. 对你自己的任意一个常用网站跑 curl -v -o /dev/null,把输出按 6.3 的十行拆解一遍:TLS 版本、证书发给谁、谁签发的、HTTP 几、缓存头是什么。
  2. 用 dig +short 分别查两个域名解析到同一个 IP 的站点(大厂很常见),再用浏览器开发者工具的 Security 面板确认它们证书不同——亲手验证一次 SNI。
  3. 在本地起一个 python3 -m http.server 8000,再 curl -v http://127.0.0.1:8000:对照本章地图认出每一段——这是没有 DNS 和 TLS 的最短旅程,缺的那两段为什么本地可以省?
  4. 故意把 hosts 里某域名指到一个不存在的 IP,观察浏览器报错停在旅程的哪一段;改回来再清缓存,体会「排错先定段」。

6.9 延伸读