Skip to content

Домены

Фреймворк поддерживает виртуальные хосты — несколько доменов на одном сервере (или несколько серверов на одном ip:port). Домены задаются массивом строк и могут быть точными именами, содержать подстановочные знаки * или полноценные регулярные выражения.

json
"servers": {
    "s1": {
        "domains": [
            "example.com",
            "www.example.com",
            "*.example.com",
            "mail.*",
            "(api|www).example.com"
        ],
        "ip": "0.0.0.0",
        "port": 80,
        ...
    },
    ...
}

Как происходит сопоставление

Виртуальный сервер выбирается по значению HTTP-заголовка Host. Перед сравнением выполняется нормализация:

  1. Порт отсекается — из example.com:8080 остаётся example.com.
  2. IPv6-литералы обрабатываются по RFC 3986: скобки [::1]:8080 снимаются, для сравнения берётся сам адрес ::1.
  3. IDN-преобразование — домен в UTF-8 (например, пример.рф) конвертируется в Punycode (xn--80akhbyknj4f.xn--p1ai), см. Интернационализированные домены (IDN).
  4. Регистр не учитывается — по RFC 9110 §4.2.3 имя хоста нечувствительно к регистру, поэтому Host: EXAMPLE.COM попадает на сервер с доменом example.com. Складывается только ASCII-регистр: к моменту сравнения имя уже в Punycode, и локаль запущенного процесса на выбор сервера не влияет.

Затем значение поочерёдно сравнивается с доменами каждого сервера, привязанного к тому же ip:port. Совпало первым — выиграло: приоритет между точным именем, wildcard и регулярным выражением отсутствует, порядок определяется только последовательностью в массиве domains. Поэтому более специфичные имена следует размещать выше общих.

Порядок имеет значение

Поскольку *.example.com захватывает и www.example.com, и более глубокие адреса, размещайте точные имена и узкие шаблоны выше wildcard, чтобы они не «перекрывались».

Если подходящий сервер не найден, клиент получит 404 Not Found. Пустой или отсутствующий заголовок Host (он обязателен в HTTP/1.1) приводит к 400 Bad Request.

В HTTP/2 и HTTP/3 ту же работу выполняет псевдо-заголовок :authority, и результат намеренно тот же: имя, которого этот слушатель не обслуживает, получает 404, а не ошибку потока или соединения. Запрос не на то имя — обычный промах, и клиент должен узнавать об этом одинаково, какой бы версией протокола он ни говорил. Для HTTP/3 у таких промахов есть отдельный счётчик, /metricshttp3.misdirected, не смешанный с 404, которые возвращает обработчик: его рост означает либо клиентов, обращающихся не по тому имени, либо список domains, в котором не хватает имени, реально используемого клиентами.

Подстановочные имена

Звёздочка * допускается только в начале или в конце имени и раскрывается в регулярное выражение .*:

json
"domains": [
    "*.example.com",
    "mail.*"
]
  • *.example.com матчит как www.example.com, так и www.sub.example.com — звёздочка захватывает сразу несколько уровней.
  • mail.* матчит mail.com, mail.org и тому подобное.
  • Имена вида w*.example.com или www.*.example.org недопустимы — звёздочка в середине вызывает ошибку загрузки конфигурации. Такие случаи реализуются регулярным выражением (см. ниже).

Внутри скобочных групп (...) и [...] символы * и . сохраняют regex-смысл (квантификатор и «любой символ» соответственно) и не подвергаются автопреобразованию.

Регулярные выражения

В качестве имени домена можно использовать регулярное выражение, совместимое с PCRE (Perl Compatible Regular Expressions):

json
"domains": [
    "(api|www).example.com",
    "^www(\\d+).example.net$",
    "(.1|.*|a3).example.com"
]

Особенности обработки:

  • Точки экранируются автоматически. Символ . вне скобок превращается в \., поэтому example.com можно писать как есть, без экранирования. Внутри (...) / [...] . сохраняет смысл «любой символ».
  • Автоматическое якорение. Если выражение не содержит ни ^ в начале, ни $ в конце, шаблон автоматически оборачивается в ^...$ (полное совпадение). Указав хотя бы один из якорей, вы полностью отключаете авто-якорение.
  • Регистр не учитывается. Выражения компилируются с флагом PCRE_CASELESS, поэтому [a-z] в шаблоне совпадёт и с заглавными буквами, а (API|WWW).example.com примет api.example.com. То же правило действует и для точных имён — см. Как происходит сопоставление.

Интернационализированные домены (IDN)

Поддержка кириллических и иных национальных доменов реализована через libidn2. Домены в конфигурации можно задавать в UTF-8 — при загрузке они конвертируются в Punycode, и то же преобразование применяется к заголовку Host (и TLS SNI) во время сравнения:

json
"domains": [
    "пример.рф",
    "*.пример.рф"
]

Если Host содержит некорректный IDN, который невозможно преобразовать, клиент получит 404 Not Found.

TLS и SNI

На TLS-соединениях виртуальный сервер первоначально выбирается по SNI (Server Name Indication). Как SNI, так и последующий заголовок Host проходят IDN-преобразование и сравниваются с одними и теми же шаблонами.

Согласно RFC 9110, на TLS-соединении с SNI заголовок Host обязан соответствовать серверу, выбранному по SNI. Если они расходятся — клиент получит 404 Not Found.

Краткая справка

СитуацияОтвет
Пустой или отсутствующий Host (HTTP/1.1)400 Bad Request
Ни один домен не совпал404 Not Found
Host не соответствует серверу, выбранному по SNI (TLS)404 Not Found
Некорректный IDN в Host404 Not Found

Выпущено под лицензией MIT.