Зацикливание на редиректе

CoDDoC coddoc на mail.ru
Чт Авг 3 09:15:16 UTC 2017


Доброго дня сообществу! Прошу совета по проблеме.
Собственно, редирект с www на https без-www

server {
    #1 http to https without www
    listen 1.2.3.4:80;
    server_name www.test.com test.com;
    rewrite ^ https://test.com$request_uri? permanent;
}

server {
    #2 https with www to https without www
    listen 1.2.3.4:443 ssl;
    server_name www.test.com;
    rewrite ^ https://test.com$request_uri? permanent;
}

server {
    #3 https without www
    listen 1.2.3.4:443 ssl;
    server_name test.com;
    ...
}

Насколько я понял из документации (http://nginx.org/ru/docs/http/ngx_http_core_module.html#var_host), переменная $host принимает значения "в порядке приоритета: имя хоста из строки запроса, или имя хоста из поля Host заголовка запроса, или имя сервера, соответствующего запросу"

Судя по логам, это не совсем так.
По крайней мере, в моем случае (nginx/1.10.2), переменная $host получает имя хоста из строки запроса только если не указано поле host заголовка. Т.е. обрабатывается ситуация с HTTP/1.0, без $http_host в заголовке. Но если в заголовке задать какое-то (любое) значение $http_host, это же значение получает и $host.

Далее (http://nginx.org/ru/docs/http/request_processing.html): nginx "сопоставляет значение поля Host заголовка запроса с директивами server_name в блоках server, которые соответствуют IP-адресу и порту". Т.е. все-таки $http_host. А туда можно прописать что угодно.
Я не рассматриваю сейчас ситуацию, когда в $http_host прописано имя, не совпадающее с перечисленными в server_name. Это все благополучно фильтруется и отправляется на 444. Также, я не рассматриваю браузеры, которые отправляют правильный $http_host и получают правильные редиректы.

Вот такой случай:
curl -ILH 'Host: www.test.com' https://test.com

Если бы переменная $host получила значение в порядке приоритета, оно было бы test.com (имя хоста из строки запроса). Тогда можно было бы реализовать такой костыль, как фильтрация по условию "$host не равно $http_host". Но в запросе присутствует заголовок host, и обе переменные $host и $http_host получают одно и то же значение www.test.com , отфильтровать невозможно.
Имя www.test.com перечислено в server_name, в итоге такой запрос успешно проходит фильтрацию. После сопоставления значения $http_host с server_name, nginx отправляет запрос в секцию 2, откуда возвращается 301 и новый location https://test.com , т.е. на выходе получаем тот же самый запрос (curl -ILH 'Host: www.test.com' https://test.com) и, естесвенно, зацикливание на второй секции:

curl -ILH 'Host: www.test.com' https://test.com
HTTP/1.1 301 Moved Permanently
..........
Location: https://test.com/

HTTP/1.1 301 Moved Permanently
..........
Location: https://test.com/

..........

Это баг или фича? Или я что-то делаю не так? Как это побороть?
Спасибо.
--
----------- следущая часть -----------
Вложение в формате HTML было извлечено…
URL: <http://mailman.nginx.org/pipermail/nginx-ru/attachments/20170803/b1a493f2/attachment.html>


Подробная информация о списке рассылки nginx-ru