Angie предоставляет широкие возможности по перенаправлению запросов, которые востребованы в большинстве веб‑приложений. В этой статье мы разберём все основные методы перенаправлений: от простых до сложных. Начнём с общего принципа обработки директив в модуле rewrite.
Навигация по циклу
Настройка location в Angie. Разделение динамических и статических запросов.
Перенаправления в Angie: return, rewrite и примеры их применения.
Видеоверсия
Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.
Порядок обработки директив
Как гласит документация модуля rewrite, сначала директивы перенаправления (if, set, break, return, rewrite
) обрабатываются последовательно в рамках выбранного блока server. Далее происходит поиск location
по URI запроса и выполняются директивы в этом location
. Если внутри location
директивы приводят к изменению URI и поиску другого location
, то процесс поиска и исполнения директив повторяется до 10 раз.
Далее мы увидим, что можно управлять поведением перенаправлений, например прекратить обработку перенаправлений после определённого rewrite.
Директива return
Самый простой способ вернуть ответ клиенту — указать директиву return. Первый вариант — заглушка: фиксированный код ответа и текст. Например, так:
return 200 'OK text, test location 1';
Эту директиву можно разместить в блоке server, location
или if
. В таком виде return
позволяет определить тестовые server
или location
, отвечать ботам при определённых обстоятельствах (с использованием блока if
). Пример с условием на основе переменной:
http {
map $http_user_agent $limit_bots {
default 0;
~*(google|bing|yandex|msnbot) 1;
}
server {
if ($limit_bots = 1) {
return 404;
}
}
Здесь определяется переменная $limit_bots
на основе значения переменной $http_user_agent
. Далее эта переменная используется в блоке server
для условного возврата кода 404. То есть, если к нам приходит бот с определёнными символами в заголовке User‑Agent
, Angie вернёт код 404 на любой запрос. В Angie есть специальный код ответа 444 для немедленного разрыва соединения без отправки ответа (полезно при борьбе с ботами).
Второй вариант использования — перенаправления. В этом варианте необходимо указать один из кодов трёхсотой серии (например, 301
или 302
) и URL, на который необходимо отправить пользователя. Типичный пример — перенаправление пользователя с HTTP‑ на HTTPS‑версию с нужным доменом и сохранением URI:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
}
Здесь стоит уточнить разницу между кодами ответа 301
и 302
. Код 301
имеет текстовое описание Moved Permanently
, что означает постоянный редирект. Клиент, получив такой код ответа, вправе кэшировать его на долгое время и не отправлять запрос на оригинальный URL. Так поступают обычные браузеры, поисковые боты воспринимают 301
код ответа как признак нового постоянного адреса ресурса.
Код 302 (Found)
менее категоричен и указывает на временный редирект, подразумевая, что адрес назначения может измениться в будущем. Это более безопасный способ перенаправления, если необходимо протестировать конфигурацию, но нет уверенности в корректности ее работы.
Директива return
покрывает широкий спектр задач, но для более гибких перенаправлений используется директива rewrite
, основанная на регулярных выражениях.
Директива rewrite
Полноценные перенаправления реализуются с помощью директивы rewrite
. В отличие от return
, в rewrite
необходимо указывать регулярное выражение, которое сопоставляется с URI запроса (GET‑параметры запроса не учитываются), а также можно производить внутренние перенаправления помимо отправки 3xx
кодов. Базовый синтаксис этой директивы включает в себя регулярное выражение (regex), целевой URL и необязательный флаг‑модификатор. Регулярные выражения используют синтаксис библиотеки PCRE (Perl compatible regular expressions).
Внутренние перенаправления нужны для преобразования адресов в форму, требуемую приложению. Например, нужно реализовать человекочитаемые URL для разделов каталога: /catalog/cat1/, /catalog/cat2/
и т. д., при этом приложение требует адреса следующего вида: /catalog?cat=1
. Создаём директиву rewrite для преобразования адресов:
server {
rewrite ^/catalog/cat(\d+)/$ /catalog?cat=$1 last;
}
В этом примере используется регулярное выражение, которое сопоставляет URI с шаблоном, при этом добавлены якоря начала (^
) и конца ($
) строки, а также присутствуют сохраняющие скобки (\d+
), которые помещают эту часть в переменную $1
, и она используется в URI замены. Кстати, регулярные выражения можно обрамлять в кавычки для исключения проблем обработки конфигурации (например, если там есть символ «}
» или «;
»). Флаг last
предписывает выполнить внутреннее перенаправление, завершить обработку в модуле rewrite на этом уровне и найти location
для обработки запроса. Таким образом, клиент получает не перенаправление с кодами 301
или 302
, а сразу полноценный ответ на запрос. Если использовать внутренние перенаправления внутри location
, то может быть полезен флаг break
, который прекращает обработку перенаправлений и оставляет обработку запроса в текущем location
. Для приведённого выше примера это будет выглядеть так:
server {
location /catalog/ {
rewrite ^/catalog/cat(\d+)/$ /catalog?cat=$1 break;
}
}
Обычные перенаправления также можно реализовать через директиву rewrite
:
server {
rewrite ^/result/([a-zA-Z0-9_]+)$ /result/$1/ permanent;
}
Здесь клиент получит ответ 301
c заголовком Location
, в котором будет новый адрес. Этот заголовок можно посмотреть в средствах разработчика браузера (в сетевой закладке), либо с помощью curl ‑-head
к исходному URL. Проверить работу перенаправления целиком можно с помощью браузера либо через curl ‑L
. При использовании флага redirect
ответ будет иметь код 302
.
Во всех примерах выше мы перенаправляли запрос на основе URI без учета GET‑параметров запроса. Если такая задача всё‑таки стоит, то есть несколько не совсем тривиальных решений так как обычного rewrite
здесь будет недостаточно. Первое решение основано на блоке if
(напомню, что блоки if рекомендуется использовать только в директивами из модуля rewrite
).
location /index/ {
if ($args ~ "^t1=(\d+)&t2=(\d+)") {
set $key_t1 $1;
set $key_t2 $2;
rewrite ^ /loc/$key_t1/$key_t2 last;
}
}
Более короткая форма с именованными захватами в регулярном выражении:
location /index/ {
if ($args ~ "^t1=(?<key_t1>\d+)&t2=(?<key_t2>\d+)") {
rewrite ^ /loc/$key_t1/$key_t2 last;
}
}
Здесь проверка параметров основана на сравнении переменной $args
(все GET‑параметры запроса) с регулярным выражением и сохранении значений параметров t1
и t2
в переменные $key_t1
и $key_t2
соответственно. Далее на основе этих значений формируется новый URI для поиска location
и финальной обработки.
Второй способ решения задачи с параметрами использует возможности проксирования. Например, так:
location /index/ {
proxy_pass http://127.0.0.1:8080/loc/$arg_t1/$arg_t2;
}
Кстати, для отладки обработку перенаправлений и директив модуля rewrite
можно логировать в error_log
(уровень notice
), указать директиву можно в контекстах http, server, location, if
:
rewrite_log on;
Возможно, стоит дополнительно вспомнить про директиву try_files
, хотя она и не относится к модулю rewrite
. С помощью try_files
можно последовательно проверять наличие файлов и использовать внутреннее перенаправление на именованный location
или возврат ошибки. Классический пример выглядит так:
location / {
try_files $uri $uri/ @namedloc;
}
location @namedloc {
proxy_pass http://127.0.0.1:8080;
}
location /static/ {
try_files $uri $uri/ =404;
}
В этом примере для location /
происходит попытка обработать запрос по $uri,
если такого файла нет, то добавляется слэш ($uri/
) и попытка обработать запрос как директорию; если и это не получилось, то идет внутреннее перенаправление на location @namedloc
. Для location /static
первая попытка ведёт на URI без изменений, вторая на директорию, а в случае ошибки отдаётся код 404
. Интересная особенность: если в try_files
файл не был найден, то запись об ошибке поиска файла в error_log
не добавляется.
Итак, мы рассмотрели основные возможности модуля rewrite, начиная с базовых вариантов использования директивы return
, и завершили более сложными конструкциями с применение регулярных выражений и блоков if
.