Немного об использовании regex в map nginx

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Давно ничего не писал, поэтому разбавим конец пятницы простыми, но не всегда очевидными иcканиями в Nginx.

В этом веб-сервере есть замечательная директива map, которая позволяет существенно упростить и сократить конфиги. Суть директивы в том, что она позволяет создать новую переменную, значение которой зависит от значений одной или нескольких исходных переменных. Ещё большую силу директива приобретает при использовании регулярных выражений, но при этом забывается, об одном важном моменте. Выдержка из мануала:

Поскольку переменные вычисляются только в момент использования, само по себе наличие даже большого числа объявлений переменных map не влечёт за собой никаких дополнительных расходов на обработку запросов.

И здесь важным является не только, то что "map не влечёт за собой никаких дополнительных расходов на обработку запросов", а и то, что "переменные вычисляются только в момент использования".

Как известно, конфиг Nginx, в основном, декларативен. Это касается и директивы map, и, не смотря на то, что она расположена в контексте http, её вычисление не происходит до момента обработки запроса. То есть при использовании результирующей переменной в контекстах server, location, if и т.п. мы "подставляем" не готовый результат вычисления, а лишь "формулу" по который этот результат будет вычислен в нужный момент. В этой конфигурационной казуистике не возникает проблем до того момента, пока мы не используем регулярные выражения. А именно регулярные выражения с выделениями. А ещё точнее, регулярные выражения с неименованными выделениями. Проще показать на примере.

Допустим у нас есть домен example.com с множеством поддоменов 3-го уровня, а-ля ru.example.com, en.example.com, de.example.com и т.д., и мы хотим их перенаправить на новые поддомены ru.example.org, en.example.org, de.example.org и т.п. Вместо того чтобы описывать сотни строк редиректов мы поступим вот так:

map $host $redirect_host {
  default "example.org";
  "~^(\S+)\.example\.com$"  $1.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
  location / {
        rewrite ^(.*)$ https://$redirect_host$1 permanent;
    
}

Здесь мы ошибочно ожидали, что при запросе ru.example.com произойдет вычисление регулярки в map и, соответственно, попав в location переменная $redirect_host будет содержать значение ru.example.org, однако на деле это не так:

$ GET -Sd ru.example.com
GET http://ru.example.com
301 Moved Permanently
GET https://ru.example.orgru

Оказалось, что на момент исполнения запроса наша переменная равна ru.example.orgru. Всё из-за того, что мы пренебрегли предупреждением "переменные вычисляются только в момент использования", а в нашем rewrite оказалась некая регулярка вложенная в регулярку.

Самый простой вариант решения - не использовать regexp одновременно и в map и в месте вычисления переменной, например, для данного конкретного случая это может выглядеть так:

map $host $redirect_host {
  default "example.org";
  "~^(\S+)\.example\.com$"  $1.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
    location / {
        return 301 https://$redirect_host$request_uri;
    }
}

Но что делать, если альтернативного решения без регулярок нет (ну или просто очень хочется).
Пробуем использовать именованные выделения в map:

map $host $redirect_host {
  default "example.org";
  "~^(?<domainlevel3>\S+)\.example\.com$"  $domainlevel3.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
    location / {
        rewrite ^(.*)$ https://$redirect_host$1 permanent;
    }
}

Попытка не увенчалась успехом:

$ GET -Sd ru.example.com
GET http://ru.example.com
301 Moved Permanently
GET https://ru.example.orgru

так как наше неименованное выделение $1 получит результат именованного $domainlevel3. То есть необходимо использовать именованные выделения в обеих регулярках:

map $host $redirect_host {
  default "example.org";
  "~^(?<domainlevel3>\S+)\.example\.com$"  $domainlevel3.example.org;
}
server {
    listen       *:80;
    server_name  .example.com;
    location / {
        rewrite ^(?<requri>.*)$ https://$redirect_host$requri permanent;
    }
}

И теперь всё работает как ожидалось:

$ GET -Sd ru.example.com
GET http://ru.example.com
301 Moved Permanently
GET https://ru.example.org/
Источник: https://habr.com/ru/post/558692/


Интересные статьи

Интересные статьи

Решения для больших компаний обычно должны выдерживать высокие нагрузки. Когда в штате много десятков тысяч человек, и значительная доля из них ежедневно пользуются ...
Все знают про Сизифа. Если кто не знает - тому сейчас должно быть стыдно. Сизиф это персонаж греческой мифологии, и в силу этой своей особоузкой специализации, мог бы ост...
Эта небольшая история — живое свидетельство того, как самые простые решения (иногда) могут оказаться очень эффективными. В одном из проектов руководство взяло курс на опт...
Я давно знаком с Битрикс24, ещё дольше с 1С-Битрикс и, конечно же, неоднократно имел дела с интернет-магазинами которые работают на нём. Да, конечно это дорого, долго, местами неуклюже...