AJAX-форма очікує JSON, але отримує HTML: розберіть відповідь крок за кроком
Відповідь зі статусом 200 не доводить, що це JSON. Пройдіть панель «Мережа» крок за кроком і розділіть збої транспорту, подання та контракту.
Форма надсилається, з’являється індикатор, і консоль повідомляє про неочікуваний символ < у 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 see | Category | Where to look |
|---|---|---|
| Request never finishes, TLS or connection error | No usable response | Endpoint reachability, certificate, front-end timeout |
| Request completes, script gets an opaque failure | Access blocked | Cross-origin policy, credentials mode, missing response header |
| 200 with an HTML body or a login form | Wrong representation | Redirect, expired session, route fallback, error handler |
| Body begins with a notice then valid JSON | Wrong representation | Server-side warning output before serialisation |
| Valid JSON, framework rejects it | Contract mismatch | Envelope 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 0 | The body starts with markup: an HTML page or an error document |
| Unexpected end of JSON input | Empty body, or the response was truncated |
| Unexpected token o in JSON at position 1 | You parsed something already parsed, typically an object rather than text |
| Unexpected token u | The 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 — ваша кінцева точка.
- Класифікуйте відповідь, перш ніж торкатися парсера.
- Виправте маршрутизацію, доступ, подання або контракт і ніколи — послаблюючи парсер.