# теория · шаг 2 из 5
map: таблица вместо условия
Коротко
map - таблица «значение на входе, значение на выходе», которая считается
один раз при первом обращении и кешируется до конца запроса. Точные значения
всегда сильнее регулярок, где бы те ни стояли. Регистронезависимость ~*
работает только на латинице. После перезаписи URI кешированная карта отдаёт
старое значение - если это мешает, ставят volatile.
Пишешь карты каждый день - листай до «Теперь сам». Разборы про порядок поиска и про значение, застрявшее до перезаписи.
Регулярка написана выше точного значения. Что вернётся, если на вход придёт
ровно abc?
map $http_x_ord $ord {
"~^abc$" "регулярка";
"abc" "точное";
default "-";
}
Порядок строк подсказывает «регулярка». На стенде возвращается точное.
На что это похоже
Карта - это не цепочка if, читаемая сверху вниз, а словарь с двумя
отделениями. Сначала nginx смотрит в отделение точных ключей - там поиск по
хеш-таблице, порядок записи роли не играет. Только если ключ не нашёлся,
он идёт во второе отделение и там уже перебирает регулярки сверху вниз.
Поэтому «переставить строки» ничего не меняет между отделениями и меняет всё
внутри второго.
Механизм: порядок поиска
Правило
Точное значение бьёт любую регулярку. Между регулярками выигрывает написанная
выше. Не нашлось ничего - берётся default, а без него пустая строка. Значение
карты вычисляется один раз за запрос и запоминается; volatile отменяет
запоминание.
Разбор: регистр и кириллица
Две одинаковые по замыслу карты, замер на шести значениях:
map $http_x_key $lat { default "нет"; "~*^BIG$" "поймала"; }
map $http_x_key $cyr { default "нет"; "~*^БОЛЬШИЕ$" "поймала"; }
| на входе | латинская карта | кириллическая |
|---|---|---|
BIG |
поймала | нет |
big |
поймала | нет |
Big |
поймала | нет |
БОЛЬШИЕ |
нет | поймала |
большие |
нет | нет |
Латиница складывается по регистру во всех трёх написаниях. Кириллица - только
при точном совпадении байтов: большие не поймалось. Причина в том, что
регулярки компилируются без режима юникода, и складывание регистра работает
для однобайтовых букв. Правило шире карт: то же самое верно для location ~*
и для условий if ($var ~* ...). Русский текст в регулярках сравнивают либо
точно, либо перечисляя оба написания.
Разбор: значение, застрявшее до перезаписи
map $uri $кеш { default "прочее"; "/шаг1" "шаг1"; "/шаг2" "шаг2"; }
map $uri $volatil { volatile; default "прочее"; "/шаг1" "шаг1"; "/шаг2" "шаг2"; }
location /шаг1 {
set $снимок "$кеш/$volatil"; # здесь обе переменные вычисляются
rewrite ^ /шаг2 last;
}
location /шаг2 { return 200 "кеш=$кеш volatile=$volatil\n"; }
Ответ на запрос /шаг1:
кеш=шаг1 volatile=шаг2
Обычная карта вернула значение, посчитанное до перезаписи, - хотя URI давно
другой. Кеш существует ради скорости: карта на тысячу строк не должна
считаться заново на каждое упоминание переменной. Но если исходная переменная
за время обработки меняется - $uri после rewrite, $remote_addr после
realip, - нужен volatile.
Тонкость, из-за которой это редко замечают: строка set в примере поставлена
специально. Без неё до перезаписи переменную никто не читал бы, кешировать
было бы нечего, и обе карты вернули бы одинаково.
Что ломается без этого
.имяи*.имя- разные маски. Приhostnamesзапись.wild.orgловит иwild.org, иa.wild.org;*.star.orgловит только поддомен, а самstar.orgуходит вdefault. Замер подтверждает оба случая.- Переменная в
proxy_passотменяет подстановку хвоста. Полный замер на запросе/x/cart?q=1:
| запись | что увидел бэкенд |
|---|---|
proxy_pass http://$куда; |
/x/cart?q=1 |
proxy_pass http://$куда/; |
/ |
proxy_pass http://$куда/префикс/; |
/префикс/ |
proxy_pass http://$куда$uri; |
/x/cart - параметры потеряны |
proxy_pass http://$куда$request_uri; |
/x/cart?q=1 |
Без переменной хвост дописывается сам, с переменной - нет. Отсюда рецепт:
адрес с переменной пишут либо совсем без пути, либо с $request_uri в конце.
- Карта не знает, откуда взялось значение. map $http_x_forwarded_for ...
разбирает то, что прислал клиент, и подделывается тривиально. Адрес берут
через realip, а не через карту.
Зачем это в работе
Карта - штатный способ развести поведение по признаку, не заводя условий. Классика - апгрейд соединения для WebSocket:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Ровно то же годится для выбора бэкенда, срока жизни кеша, признака «пускать ли
в бету». Родственница карты - geo, та же таблица, но ключ - адрес клиента:
geo $зона {
default чужой;
127.0.0.0/8 локальный;
172.16.0.0/12 докер;
}
Замер со стенда: запрос с 172.25.0.1 дал зона=докер. Дальше эту переменную
можно класть в ключ лимита, в условие или в пустой ключ - чтобы свои адреса
лимит не считал вовсе.
Теперь сам
Карта должна отдавать да на любой заголовок, начинающийся с Bot или бот
в любом регистре. Найди в ней две ошибки.
map $http_user_agent $бот {
"~*^бот" "да";
"Bot" "да";
default "нет";
}
Ответ: первая строка не поймает Бот и БОТ - складывание регистра на
кириллице не работает, нужно перечислить написания или сравнивать точно.
Вторая строка - точное значение, то есть сработает только на заголовке,
равном ровно Bot, а не начинающемся с него; нужна регулярка ~*^Bot.
Главное
Точное значение в карте всегда сильнее регулярки, порядок строк важен только
среди регулярок. ~* складывает регистр на латинице и не складывает на
кириллице. Значение кешируется на запрос: если исходная переменная меняется по
дороге, нужен volatile. Переменная в proxy_pass отменяет подстановку
хвоста - его дописывают через $request_uri.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий