# теория · шаг 2 из 7
Адрес по частям: что доезжает до сервера
Коротко
Адрес в браузерной строке и то, что получает сервер, - разные вещи. Часть адреса уезжает в заголовок, часть не уезжает вовсе, а часть приезжает в двух видах сразу.
- В строке запроса едут только путь и параметрыПараметры запросаПары «ключ=значение», разделённые амперсандом. В nginx лежат в переменной $args и в выборе location не участвуют вовсе - location смотрит только на путь.. Схема и хост - не там: хост
приходит заголовком
Host. - Всё после
#до сервера не доезжает никогда. - Путь приезжает в двух переменных:
$request_uri- как прислали,$uri- раскодированный и нормализованный.locationвыбирается по второму. - Параметры (
$args) nginx не раскодирует вообще:%20там так и остаётся%20. - Слишком длинный адрес - это
414и строка в логе на уровнеinfo.
Если разница $uri и $request_uri и судьба фрагмента знакомы - листай до «Теперь сам».
Сначала ответь сам
Человек открыл в браузере:
https://shop.local/catalog/шкафы?sort=price#отзывы
Какие части этого адреса увидит nginx в строке запроса?
Не схему и не хост - в строке запроса их нет вовсе, а имя сайта приедет отдельным
заголовком Host. И не фрагментФрагментУказание браузеру, к какому месту страницы прокрутиться. На сервер он не отправляется никогда, поэтому в конфиге nginx его нельзя ни увидеть, ни поймать правилом.: всё после # браузер оставляет себе,
сервер о нём не узнает никогда. Останется путь и параметры, причём кириллица
будет закодирована:
GET /catalog/%D1%88%D0%BA%D0%B0%D1%84%D1%8B?sort=price HTTP/1.1
Отсюда сразу следует практическое: правило в конфиге, написанное «по адресу из браузера», может не сработать - сервер видит другую строку.
На что это похоже
Почтовый адрес на конверте состоит из частей, и у каждой своя судьба.
Страна и город нужны, чтобы конверт вообще доехал до нужного отделения, - дальше на них никто не смотрит. Улица и дом - то, по чему сортируют внутри отделения. А приписка «позвонить перед доставкой, домофон не работает» вообще не адрес: она для того, кто понесёт конверт, и в маршрутизацию не входит.
Схема и хост - это страна с городом: они решают, до какого сервера идти, а в запросе едут отдельно. Путь и параметры - улица с домом. Фрагмент - приписка, которую читает только браузер: по ней он прокручивает страницу к нужному месту, и отправлять её на сервер незачем.
Механизм: что из адреса куда попадает
Правило
$request_uri - что прислали, $uri - что получилось после раскодирования и
нормализации. Решения о маршруте nginx принимает по второму, а в редиректах
почти всегда нужен первый.
Разбор: одна строка в трёх переменных
Запрос с процент-кодированиемПроцент-кодированиеСпособ положить в адрес то, чего в нём быть не может: пробел становится %20, кириллица - парами байтов. nginx раскодирует путь до выбора location, а вот параметры запроса не раскодирует вовсе. в пути и в параметрах:
curl --path-as-is 'http://lab.local/vars/a%2Fb%20c?x=a+b%20c'
uri=/vars/a/b c
request_uri=/vars/a%2Fb%20c?x=a+b%20c
args=x=a+b%20c
Три вещи разом:
%20в пути стал пробелом, а%2F- слешем. То есть в$uriпоявился разделитель каталогов, которого в исходном адресе не было;$request_uriсохранил всё как прислали, вместе с?и параметрами;$argsне раскодирован вообще: там как былa+b%20c, так и остался. Плюс тоже остался плюсом, хотя в параметрах он по традиции значит пробел - раскодировать его должно приложение.
Кириллица ведёт себя так же: /vars/файл.txt в $uri, а в $request_uri -
двадцать четыре процента с цифрами.
Разбор: где наивное правило подводит
Фрагмент нельзя поймать в конфиге. Ни location, ни if, ни map его не
увидят - его нет в запросе. Просьба «сделай редирект для ссылок с #promo»
невыполнима на стороне сервера, и это не ограничение nginx, а устройство
протокола.
Тонкость: если кривой клиент всё-таки отправит # в строке запроса, nginx
положит его в $request_uri, но отрежет от $uri - проверено. То есть даже в
этом случае маршрутизация фрагмент не увидит.
%2F в пути превращается в обычный слеш ещё до выбора location. Правило
курса из раздела про location («URIURIТо, что идёт после метода: путь плюс необязательные параметры после вопросительного знака. Имени сайта в нём нет - оно приезжает заголовком Host, - а фрагмента после решётки нет тем более. нормализуется до выбора») работает и здесь,
и следствие у него двойное. Хорошее: запрет deny не обойти, закодировав слеш.
Плохое: путь /files/a%2Fb.txt, в котором слеш был частью имени файла,
превратится в путь на два уровня, и файл не найдётся.
Редирект по $uri теряет параметры. Строка return 301 https://$host$uri;
выглядит правильной, но выбрасывает всё после ?: человек пришёл по ссылке с
utm-метками, а уехал на голый адрес. Нужен $request_uri, и он уже включает
?:
return 301 https://$host$request_uri;
Что ломается без этого
Три типовые беды растут из одной путаницы. Редирект, который теряет параметры
поиска, - это $uri вместо $request_uri. Аналитика, где половина переходов без
меток, - тот же редирект. Правило, которое «не срабатывает», хотя адрес совпадает
буква в букву, - сравнение с закодированной строкой при том, что nginx сравнивает
с раскодированной.
Зачем это в работе
Длина адреса не бесконечна. Заголовки и строка запроса читаются в буфер, и по
умолчанию его хватает примерно на восемь килобайт. Адрес длиннее даёт 414:
curl -s -o /dev/null -w 'код=%{http_code}\n' "localhost/$(head -c 9000 /dev/zero | tr '\0' a)"
код=414
Выражение в $( ) собирает адрес из девяти тысяч букв a, а -w печатает
код ответа.
[info] client sent too long URI while reading client request line
У строки метка info - это уровень логаРазбирается в разделе 6, глава «access_log и log_format»: Что пишется без тебя,
одна из восьми ступеней серьёзности; настройка error_log отсекает всё, что
ниже названной. При типовом уровне - error в пакете Debian или warn из
инструкций - этой строки не будет, и
414 останется без объяснения. Лечится large_client_header_buffers, но сначала
стоит выяснить, кто и зачем шлёт такие адреса: обычно это самодельный клиент,
складывающий список идентификаторов в параметры вместо тела запросаТело сообщенияВсё, что идёт после пустой строки: содержимое файла, JSON, загружаемая картинка. У обычного GET его нет, а у ответов 204 и 304 не бывает по определению..
Теперь сам
В конфиге стоит редирект return 301 https://$host$uri;. Отдел маркетинга
жалуется: переходы с рекламы приходят в аналитику без источника, хотя ссылки
размечены метками. Что случилось и как чинить?
Метки живут в параметрах, а $uri их не содержит - редирект отправляет человека
на адрес без ?utm_source=..., и аналитика видит прямой заход. Заменить на
$request_uri, который несёт и путь, и параметры целиком. Проверить просто:
curl -sI 'http://shop.local/page?a=1' и посмотреть, что стоит в Location.
Главное
В строку запроса едут только путь и параметры; хост приезжает заголовком Host,
а фрагмент после # не уходит на сервер никогда и в конфиге недоступен.
$request_uri - строка как прислали, $uri - раскодированная и
нормализованная, по ней выбирается location; $args не раскодируется вовсе.
%2F в пути становится настоящим слешем ещё до маршрутизации. В редиректах
почти всегда нужен $request_uri - иначе теряются параметры. Адрес длиннее
буфера даёт 414, а объяснение пишется на уровне info.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий