# теория · шаг 1 из 4
Четыре формы имени и их приоритет
Коротко
Среди блоков, прошедших отбор по адресу и порту, сайт выбирается по заголовку
Host. Путь на выбор не влияет.
- Четыре формы имени с жёстким порядком: точное → маска
*.имя→ маскаимя.*→ регулярка. - Регулярка проигрывает ЛЮБОЙ маске, хотя выглядит самой точной. Это главная ловушка урока.
- Среди масок побеждает самая длинная, а не написанная первой.
*.shop.localне совпадает с самимshop.local- отсюда «с www работает, без www нет».- Скобочные группы регулярки становятся переменными: на этом делают мультитенантность (один блок на много клиентов) в три строки.
Если порядок «точное, маска, маска, регулярка» помнишь наизусть - листай до «Теперь сам».
Сначала ответь сам
На сервере два блока:
server { listen 80; server_name ~^api\d+\.shop\.local$; return 200 "API\n"; }
server { listen 80; server_name *.shop.local; return 200 "сайт\n"; }
Запрос приходит на api7.shop.local. Кто ответит?
Инстинктивный ответ - блок с регуляркой: он написан первым, он точнее, он
буквально про api7. Настоящий - «сайт». Регулярка не сработает никогда,
и блок API мёртв с момента написания.
На что это похоже
Представь сортировку писем в большом здании.
Сначала ищут точный адрес - фамилию на конверте. Нашли - письмо ушло, дальше никто не смотрит. Не нашли - смотрят на отдел: «всё, что для бухгалтерии». Если таких правил несколько, берут самое узкое: «для бухгалтерии второго этажа» точнее, чем «для бухгалтерии».
И только когда не сработало ничего из перечисленного, конверт попадает к человеку, который читает адрес целиком и разбирается по смыслу. Он умнее всех предыдущих - и именно поэтому стоит последним: его работа дороже, и трогают его в последнюю очередь.
Регулярка в nginx - тот самый человек. Не потому что слабее, а потому что дороже.
Механизм: четыре ступени и порядок между ними
server_name shop.local; # 1. точное имя
server_name *.shop.local; # 2. маска в начале
server_name shop.*; # 3. маска в конце
server_name ~^shop\d+\.local$; # 4. регулярное выражение
Это четыре варианта для разных блоков, а не один блок. В регулярке ~ в
начале - знак «дальше регулярка», ^ - начало имени, \d+ - одна или больше
цифр, \. - буквальная точка, $ - конец имени. Звёздочка в маске заменяет
одну или несколько частей имени - кусков между точками.
Внутри второй и третьей ступени решает длина. Для img.cdn.shop.local маска
*.cdn.shop.local победит *.shop.local, потому что длиннее, - независимо от
того, какая написана выше.
Правило
Точное имя → маска *.имя → маска имя.* → регулярка. Среди масок побеждает
самая длинная, среди регулярок - первая по порядку в конфиге. Любая маска
сильнее любой регулярки.
Разбор: шесть запросов и живой ответ
Собрал конфиг, где блоки написаны в «неудобном» порядке - регулярка сверху, точное имя снизу, - и прошёл по нему запросами. Проверено на nginx 1.31.5.
server { listen 80; server_name ~^api\d+\.shop\.local$; return 200 "4-regex\n"; }
server { listen 80; server_name shop.*; return 200 "3-tail\n"; }
server { listen 80; server_name *.shop.local; return 200 "2-wild\n"; }
server { listen 80; server_name *.cdn.shop.local; return 200 "2-wild-long\n"; }
server { listen 80; server_name shop.local; return 200 "1-exact\n"; }
for h in shop.local www.shop.local img.cdn.shop.local api7.shop.local shop.test nomatch.example.com; do printf '%-22s -> ' $h; curl -s -H "Host: $h" localhost/; done
shop.local -> 1-exact
www.shop.local -> 2-wild
img.cdn.shop.local -> 2-wild-long
api7.shop.local -> 2-wild
shop.test -> 3-tail
nomatch.example.com -> 4-regex
Три строки стоит разобрать отдельно.
img.cdn.shop.local подошёл под обе маски, победила длинная - хотя написана
она ниже.
api7.shop.local ушёл в маску, а не в регулярку. Это и есть ответ на вопрос из
начала урока: блок API недостижим.
nomatch.example.com не подошёл никуда и всё равно получил ответ - от блока с
регуляркой. Не потому, что регулярка совпала, а потому что этот блок написан
первым на своём порту. Об этом следующая глава, и это самое неприятное в теме.
Разбор: где наивное правило подводит
Наивное правило: «*.shop.local - это про весь домен».
1-exact
Убери из конфига блок с точным именем - и shop.local перестанет попадать
куда надо: замер отправил его в 3-tail, маску shop.*. Звёздочка заменяет
одну или несколько частей имени, но не пустоту. *.shop.local совпадает с www.shop.local и a.b.shop.local и не
совпадает с shop.local.
Отсюда самый частый симптом в теме: сайт открывается с www и не открывается
без него, или наоборот. Лечится перечислением, а не звёздочкой:
server_name shop.local www.shop.local;
Имена разделяют пробелом, перечислять можно сколько угодно.
Второе, что стоит знать про регулярки: раз они дороги, брать их надо ради того, чего маска не умеет, - запоминать часть имени.
server {
listen 80;
server_name ~^(?<tenant>[a-z]+)\.tenants\.local$;
root /var/www/tenants/$tenant;
add_header X-Tenant $tenant always;
}
(?<tenant>…) - скобочная группа с именем: всё, что совпало с [a-z]+ (одна
или больше латинских букв), попадает в переменную $tenant. Заголовок
X-Tenant добавлен, чтобы увидеть её значение:
curl -sI -H "Host: a.tenants.local" localhost/ | grep X-Tenant
X-Tenant: a
Один блок обслуживает сколько угодно клиентов, и добавление нового - это
создание каталога /var/www/tenants/имя с index.html, а не правка конфига
с перезагрузкой. Пока каталога нет, nginx отвечает 404 - замер на
b.tenants.local.
Что ломается без этого
- Блок с регуляркой никогда не срабатывает. Проверь, нет ли маски, которая накрывает те же имена. Маска победит.
- Сайт открывается с
wwwи не открывается без него. Вserver_nameтолько маска; допиши точное имя. - Запрос уходит не в тот блок из двух похожих. Сравни длину масок: решает она, а не порядок строк.
nginx -tв порядке, а сайт отдаёт чужие страницы.Hostне совпал ни с одним именем, и ответил первый блок на порту.
Зачем это в работе
Отладка тут устроена приятно: Host - обычный заголовок, и его можно
подставить руками, не трогая DNS (систему, которая превращает имя в адрес) и
не дожидаясь, пока новая запись в ней разойдётся по миру.
Так проверяют новый сайт до того, как на него направлен домен, и так же ловят «блок не срабатывает»: подставляешь имя по очереди и смотришь, кто ответил. Чтобы ответивший блок был виден сразу, в него на время добавляют метку:
add_header X-Block "shop.local" always;
curl -sI -H "Host: api7.shop.local" localhost/ | grep X-Block
На удалённом сервере вместо localhost пишут его адрес.
Это тот же приём, что из раздела про переменные, и на десятке похожих блоков он экономит больше всего времени.
Теперь сам
1. Нужно, чтобы shop.local и все его поддомены вели на один сайт. Как
написать server_name?
server_name shop.local *.shop.local;
Обе формы в одной строке. Маска не покрывает голое имя, поэтому оно пишется отдельно.
2. В конфиге две регулярки, обе подходят под api7.shop.local. Какая
сработает?
Первая по порядку в собранном конфиге. Внутри четвёртой ступени длина уже не
считается - только место. А собранный порядок зависит от порядка include,
то есть от имён файлов по алфавиту: это уже знакомая связка из раздела про
конфиг.
3. Почему server_name не влияет на выбор, если запрос пришёл на порт
8080, а блок слушает 80?
Потому что отбор по адресу и порту идёт раньше. Блок с чужим портом выбывает до того, как имена вообще начинают сравнивать.
Главное
Сайт выбирается по Host, ступени идут строго сверху вниз: точное имя, маска
*.имя, маска имя.*, регулярка. Любая маска сильнее любой регулярки, среди
масок побеждает самая длинная, а *.shop.local не покрывает сам shop.local.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий