Ошибки 402, 403 и 429 на Тильде: один код ответа, пять разных реакций
Если на сайте на Тильде что-то не загрузилось и в консоли появилось число в квадратных скобках — это HTTP-статус ответа. Я прочитал все 53 файла рантайма Тильды и замерил в браузере, что её код делает при каждом коде ответа. Оказалось, что единого правила нет: у Тильды пять разных слоёв запросов, и один и тот же 403 в них даёт от одного до пяти обращений к серверу. Номера 402 и 403 при этом не названы в коде ни разу.
Короткий ответ
В 53 файлах рантайма Тильды номера 402 и 403 не названы ни разу. Повторяет запрос
она по списку: 500 и выше, 408, 410, 429 и таймаут. Но это только один из слоёв:
загрузчик файлов повторяет пять раз при любом отказе, а новый слой магазина на
fetch — при любом ответе вне 200–299, включая 403.
Что именно я мерил, и чего этот замер не знает
Мерил две вещи. Первая — 53 файла рантайма Тильды: искал каждое место, где код смотрит на статус ответа. Вторая — поведение в браузере: брал её же функции без правок, подсовывал им сервер, который отдаёт нужный код, и считал запросы.
Чего здесь нет и быть не может: это код, работающий в браузере. Он не знает и не может сказать, почему сервер Тильды ответил 402 или 403 именно на вашем сайте. Если вы пришли за этим — ответ у поддержки Тильды, а не в коде страницы. Здесь другое: что происходит после такого ответа, и как по тексту в консоли понять, какая именно служба не ответила.
Число в квадратных скобках — это HTTP-статус
Первое, что стоит знать: если вы видите [403] или [429], это не внутренний код
Тильды. Это статус ответа, вставленный в строку:
// каталог, tilda-catalog-1.1
o = t && t.responseText > "" ? "[" + t.status + "] " + t.responseText
: t && t.statusText ? "Error [" + t.status + ", " + t.statusText + "]"
: "Error type [" + t.type + "]";
console.error(o + ". Please, try again later.");
Форма сообщения зависит от того, что пришло от сервера. Замерил все три случая, вызывая эту функцию Тильды с разными ответами:
| Что ответил сервер | Что напечатано |
|---|---|
403 и тело Forbidden | [403] Forbidden. Please, try again later. |
| 403, тела нет, есть statusText | Error [403, Forbidden]. Please, try again later. |
| 403, ни тела, ни statusText | Error type [undefined]. Please, try again later. |
Третья строка — ловушка: номер статуса из сообщения исчезает совсем. Пустой
ответ с кодом 403 даёт текст, по которому нельзя понять, что это был 403. У
доставки та же развилка, но с четвёртой веткой, и там номер остаётся: [403] Unknown error. Please, try again later.
Пять слоёв запросов, и в каждом свой список поводов
Это главное. У Тильды не одно правило повтора, а пять, и они не согласованы между собой. Таблица собрана из замеров: колонки «403» и «429» — сколько обращений к серверу я насчитал.
| Слой | Где живёт | Что считает поводом повторить | 403 | 429 |
|---|---|---|---|---|
| Загрузка js и css | tilda-scripts-3.0 | любой отказ; статус коду не виден | 5 запросов | 5 запросов |
Магазин на fetch | tilda-cart-1.1, tilda-delivery-1.0 | любой ответ вне 200–299 | 2 запроса | 2 запроса |
| Каталог на XHR | tilda-catalog-1.1 | ≥500, 408, 410, 429, таймаут | не повторяет | вызывает повтор |
| Доставка | tilda-delivery-1.0 | ≥500, 408, 410, 429, таймаут | не повторяет | вызывает повтор |
| Формы | tilda-forms-1.0 | ≥500, 408, 410, 429, таймаут | не повторяет | отправляет заново |
Разница между 403 и 429 живёт только в трёх нижних строках. В двух верхних код ответа не важен вообще.
Загрузчик файлов: пять запросов при любом статусе
Когда Тильда догружает свой js или css, она делает это через t_loadJsFile.
Повтор там висит на событии error у тега <script>, а это событие не несёт
HTTP-статуса — браузер его не отдаёт. Поэтому 402, 403, 429 и 500 для загрузчика
неразличимы:
function t_loadJsFile(t, e, n) {
void 0 === n && (n = 0);
var o = 3, i = 1e3, r = t_modifyRootZone(t);
// …
a.addEventListener("error", function (t) {
if (!(n <= 3)) {
var o = t.message || "<no error message>";
throw new Error("Failed to load " + r + " script: " + o, { cause: t })
}
setTimeout(function () {
a.remove(), t_loadJsFile(r, e, n + 1),
console.warn("Retrying to load " + r + ". Retry: " + (n + 1))
}, n * i)
})
}
Замер: поднял страницу, подсунул адрес, который всегда отдаёт нужный код, и посчитал обращения.
| Сервер отдаёт | Запросов | Когда, мс от первого |
|---|---|---|
| 403 | 5 | 0, 4, 1008, 3011, 6014 |
| 402 | 5 | 0, 2, 1005, 3007, 6010 |
| 429 | 5 | 0, 3, 1007, 3012, 6015 |
| 500 | 5 | 0, 2, 1005, 3007, 6009 |
| 200 (контроль) | 1 | 0 |
Три вещи, которые видно в этих числах и в коде.
Первый повтор уходит мгновенно. Задержка считается как n * i, где i — 1000
мс, а n на первом отказе равно нулю. Ноль умножить на тысячу — ноль. Дальше
1000, 2000 и 3000 мс, и все пять запросов укладываются в шесть секунд.
Повторов четыре, а не три. Условие n <= 3 истинно при n равном 0, 1, 2 и 3,
то есть повтор назначается четыре раза, и запросов получается пять. После пятого
отказа код бросает исключение.
Переменная o = 3 не читается. Она объявлена рядом с задержкой и, судя по
значению, задумывалась как предел числа попыток. Но в самом сравнении стоит
литерал 3, а внутри обработчика o переопределяется под текст ошибки. Ровно то
же самое — в t_loadCSSFile, слово в слово, включая мёртвую o.
Новый слой магазина: повтор при любом ответе вне 200–299
Часть запросов каталога и корзины идёт уже не через XMLHttpRequest, а через
fetch. Здесь решает одна строка:
Promise.resolve(fetch(t, s)).then(function (t) {
if (clearTimeout(m), !t.ok) throw new Error("HTTP error! Status: " + t.status);
return Promise.resolve(parseResponse(t, _))
})
response.ok ложно для любого статуса вне 200–299. Значит 402 и 403 здесь бросают
исключение так же, как 500, а обёртка выше ловит его и уходит на дублёра — не
разбирая, что именно случилось:
_catch$a(
function () { return fetchWithRedirectHandling(t_cart__getStoreEndpointUrl(e, r), n) },
function (t) {
console.error("Initial fetch failed, retrying with fallback URL:", t);
return fetchWithRedirectHandling(t_cart__getStore2EndpointUrl(e, r), n)
}
)
Замер на странице с data-tilda-root-zone="ru", подсчёт по хостам:
| Сервер отдаёт | Запросов | Куда |
|---|---|---|
| 403 | 2 | store.tildaapi.ru, затем store2.tildaapi.ru |
| 402 | 2 | то же |
| 429 | 2 | то же |
| 500 | 2 | то же |
| 200 (контроль) | 1 | только store.tildaapi.ru |
То есть на вопрос «повторяет ли Тильда запрос при 403» правильный ответ — «в этом слое да, ровно один раз». Таймаут по умолчанию здесь 30 секунд, а у запроса товаров по идентификатору — 20.
Старый слой на XHR: список кодов, и 402 с 403 в него не входят
Три файла содержат одну и ту же конструкцию. Вот версия каталога:
function t_store_changeEndpoint(t, e) {
if (t && (t.status >= 500 || 408 == t.status || 410 == t.status
|| 429 == t.status || "timeout" === t.type)
&& -1 !== window.t_store_endpoint.indexOf("store.tilda")) {
var r = t_store__getRootZone();
window.t_store_endpoint = "store2.tildaapi." + r;
"function" == typeof e && e()
} else { /* печать в консоль */ }
}
Замер: вызывал эту функцию Тильды с поддельным ответом на каждом статусе. Контроль — 429 обязан переключить адрес, 200 обязан не переключать.
| Статус | Адрес после | Повтор |
|---|---|---|
| 200 (контроль) | store.tildaapi.ru | нет |
| 402 | store.tildaapi.ru | нет |
| 403 | store.tildaapi.ru | нет |
| 404 | store.tildaapi.ru | нет |
| 408 | store2.tildaapi.ru | да |
| 410 | store2.tildaapi.ru | да |
| 429 | store2.tildaapi.ru | да |
| 500 | store2.tildaapi.ru | да |
| 503 | store2.tildaapi.ru | да |
| таймаут | store2.tildaapi.ru | да |
Обратите внимание: 503 переключает, хотя в списке его нет. Он проходит по
условию status >= 500. А 402, 403 и 404 не проходят ни по одному из пяти условий
и уходят в общую ветку с печатью в консоль.
Повтор ровно один, и это сделано не счётчиком
Счётчика попыток в этих трёх файлах нет вообще — я искал по границам слов, и единственные совпадения оказались текстами служебных сообщений сборщика. Один повтор получается из формы условия:
| Слой | Сторож | Адрес после переключения | Сторож после |
|---|---|---|---|
| Каталог | indexOf("store.tilda") | store2.tildaapi.<зона> | не находит подстроку |
| Формы | indexOf("forms.tilda") | forms2.tildacdn.com | не находит подстроку |
| Доставка | ".tildacdn.com" === endpointDelivery | 2.tildacdn.com | равенство ложно |
После первого переключения условие становится ложным навсегда — до перезагрузки страницы. Даже если сервер продолжает отвечать 429, третьего адреса не будет.
Ещё одна деталь той же семьи: таймаут в этих трёх файлах распознаётся тремя
разными способами — "timeout" === t.type в каталоге, t.isTimeout в доставке и
"timeout" == xhr.statusText в формах.
И одна разница, которая важнее, чем кажется: каталог при переключении зону
перечитывает, поэтому дублёр получается store2.tildaapi.ru. А формы уходят на
зашитый forms2.tildacdn.com — то есть сайт в зоне .ru при отказе форм
отправляет данные на .com.
402, 403 и остальные номера, которых в коде нет
Проверил каждый номер отдельно: искал его в тексте всех 53 файлов и оставлял
только те вхождения, рядом с которыми есть status, statusText, type или
readyState.
| Номер | Вхождений в контексте статуса |
|---|---|
| 401 | 0 |
| 402 | 0 |
| 403 | 0 |
| 404 | 2, оба внутри jQuery |
| 409 | 0 |
| 429 | 3, по одному в формах, каталоге и доставке |
| 503 | 0, но проходит по >= 500 |
Две находки 404 — это внутренности jQuery (status = responses.text ? 200 : 404,
обход старой ошибки в IE). Своей ветки на 404 у Тильды нет ни одной.
Как по тексту понять, какая служба не ответила
Практическая часть. Тексты у слоёв разные, и по ним слой опознаётся однозначно.
| Что видно | Какой слой | Где смотреть |
|---|---|---|
Retrying to load … Retry: 1 | не загрузился файл js или css | консоль, warn |
Failed to load … script: | тот же файл, все пять попыток исчерпаны | консоль, необработанное исключение |
Fetch error: Error: HTTP error! Status: 403 | новый слой магазина | консоль |
Initial fetch failed, retrying with fallback URL: | тот же слой, уходит на дублёра | консоль |
Error type [undefined]. Please, try again later. | каталог, и ответ пришёл пустым | консоль |
[403] Unknown error. Please, try again later. | доставка | консоль |
[403] … в блоке ошибки под формой | формы | сама страница |
Последняя строка — единственная, которую видит обычный посетитель. Ошибки каталога
и доставки уходят только в console.error: человек видит пустой список товаров или
не открывшийся расчёт доставки, и никакого сообщения.
Отдельный случай — обрыв связи, а не код ответа. Формы проверяют его так:
if (!secondFormTypeXhr || 0 == secondFormTypeXhr.status && tsDelta < 100)
allError.forEach(function (t) {
t.innerHTML = "Request error (sending form data). Please check internet connection..."
})
Статус 0 и меньше 100 мс с начала отправки — значит запрос не ушёл. Этот текст
про интернет не имеет отношения ни к 402, ни к 403.
Как проверить у себя
- Откройте страницу, где что-то не работает, с открытой консолью.
- Найдите в консоли строку с числом в квадратных скобках или со словом
Retrying. По таблице выше определите слой. - Перейдите на вкладку «Сеть», включите фильтр по
tilda, перезагрузите страницу. Красные строки покажут и адрес, и настоящий код ответа — тот самый, который стоит в скобках. - Если запросов к одному адресу пять с интервалами примерно 0, 1, 2 и 3 секунды —
это загрузчик файлов. Если два, ко второму адресу с цифрой
2в имени, — это переключение на дублёра.
Чего сделать нельзя: заставить код повторить запрос при 403. Список поводов зашит в минифицированный файл на стороне Тильды.
Что из этого следует
Одна общая мысль: в рантайме Тильды нет единого обработчика ошибок. Есть пять независимых слоёв, написанных в разное время, и они расходятся даже в мелочах — в способе распознать таймаут, в том, теряется ли зона при переключении, и в том, останется ли номер статуса в тексте сообщения.
Для практики это значит, что диагностировать нужно от слоя, а не от номера ошибки. Один и тот же 403 в одном месте вызовет пять запросов за шесть секунд, в другом — два, а в третьем тихо напечатает строчку в консоль, которую никто не прочитает.
Куда смотреть дальше: адреса всех служб и то, как они собираются из зоны, разобраны в «static.tildacdn.com». Какие файлы рантайма догружаются по требованию и почему их не видно в списке загрузок — в «переносе сайта с Тильды». Если нужно, чтобы ошибка не оставалась только в консоли, а показывалась посетителю, — это уже работа с бэкендом.