Глава 8 / 51
Том 2 · Конфигурация и сборка

xs.json подробно · уровень хоста и vhost

Несколько сайтов в одном server: host_default подстраховывает все несовпавшие запросы, hosts[] точно разводит трафик по доменам. Глава объясняет три источника маршрутизации и раздачу сертификатов по хостам.

Обзор

Позиционирование: хост — носитель vhost, единица раздачи сертификатов и единица исполнения скрипта. После этой главы практика vhost из главы 20 — лишь масштабирование конфигурации.

8.1 Введение

Много сайтов на одной машине — обычное требование: основной сайт, API, админ-панель и несколько клиентских площадок в одном процессе. Модель XServer — два уровня: server (эндпоинт) × host (сайт): один server слушает порт, каждый запрос маршрутизируется к хосту по принципу «какому сайту он адресован»; у каждого хоста свои корень статики, скрипт и сертификаты.

порт 80/443 (server) │ разводка по доменам ┌──────────┼──────────┬─────────────┐ ▼ ▼ ▼ ▼ host A host B host C host_default a.com b.com c;d.com (откат, существует всегда) wwwroot-a wwwroot-b script-c сайт-подстраховка cert-a cert-b cert-c

8.2 Поля уровня хоста

ПолеОграничениеОписание
nameуникально внутри server, непустоеимя хоста; повторы / пустое значение — 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_keyпути PEMсертификаты уровня хоста → источник данных таблицы SNI (см. 8.4)

Один домен привязывается ровно к одному хосту (двойная привязка — fail-fast); один хост может держать несколько доменов (через точку с запятой, сопоставление по правилам регистра доменов).

8.3 Три источника маршрутизации

«Целевой хост» HTTP-запроса может стоять в трёх местах; XServer опрашивает их по порядку:

ИсточникФормаТиповой сценарий
Заголовок HostHost: 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; смена Host внутри keep-alive-соединения тоже маршрутизируется корректно на каждый запрос
  • На TLS-эндпоинте выбор личности происходит в рукопожатии (SNI, следующий раздел); маршрутизация HTTP-слоя идёт после расшифровки — слои согласованы

8.4 Сертификаты уровня хоста и SNI

Каждый http/ws-хост может нести свои tls_ca/tls_cert/tls_key. При сборке они попадают в таблицу SNI; в TLS-рукопожатии клиент называет целевой домен в ClientHello (SNI), сервер по нему выбирает сертификат — сопоставление домена и сертификата завершается в рукопожатии, до начала HTTP-запроса. Серверная cookie привязывает выбор личности из ClientHello и Accept к одному поколению, устраняя гонки рукопожатия между поколениями.

Сертификаты заменяются на ходу: при reload, где сравнение на уровне конфигурации не изменилось (эндпоинты/структура те же), срабатывает путь горячей замены сертификатов — атомарная подмена таблицы под блокировкой; старые личности защищены ссылками до конца рукопожатий в полёте. Форматы сертификатов и требования к парам — в главе 12.

8.5 Контракт

  • Маршрутизация: три источника Host → точное совпадение в hosts[] → откат в host_default; HTTP/1.1 без Host — 400.
  • Сертификаты: сертификаты уровня хоста раздаются по SNI; хост без сертификатов не сможет завершить выбор личности рукопожатия на TLS-эндпоинте.
  • Скрипты: у каждого хоста с devlang=c свой независимый рантайм скрипта — перезагрузка публикуется полным поколением server этого хоста.

Частые ошибки

Представление, что host — это «сопоставление по префиксу»
Симптом
Настроили "api.com" и ждёте, что "www.api.com" тоже попадёт — а он уходит в default.
Причина
Привязка host — точное совпадение (список через точку с запятой сверяется целиком); ни wildcards, ни префиксов.
✗ Ошибочное ожидание
"host": "api.com"
/* ждём, что www.api.com тоже попадёт */
✓ Правильно
"host": "api.com;www.api.com"
В hosts[] поле host оставили пустым
Симптом
Старт падает: для http/ws с включённым vhost поле host обязательно непустое.
Причина
Пустой домен не имеет смысла маршрутизации — «ловить всё» — работа host_default, а не пустого host.

Упражнения

БазовыйДва сайта на одном порту

Настройте один server с host_default (сайт А) + hosts[] (сайт Б, привязан к test.local); в двух wwwroot положите разные index.html.

Приёмка: curl -H "Host: test.local" и запрос без заголовка Host попадают каждый в свой сайт.
ПродвинутыйПроверка трёх источников

Через --resolve curl и absolute-form (curl -x http://127.0.0.1:8080 http://test.local/, форма прокси) вызовите источники «заголовок Host» и «absolute-form».

Приёмка: обе формы попадают в один хост; без всякой информации о личности — 400 (HTTP/1.1 требует Host).
ВызовСмена сайта внутри keep-alive

Напишите скрипт, отправляющий по одному keep-alive-соединению два запроса с разными Host, и проверьте маршрутизацию каждого запроса.

Приёмка: запросы попадают каждый в свой сайт при переиспользовании соединения (один локальный порт).

Итоги

ТезисВывод
Двухуровневая модельserver = эндпоинт; host = сайт (корень статики/скрипт/сертификаты)
МаршрутизацияТри источника: Host / absolute-form / authority-form, точное совпадение
Откатhost_default существует всегда; несовпавшее уходит туда
СертификатыУровень хоста → таблица SNI, раздача в рукопожатии; горячая замена
Что дальше по xrt