Форма отправляется, появляется индикатор, и консоль сообщает о неожиданном символе < в JSON на позиции 0. Сама страница загружается нормально, поэтому первый рефлекс — обвинить парсер или туннель. Обычно ни тот ни другой не участвует: отправка — это отдельный запрос, а не загрузка страницы, и она вернулась с чем-то, что не является JSON. Работайте в том порядке, в котором браузер действительно это раскрывает, потому что четыре возможных ответа требуют четырёх разных исправлений.

Шаг 1: поймайте отправку, которая действительно произошла

Откройте панель «Сеть» до отправки, настройте журнал так, чтобы записи сохранялись при переходах, и сделайте одну безобидную тестовую отправку в аккаунте, который вам разрешено использовать. Выберите запрос, который инициировала сама форма, а не запрос документа страницы.

Осторожнее с фильтрами. Фильтр XHR или Fetch удобен, но если фреймворк всё ещё отправляет через скрытый iframe или динамически созданную форму, фильтр скроет единственный важный запрос. Сначала отправьте один раз без фильтра и посмотрите, что появилось.

Для выбранного запроса запишите шесть вещей до любого толкования: метод, полный URL вместе со строкой запроса, статус, цепочку перенаправлений, Content-Type ответа и первые строки тела. Справочник по панели «Сеть» в Chrome DevTools описывает представления Headers, Response и Initiator, где это находится, а аналогичные панели в других браузерах показывают те же поля.

Шаг 2: убедитесь, что это тот самый запрос

Удивительно много ошибок JSON — на самом деле ошибки маршрутизации. Если обработчик отправки выбрасывает исключение до вызова preventDefault, браузер выполняет собственный POST формы по адресу из атрибута action, и сервер отвечает полной HTML-страницей. Обработчик не выполняется, значит JSON вообще не запрашивался.

Сравните URL на панели с URL, который вызывает ваш JavaScript. Ищите перенаправление, заканчивающееся в другом месте, отсутствующий или лишний завершающий слэш, несовпадение метода, когда обработчик шлёт GET, а конечная точка принимает POST, или форму, отправляющую на относительный путь со страницы, которая переехала. Проследите цепочку перенаправлений до конца и запишите конечный URL, а не первый.

Затем проверьте инициатора. Если запрос пришёл из навигации документа, а не из вашего скрипта, проблема уже найдена, и никакие изменения в парсере ответа не помогут.

Шаг 3: разложите результат по четырём категориям

Каждый результат относится ровно к одной из них, и категория определяет, где смотреть дальше.

What you seeCategoryWhere to look
Request never finishes, TLS or connection errorNo usable responseEndpoint reachability, certificate, front-end timeout
Request completes, script gets an opaque failureAccess blockedCross-origin policy, credentials mode, missing response header
200 with an HTML body or a login formWrong representationRedirect, expired session, route fallback, error handler
Body begins with a notice then valid JSONWrong representationServer-side warning output before serialisation
Valid JSON, framework rejects itContract mismatchEnvelope shape, field names, value types

Средние строки чаще всего диагностируют неверно, потому что статус 200. Страница входа, достигнутая после перенаправления, — совершенно нормальный ответ HTTP и очень плохой документ JSON. Тип содержимого application/json это не исправляет: это лишь ярлык, а неверно помеченное тело всё равно не парсится. «Доступ заблокирован» — строка, которая выглядит как завершённый запрос: панель показывает статус, но политика кросс-доменных запросов удерживает тело, поэтому для вызывающего скрипта оно так же бесполезно, как таймаут.

Четвёртая строка столь же обычна на старых стеках PHP и фреймворков. Предупреждение об устаревании, напечатанное до закодированной нагрузки, даёт тело, которое не примет ни один парсер JSON, и часто это предупреждение и есть всё объяснение.

Шаг 4: читайте ошибку парсера буквально

Текст ошибки называет первый символ, сломавший разбор, и это обычно определяет категорию без всяких догадок.

Error text (Chrome/V8)What it means
Unexpected token < in JSON at position 0The body starts with markup: an HTML page or an error document
Unexpected end of JSON inputEmpty body, or the response was truncated
Unexpected token o in JSON at position 1You parsed something already parsed, typically an object rather than text
Unexpected token uThe body starts with the bare word undefined, usually an empty response rendered as a string

Пустое тело со статусом 200 — отдельный случай: конечная точка отработала, ничего не произвела, а что-то ниже по потоку приняло это за успех. Это сбой контракта приложения, а не транспорта, и его место в следующем шаге, а не в прокси. Firefox и Safari формулируют те же четыре сбоя иначе, поэтому сопоставляйте по символу или по пустому телу, а не по точной строке.

Шаг 5: исправьте слой, на который указывают доказательства

При проблеме маршрутизации исправьте запрос: поправьте action, не дайте обработчику провалиться, согласуйте конечную точку и метод и проверьте снова. При проблеме представления исправьте то, что породило тело, и выясните, почему документ ошибки возвращается там, где ожидались данные. При проблеме контракта прочитайте реальную ожидаемую структуру фреймворка для вашей версии и приведите ответ к ней, вместо того чтобы ослаблять клиент.

Ответ с заблокированным доступом требует отдельного прохода, потому что сам запрос может быть корректным, а тело может так и не дойти до вашего кода. Сравните Access-Control-Allow-Origin ответа с точным Origin, который отправил браузер, и помните, что подстановочный знак не удовлетворяет запрос с учётными данными. Свяжите credentials: 'include' на клиенте с Access-Control-Allow-Credentials: true на сервере и проверьте, не использовал ли вызов режим no-cors, где непрозрачное тело — документированный результат, а не дефект.

Замена парсера на что-то более снисходительное — единственное исправление, которого стоит избегать. Выполнение ответа как кода или срезание текста до тех пор, пока он не разберётся, скрывает несоответствие контракта и добавляет поверхность для инъекций в обмен на тишину. Точно так же статус нужно проверять отдельно от тела: 200 с объектом ошибки уровня приложения нормален во многих API, и его нужно обрабатывать, читая нагрузку.

Когда нужна помощь другого человека, поделитесь минимальной записью — метод, URL, статус, конечный URL после перенаправлений, тип содержимого ответа, форма тела и ожидаемый контракт — вместо необработанного экспорта. Сетевые архивы содержат cookie, токены, строки запроса и персональные данные, которые были в форме, а автоматическая очистка — не проверка.

  • Поймайте запрос без фильтра, прежде чем менять любой код.
  • Убедитесь, что инициатором был ваш скрипт, а конечный URL — ваша конечная точка.
  • Классифицируйте ответ, прежде чем трогать парсер.
  • Исправьте маршрутизацию, доступ, представление или контракт и никогда — ослабляя парсер.

Похожие статьи