
Собственное описание этой ошибки в Chromium изумительно просто: «Попытка загрузить URL привела к слишком большому количеству переадресаций». Браузер следовал по цепочке переадресаций, цепочка не завершилась, и в какой-то момент он перестал следовать дальше.
Количество переадресаций, при котором браузер останавливается, определено в Fetch Standard совершенно однозначно: если количество переадресаций в запросе достигло 20, то вернуть сетевую ошибку. Итак, двадцать переходов, и после этого страница, которую вы видите, выполнена браузером, а не вашим сервером.
Почему эта ошибка достойна отдельной статьи, это не механизм её возникновения, который очевиден. Дело в том, что цикл переадресаций почти никогда не является результатом поломки одного компонента. Это две вещи, каждая из которых ведёт себя ровно так, как она настроена, но при этом не согласны друг с другом по поводу чего-то, что ни одна из них не может видеть.
Классический случай: несогласие по поводу HTTPS
Вы помещаете CDN или балансировщик нагрузки перед своим приложением. Он завершает TLS, что является его задачей, и пересылает запрос вашему источнику по обычному HTTP, что нормально и обычно делается специально.
Ваше приложение имеет правило: если этот запрос не HTTPS, переадресуй его на HTTPS. Это тоже правильно, и на сервере без прокси перед ним такое правило именно то, что нужно.
Теперь поставьте их вместе. Браузер запрашивает https://example.com. CDN завершает TLS
и пересылает http://example.com вашему приложению. Ваше приложение видит обычный HTTP,
делает разумное действие и отвечает: «переадресуй на https://example.com». Браузер подчиняется.
CDN завершает TLS и пересылает HTTP. Двадцать раз, и потом браузер сдаётся.
Ни один из компонентов не настроен неправильно сам по себе. Приложение требует HTTPS. Прокси завершает TLS. Ошибка находится в щели между ними, поэтому обе команды могут посмотреть на свою собственную конфигурацию и правильно заключить, что она хороша.
Исправление в том, что приложение должно перестать задавать неправильный вопрос. Вместо
«был ли этот запрос HTTPS» ему нужно спросить «был ли исходный запрос HTTPS», что именно
то, для чего существует заголовок X-Forwarded-Proto. В большинстве фреймворков есть
настройка для доверия этому заголовку от известного прокси, и обычно она отключена по
уважительной причине: заголовок, который может установить кто угодно, это не доказательство,
если вы не знаете, кто его установил.
Та же форма, три раза ещё
Когда вы видите закономерность, другие типичные циклы имеют ту же историю с разными именами.
www против вершины домена. Один слой переадресует example.com на www.example.com. Другой,
добавленный позже или кем-то другим, переадресует www обратно на вершину. Каждое правило
можно защитить. Вместе они образуют цикл.
Косая черта в конце пути. Ваш веб-сервер переадресует /about на /about/. Маршрутизатор
фреймворка переадресует /about/ на /about. Оба требуют канонический вид. Они не согласны
по поводу того, какой вид является каноническим.
Цикл входа, который достигает настоящих пользователей. Приложение не видит действительной
сессии, поэтому оно переадресует на /login. Страница входа проверяет куки сессии, считает,
что вы вошли в систему, и переадресует обратно в приложение. Туда-сюда, туда-сюда.
Последний из них стоит обсудить подробнее, потому что причина обычно в том, что куки не
сохраняется, а не в том, что правила переадресации неправильны: куки Secure на странице,
подаваемой по HTTP, куки SameSite=None без Secure, куки установлена на www.example.com
и читается на example.com, или браузер настроен отказывать сторонним куки в встроенном
контексте. Логика переадресации верна во всём. Она действует на основе сессии, которая никогда
не была сохранена.
- Слабо
- «Страница входа застревает в цикле. Она просто всё время перезагружается».
- Лучше
- ERR_TOO_MANY_REDIRECTS на /login, только в приватном окне, только после того как я вышел и вошёл снова. Работает в обычном окне. Цепочка: /app → /login → /app.
Почему это особенно сложно увидеть изнутри
Цикл переадресации часто характерен для состояния, в котором вы не находитесь.
Если это цикл входа, ваша куки уже установлена и работает, поэтому сайт работает идеально для вас и циклится для каждого, кто приходит впервые. Если это правило www, которое кто-то добавил на прошлой неделе, оно циклится только для людей, которые ввели другое имя хоста. Если это зависит от куки, которую браузер отказывается использовать, это воспроизводится в приватном окне и больше нигде.
И очевидная проверка это пропускает. curl -I https://example.com показывает вам один ответ,
который будет выглядеть как совершенно разумный 301 с заголовком Location. Ничто в нём не
говорит, что цепочка никогда не завершится. Что вам нужно, так это следовать всей цепочке:
curl -sIL -o /dev/null -w '%{url_effective} %{http_code}\n' https://example.com
curl -sI https://example.com | grep -i '^location'
Или откройте вкладку Сеть с
установленной галочкой Сохранять журнал, что единственный способ сохранить ранние переходы
вместо того, чтобы стирать их при каждой навигации. Цепочка это диагноз: прочитайте заголовок
Location на каждом этапе, и вы обычно можете увидеть два правила, спорящих друг с другом за
три переадресации.
Session Replay
Бесплатное расширение Chrome. Один клик на странице, которая ведёт себя неправильно, захватывает скриншот, консоль и журнал сети, и даёт вам ссылку для вставки в тикет.
Один момент про 301, который стоит знать перед использованием
301 это не просто 302, что означает его больше.
MDN конкретна об одном следствии, которое удивляет людей: в Fetch Standard пользовательский агент,
получивший 301 в ответ на POST, использует GET для переадресованного запроса. Ваша отправка
формы становится GET, тело исчезает, и ошибка выглядит совсем не как проблема переадресации.
Если вам нужно сохранить метод, 308 это тот, который запрещает его изменять.
Общее правило более скучно и полезнее: выбирайте 302 или 307 пока вы всё ещё работаете
над определением канонического URL, и используйте 301 или 308 когда вы уверены. Постоянная
переадресация это утверждение, а утверждения намного легче сделать, чем отозвать.
Что из этого взять
ERR_TOO_MANY_REDIRECTS означает двадцать переходов без завершения, и завершение отсутствует
потому что два правила не согласны, а не потому что одно из них сломано.
Поэтому не начинайте с поиска сломанного правила. Начните со списания каждого уровня, который
может издавать переадресацию: CDN, балансировщик нагрузки, веб-сервер, фреймворк, приложение,
и что-нибудь, что плагин добавил без вашего ведома. Потом прочитайте цепочку, один заголовок
Location за раз, и найдите два, которые спорят.
И когда это не воспроизводится для вас, это информация, а не препятствие. Это означает, что цикл зависит от состояния, которое вы уже прошли, и самый быстрый способ к нему это приватное окно, другое имя хоста, или полная картина того, что браузер фактически получил от кого-нибудь, для которого это сейчас не работает.