1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 3 из 6

rewrite: флаги и ловушки

Коротко

rewrite заменяет URI целиком - несовпавший хвост не дописывается сам. Флаг решает, что дальше: last отправляет искать location заново, break остаётся здесь и заодно обрывает остальные директивы фазы, включая return после себя. Замена, начинающаяся со схемы, отвечает клиенту редиректом даже без флага. Десять перезаписей проходят, одиннадцатая даёт 500.

Флаги знаешь - листай до «Теперь сам». Разборы про то, почему break отдал 404 и куда делся хвост адреса.

Что вернётся на запрос /break/файл?

location /break/ {
    rewrite ^/break/(.*)$ /цель/$1 break;
    return 200 "остались здесь\n";
}

Кажется, что «остались здесь»: break не уводит из location, а return стоит следующей строкой. На стенде приходит 404.

На что это похоже

last и break - это «вернуться к началу очереди с новым талоном» против «остаться у этого окна». Но у второго варианта есть побочный эффект, о котором не пишут: сказав «остаюсь», ты закрываешь и всё остальное окно тоже. break не просто отменяет повторный поиск location - он прекращает работу всего модуля перезаписи в этом блоке, и написанные ниже return, set и rewrite не выполняются.

Механизм: две дороги после замены

rewrite заменил URI last break поиск location заново остаёмся в этом блоке другой блок обслужит строки ниже не выполнятся не больше десяти раз отдаёт контент этого блока
Правая дорога короче, но она же уносит с собой остаток блока

Правило

Замена - это новый URI целиком, а не приставка к старому. Всё, что нужно сохранить, переносится скобками: $1, $2. Флаг решает, куда идти дальше:

Флаг Что происходит
нет флага URI заменён, следующие rewrite выполняются, потом поиск location заново
last немедленно остановиться и искать location заново
break остаться в этом блоке; остальные директивы фазы не выполняются
redirect отдать клиенту 302
permanent отдать клиенту 301

Разбор: куда делся хвост адреса

location /хвост/ { rewrite ^/хвост/ /короткий last; }
location /короткий { return 200 "uri=$uri\n"; }

Запрос /хвост/длинный/путь даёт uri=/короткий. Не /короткий/длинный/путь.

В регулярке нет скобок, значит переносить нечего, а замена подставляется как есть. Ошибка живучая, потому что на коротком адресе /хвост/ разницы не видно - она вылезает на первом же вложенном пути. Верная запись сохраняет хвост явно:

rewrite ^/хвост/(.*)$ /короткий/$1 last;

Там же вторая тонкость - строка параметров. Замер на запросе ?a=1:

замена что получил целевой блок
/цель/x args=a=1 - исходные параметры дописаны
/цель/x? args= - знак вопроса в конце их отбрасывает

Разбор: почему break отдал 404

Возвращаемся к блоку из начала урока. break сработал буквально: перезапись выполнена (/break/файл стал /цель/файл), поиск location не повторяется, а return 200 ниже не выполняется вовсе - модуль перезаписи в этом блоке остановлен. Дальше запрос идёт на фазу контента с новым URI, обработчика в блоке нет, и nginx честно ищет файл /www/цель/файл. Файла нет - 404.

Отсюда единственное место, где break действительно нужен:

/etc/nginx/conf.d/shop.local.confhttp server
location /api/v1/ {
    rewrite ^/api/v1/(.*)$ /api/v2/$1 break;
    proxy_pass http://app:8000;
}

Обработчик контента (proxy_pass) стоит в том же блоке - значит уходить некуда, и break тут по делу. Поставь last - и nginx пойдёт искать location для /api/v2/..., а proxy_pass из этого блока не сработает.

Что ломается без этого

  • Замена со схемы - всегда ответ клиенту. rewrite ^ http://example.org/x; без флага дал 302: если замена начинается с http://, https:// или $scheme, nginx перестаёт переписывать внутри. Собрал адрес из $scheme для внутренней перезаписи - получил редирект.
  • Одиннадцатая перезапись роняет запрос. Замер цепочкой из блоков: десять rewrite ... last подряд проходят и отдают 200, одиннадцатая даёт 500, а в error_log появляется rewrite or internal redirection cycle while processing "/ш12" - с именем URI, на котором счётчик кончился. Внутренние перенаправления от error_page и try_files тратят тот же лимит.
  • rewrite на уровне server идёт до выбора location. Проверено: с rewrite ^/старый(.*)$ /новый$1; в блоке server запрос /старый/путь попадает в location /новый, а location /старый не выигрывает никогда.
  • Лимиты считают уже новый адрес. Перезапись из location /вход в location /ограничен с limit_req: четыре запроса подряд дали 200 503 503 503. Ограничение применяется к тому блоку, куда пришли, а не к тому, откуда ушли.

Зачем это в работе

Настоящих поводов брать rewrite вместо return немного, и все они про сбор адреса из кусков.

/etc/nginx/conf.d/shop.local.confhttp server
location /promo/ {
    rewrite ^/promo/(.*)$ /landing/$1.html last;
}

/promo/summer превращается в /landing/summer.html и отдаётся статикой, а в браузере остаётся исходный красивый адрес. Для переезда домена целиком регулярка не нужна - там дешевле return 301 ... $request_uri, который не запускает движок регулярных выражений на каждый запрос.

Теперь сам

Конфиг переехавшего раздела. Что придёт на /старое/док.pdf?

location /старое/ {
    rewrite ^/старое/(.*)$ /новое/$1 permanent;
    return 200 "перезапись\n";
}

Ответ: 301 с адресом http://<твой Host>/новое/док.pdf. Флаг permanent отвечает клиенту сразу, поэтому return ниже не выполняется - ровно как в случае с break. И относительная замена достроится до абсолютного адреса из заголовка Host, как у return 301 /new.

Главное

Замена - это весь новый URI: хвост переносят скобками, иначе он пропадёт. break остаётся в блоке и обрывает остальные директивы фазы, last уходит искать location заново. Замена со схемы - редирект без всякого флага. Предел - десять перезаписей, одиннадцатая даёт 500 со строкой cycle в логе.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.