第 8 章 / 共 51 章
卷二 · 配置与服务装配

xs.json 全解 · 主机级与 vhost

一个 server 装多个站点:host_default 兜底一切未命中的请求,hosts[] 按域名精确分流。本章讲清路由的三个来源与证书的按 host 分发。

本章导读

站位:host 是 vhost 的载体、证书的分发单位、脚本的运行单元。理解本章后,第 20 章的 vhost 实战只是配置规模的放大。

8.1 引入

单机多站是服务器的常态需求:主站、API、管理后台、若干客户站点共用一个进程。XServer 的模型是server(端点)× host(站点)两级:一个 server 监听端口,收到的请求按"它是给哪个站点的"路由到 host;每个 host 有独立的静态根、脚本、证书。

端口 80/443(server) │ 按域名分流 ┌──────────┼──────────┬─────────────┐ ▼ ▼ ▼ ▼ host A host B host C host_default a.com b.com c;d.com (未命中回落,恒存在) wwwroot-a wwwroot-b 脚本-c 兜底站点 cert-a cert-b cert-c

8.2 主机级字段

字段约束说明
nameserver 内唯一、非空主机名;重复 / 空都 fail-fast
host分号分隔域名http/ws 的启用 vhost 必填非空;如 "a.com;www.a.com"
path目录须存在静态根;缺失 fail-fast
devlang / devfile—c / static / 自定;static 则 devfile 可空
tls_ca / tls_cert / tls_keyPEM 路径host 级证书 → SNI 表数据源(见 8.4)

一个域名只能绑在一个 host 上(重复绑定 fail-fast);一个 host 可绑多个域名(分号分隔,大小写不敏感按域规则处理)。

8.3 路由三来源

HTTP 请求里"目标主机"可能出现在三个位置,XServer 依次取用:

来源形态典型场景
Host 头Host: a.com:8080日常绝大多数请求(端口后缀被剥离)
absolute-formGET http://a.com/x HTTP/1.1代理型客户端
authority-formCONNECT a.com:443隧道请求
  • HTTP/1.1 强制 Host:缺失或重复都按协议错误处理(400)——这是行为正确性,不是可选校验
  • 未命中任何 hosts[] → 回落 host_default;keep-alive 连接内切换 Host 也能逐请求正确路由
  • TLS 端点上的身份选择发生在握手里(SNI,见下节),HTTP 层路由在解密后进行——两层一致

8.4 host 级证书与 SNI

每个 http/ws host 可以有自己的 tls_ca/tls_cert/tls_key。装配时它们进入 SNI 表;TLS 握手中客户端在 ClientHello 里带上要访问的域名(SNI),服务端据此选出对应证书——域名与证书的匹配在握手期完成,HTTP 请求还没开始。服务端 Cookie 机制把 ClientHello 的身份选择与 Accept 绑定到同一代,避免握手跨代的竞态。

证书可热替换:reload 时配置级比对未变(端点/结构一致)则走证书热替换路径——锁内原子换表,旧 identity 由引用保护到在途握手结束。证书格式与配对要求见第 12 章。

8.5 契约

  • 路由线:Host 三来源 → hosts[] 精确匹配 → host_default 回落;HTTP/1.1 无 Host 即 400。
  • 证书线:host 级证书按 SNI 分发;未配置证书的 host 在 TLS 端点上无法完成握手身份选择。
  • 脚本线:每个 devlang=c 的 host 一份独立脚本运行时——重载以 host 所属 server 的完整 generation 为发布单元。

常见错误

以为 host 是"前缀匹配"
症状
配了 "api.com" 期望 "www.api.com" 也命中,结果进了 default。
原因
host 绑定是精确匹配(分号列表逐个比对),不做通配与前缀。
✗ 错误预期
"host": "api.com"
/* 期望 www.api.com 也进来 */
✓ 正确
"host": "api.com;www.api.com"
hosts[] 写了 host 字段却留空
症状
启动失败:http/ws 启用 vhost 的 host 字段必填非空。
原因
空域名的 vhost 没有路由意义——想"全收"应该用 host_default,而不是空 host。

编程练习

基础一端口两站点

单 server 配 host_default(站点甲)+ hosts[](站点乙,绑 test.local),两个 wwwroot 各放不同 index.html。

验收:curl -H "Host: test.local" 与不带 Host 头各命中一个站点。
进阶三来源验证

用 curl 的 --resolve 与 absolute-form(curl -x http://127.0.0.1:8080 http://test.local/代理形态)分别触发 Host 头与 absolute-form 两种路由来源。

验收:两种形态命中同一 host;去掉所有身份信息得到 400(HTTP/1.1 强制 Host)。
挑战keep-alive 内切换站点

写脚本在一条 keep-alive 连接上连续发两个不同 Host 的请求,验证逐请求路由正确。

验收:两个请求分别命中各自站点且连接复用(同一本地端口)。

小结

要点结论
两级模型server = 端点;host = 站点(静态根/脚本/证书)
路由Host 头 / absolute-form / authority-form 三来源,精确匹配
回落host_default 恒存在,未命中即进它
证书host 级 → SNI 表,握手期分发;可热替换
xrt 延伸阅读