vyvchy
    Теми розділу

    03 · Веб і мережі для AQA

    HTTP статус-коди

    Зміст

    (status code) — тризначне число в стартовому рядку відповіді, яким сервер описує долю запиту: чи його опрацьовано і що вкладено у відповідь. Усі валідні коди лежать у діапазоні 100–599. Це найкоротше повідомлення в протоколі: одним числом сервер каже, на чиєму боці проблема і що робити далі.

    Для QA це базовий інструмент діагностики. Червоний тест, (flakiness) у , «нічого не працює на стейджі» — усе це починається з питання «а який код повернув сервер?». Ця глава — канон теми на сайті, інші глави посилаються сюди. Перевірка кодів усередині API-тестів — схеми, тіла помилок, контракт — лишається в розділі про API-тестування.

    Де живе код і що каже перша цифра

    Ось сирий (raw) обмін даними:

    GET /api/users/42 HTTP/1.1
    Host: example.com
    Accept: application/json
    
    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 53
    
    {"id": 42, "name": "Ada", "email": "ada@example.com"}

    Перший рядок відповіді — стартовий рядок статусу: версія протоколу, число і коротка (reason phrase, тут OK). Фраза необовʼязкова: RFC 9110 називає її optional, а RFC 9112 велить клієнту її ігнорувати як ненадійний канал. HTTP/3 узагалі не визначає способу переносити версію чи reason phrase зі статус-рядка HTTP/1.1, а в HTTP/2 статус несе єдиний псевдозаголовок :status. Висновок для тесту: число, а не рядок.

    Перша цифра задає клас відповіді, а клас — стратегію реакції.

    КласЗначенняЩо це означає для клієнта
    1xxінформаційні (informational)проміжна відповідь, а не остаточна — фінальний код прийде слідом
    2xxуспіх (success)запит опрацьовано — читай тіло й заголовки
    3xxперенаправлення (redirection)сам запит нормальний, але потрібна ще одна дія
    4xxпомилка клієнта (client error)запит містить помилку або не може бути виконаний
    5xxпомилка сервера (server error)сервер не впорався з очевидно валідним запитом

    Головна лінія розлому — між 4xx і 5xx, і проходить вона там, де ти шукаєш винного: «із запитом щось не так» проти «сервер не впорався».

    2xx: успіх теж буває різним

    200 OK — загальний успіх, але конкретний його зміст залежить від методу. 201 Created каже більше: запит виконано і як результат створено новий ресурс; це типова відповідь на POST і частину PUT, за домовленістю з заголовком Location на адресу нового ресурсу. 204 No Content — успіх без тіла, але зі значущими заголовками: спроба розпарсити з 204 JSON впаде, і тест покаже помилку парсингу там, де все спрацювало.

    Асерт на статус перевіряє контракт, а не «щось повернулося». 200 замість 201 на створення — легітимна претензія до API. Але й твій expect(200) зламається, щойно почне чесно віддавати 201.

    Метод — друга вісь, з якою код читають у парі

    Метод каже, що клієнт хоче зробити з ресурсом; код каже, чим це скінчилося. Нарізно вони втрачають половину змісту.

    POST просить цільовий ресурс обробити передане подання — тому 201 на ньому природний. PUT створює або повністю замінює стан ресурсу. DELETE просить розірвати звʼязок ресурсу з його функціональністю — це не гарантія фізичного видалення даних. 405 означає, що метод серверу відомий, але цільовий ресурс його не підтримує: адреса правильна, дієслово — ні. А 415 Unsupported Media Type через формат вмісту (Content-Type чи Content-Encoding), і саме він, а не 400, канонічний на непідтримуваний чи помилковий Content-Type.

    Ще одне, що видно лише в парі: HTTP описує наміри, а не гарантує поведінку реалізації. GET, який мутує стан, технічно можливий — і саме тому це баг контракту. І навпаки: «зайвий» OPTIONS перед POST у панелі Network — це CORS preflight, а не баг. Детальніше — у главі HTTP: методи, структура, заголовки.

    Тіло відповіді: коли воно є, а коли його немає

    Запит і відповідь мають однакову будову: стартовий рядок, необовʼязкові заголовки, порожній рядок як межа метаданих і необовʼязкове тіло (body). Що в тілі й скільки його — кажуть Content-Type і Content-Length.

    У запиті тіло зазвичай несуть POST, PUT і PATCH. Тіло в GET синтаксис не забороняє, але воно не має загальновизначеного значення й не може змінити зміст чи ціль запиту — тому складні структури «для читання» передають через POST.

    У відповіді тіла немає у трьох упізнаваних випадках: HEAD (тут це пряма заборона — сервер не має права надсилати вміст), 204 No Content і 304 Not Modified. Жоден із трьох не є помилкою чи «порожньою відповіддю»: заголовки в них значущі, а тіла там і не мало бути.

    4xx: сервер зрозумів і відмовив

    400 Bad Request — сервер не може або не буде обробляти запит через помилку клієнта, зокрема зламаний синтаксис. 422 Unprocessable Content — запит сформований коректно, але не може бути виконаний через семантичні помилки. Простими словами: 400 — «я не зрозумів, що ти написав», 422 — «я зрозумів, але так не можна».

    POST /api/users HTTP/1.1
    Content-Type: application/json
    
    {"name": "Grace", "email": "grace@example.com", "age": -5}
    
    HTTP/1.1 422 Unprocessable Content

    409 Conflict — запит конфліктує з поточним станом ресурсу: зайнятий email, дублікат, конкурентне редагування. 429 Too Many Requests — перевищено звернень; відповідь може містити Retry-After (невідʼємне число секунд або HTTP-дату).

    І про межу з 5xx: на відверто кривий ввід добре спроєктований сервер має відповісти 400 чи 422, а не впасти з 500. Тому 500 на невалідному вводі — знахідка сама собою, навіть коли ввід справді кривий.

    Наша практика (не канон). У пайплайні 429 — одне з найпідступніших джерел : паралельні гатять в один ендпоінт, спрацьовує ліміт, частина тестів червоніє «випадково». Симптом упізнаваний: локально зелено, у CI плаває. Стандарту під це немає; у командах, з якими ми працювали, лікується поважанням Retry-After у , зниженням паралелізму на «гарячих» ендпоінтах і моканням мережі, коли сам ліміт не є метою тесту.

    401 проти 403: хто ти й що тобі можна

    401 Unauthorized403 Forbidden
    Питання«Хто ти?»«Тобі можна?»
    Зміст за канономбракує валідних облікових данихсервер зрозумів запит і відмовляється виконати
    Обовʼязковий заголовокWWW-Authenticate
    Чи допоможе логінтак, це стадія автентифікаціїні, повторний вхід нічого не змінить

    Назви обох кодів читаються не так, як звучать, тому канон розводить їх за змістом: 401 — про брак валідних даних для входу, і відповідь мусить нести WWW-Authenticate (на практиці вимогу часто порушують); 403 — про відмову виконати зрозумілий запит. розводить і самі поняття: автентифікація — процедура встановлення особи, авторизація — наданий дозвіл.

    Два застереження, які RFC робить прямо. Перше: запит може бути заборонений із причин, не повʼязаних з обліковими даними, — тож «прийшов 403, отже роль не та» це гіпотеза, а не висновок. Друге: сервер, який хоче приховати саме існування забороненого ресурсу, може віддати 404 замість 403.

    Поруч живе третій код, який плутають із 401: 407 Proxy Authentication Required — той самий сценарій від , з парою Proxy-Authenticate і Proxy-Authorization. І окрема поведінка: на валідні, але недостатні облікові дані сервер має відповісти 403 — і, на відміну від 401 і 407, браузер нової спроби не запропонує. Канон теми — Автентифікація та авторизація.

    Наша практика (не канон). Оскільки сервер має право приховати ресурс під 404, у негативному сценарії з правами очікувати варто або 403, або 404. Специфікація описує право сервера, а не тестову практику, тож жорсткий асерт рівно на 403 робить тест крихким — звіряйся з контрактом конкретного API.

    OAuth 2.0: звідки в потоці беруться свої коди

    Сам механізм розібрано в главі Автентифікація та авторизація — тут лише коди, які цей потік додає. OAuth 2.0 — фреймворк делегованого доступу: він дає сторонньому застосунку обмежений доступ до сервісу від імені власника ресурсу. Ролей чотири: власник ресурсу, клієнт, сервер авторизації, сервер ресурсів. У потоці з кодом авторизації браузер спершу йде на ендпоінт авторизації (authorization endpoint), і лише потім клієнт міняє код на токен — уже сервер-до-сервера; саме тому пароль користувача до клієнта не потрапляє. Токен предʼявляють заголовком Authorization зі схемою Bearer: це Bearer-токен, який працює в кожного, хто ним володіє, тож TLS обовʼязковий.

    Що з цього дає коди, яких у звичайному CRUD не буває:

    • 401 тут не завжди про «протух логін»: це штатний сигнал оновити access-токен через refresh і повторити запит.
    • Заголовок Authorization знімається на редиректі — звідси «раптовий 401» у флоу, де клієнт токен точно слав.
    • Код авторизації одноразовий і короткоживучий: повторний обмін того самого коду має провалитися. Готовий негативний тест.
    • При невідповідному redirect_uri сервер має повідомити власника ресурсу і не редиректити на невалідну адресу — тобто очікувати тут 3xx неправильно. Звірка адреси робиться точним рядковим збігом; єдиний виняток — номер порту в localhost-адресах нативних застосунків.

    Термінологічна межа, яку варто тримати чітко: OAuth 2.0 — про авторизацію, а — надбудова над ним для автентифікації; «вхід через Google» технічно спирається саме на неї.

    Редиректи: 301, 302, 307, 308

    Редирект (redirect) — відповідь із кодом 3xx і заголовком Location, у якому вказано адресу, куди клієнту треба піти далі.

    СерверКлієнтСерверКлієнтклієнт іде за редиректом самPOST /old-path301 Moved Permanently, Location: /new-pathGET /new-path (метод підмінено, тіло втрачено)200 OKСерверКлієнтСерверКлієнтклієнт іде за редиректом самPOST /old-path301 Moved Permanently, Location: /new-pathGET /new-path (метод підмінено, тіло втрачено)200 OK

    Усередині класу дві незалежні осі: постійність і збереження методу з тілом.

    КодНазваПостійністьМетод і тіло
    301Moved Permanentlyпостійнонезмінні лише для GET; інший метод може стати GET
    302Foundтимчасовоте саме: не-GET може стати GET, тіло втрачається
    307Temporary Redirectтимчасовометод і тіло не змінюються
    308Permanent Redirectпостійнометод і тіло не змінюються

    Постійні редиректи означають, що стару адресу більше не слід використовувати; MDN додає, що й RSS-читалки оновлюють у себе адресу. Але RFC це саме пропонує, а не гарантує, і сам каже, що пропозицію зазвичай ігнорують. Тобто «після 301 усі оновлять посилання» — очікування, а не норма протоколу.

    Специфікація не передбачала зміни методу, але реальні клієнти її роблять — саме тому додали 307 і 308. Тонкість, яку варто тримати точно: нормативну заборону змінювати метод RFC 9110 прописує лише для 307, а про тіло не говорить у жодному з цих місць — його збереження документує MDN. Сам 308 молодший (червень 2014), і RFC застерігає, що розпізнають його не всюди; поширене «308 живе окремим документом» описує стан до 2022 року — RFC 9110 обсолетнув RFC 7538 і визначає цей код сам.

    Ще три члени родини. 303 See Other навпаки — навмисно змінює метод на GET (тіло втрачається); типово після обробки форми. 300 Multiple Choices — ручний вибір із переліку варіантів. А 304 Not Modified формально теж 3xx, але «перенаправляє» на локальну копію — про нього нижче.

    Петля редиректів — коли перенаправлення зациклюються і сторінка ніколи не завантажується; зазвичай це проблема конфігурації сервера, і якщо сервер її помічає, то віддає 500. Поруч тиха ціна: кожен редирект коштує зайвого запиту, тож внутрішні правильніше прибирати, виправивши посилання.

    І чому редиректу часто «не видно»: браузери й більшість HTTP-клієнтів ідуть за ним автоматично, помітний виняток — curl без прапорця -L. Перенаправити можна ще двома способами поза HTTP: <meta>-refresh у HTML і зміна адреси через DOM — обидва статус-коду не мають узагалі.

    Наша практика (не канон). Автопрямування зручне й небезпечне водночас: у коді ти бачиш фінальний 200, навіть якщо між ним був 301, — і мовчки пропускаєш зламану ланку ланцюга. Канонічного припису під тестову техніку немає; ми вимикаємо автопрямування й асертимо проміжний код разом із Location.

    // Playwright APIRequestContext: не йти за редиректом, перевірити його явно
    const res = await request.get('/old-path', { maxRedirects: 0 });
    expect(res.status()).toBe(301);
    expect(res.headers()['location']).toBe('/new-path');

    У UI-тестах ланцюг редиректів (на сторінку логіну й назад) — часте джерело флаку: очікування ловить URL, який існує лише мить. Надійніше чекати на фінальний стан сторінки.

    304 і умовні запити

    Коли збережена відповідь більше не придатна до віддачі напряму, кеш надсилає умовний запит (conditional request) із валідатором: ETag перетворюється на If-None-Match, Last-Modified — на If-Modified-Since.

    GET /api/config HTTP/1.1
    If-None-Match: "a1b2c3"
    
    HTTP/1.1 304 Not Modified
    ETag: "a1b2c3"

    304 означає: збережену відповідь можна оновити метаданими й перевикористати, тіло повторно не передається. Тонкість, за яку чіпляються: копія при 304 валідна, а не застаріла — RFC каже, що клієнт уже має валідне подання, і сервер лише спрямовує його скористатися збереженою копією так, ніби це тіло 200. MDN тут суперечить сам собі на одній сторінці: у прозі копія «була застарілою», у таблиці — «ще свіжа». Канон тут RFC. Механіка кешу — у главі Кешування.

    Ресурс і ендпоінт: чому 404 неоднозначний

    Ресурс (resource) — концептуальне відображення на набір сутностей, а не сама сутність у конкретний момент часу; ендпоінт (endpoint) — конкретна адреса, за якою до нього звертаються. Саме звідси росте неоднозначність 404: в API цей код покриває щонайменше три різні ситуації.

    1. Невідомий шлях — такого маршруту немає взагалі (одрук у слагу, не та версія API).
    2. Відсутній обʼєкт за валідним ендпоінтом — маршрут є, запису немає: видалено, не створено , не те середовище.
    3. Прихована заборона — ресурс існує, але сервер віддає 404 замість 403, щоб не розкрити факт його існування.

    Є й четверте, суто технічне джерело «404 на рівному місці»: схема й хост нечутливі до регістру, а шлях і рядок запиту — чутливі.

    Тому корисний рефлекс при 404 — не «баг», а три питання: чи існує маршрут, чи існує обʼєкт у цьому середовищі і чи не ховає сервер відмову. Ресурси й адреси розібрані в главах REST API та формати даних і URL і кодування.

    Посередники: 502 і 504 — і чому 503 не з їхньої родини

    Між браузером і застосунком майже завжди стоїть ще хтось. (intermediary) має три форми: проксі (proxy) — вузол, якого обирає клієнт; (gateway, він же reverse proxy) — вузол, що назовні поводиться як origin-сервер, а всередину транслює запити іншим серверам; (tunnel) — сліпий ретранслятор, який повідомлень не змінює.

    Браузер

    Шлюз / балансувальник

    Застосунок

    База даних

    502 / 504 — відповідає шлюз

    500 / 503 — відповідає застосунок

    Браузер

    Шлюз / балансувальник

    Застосунок

    База даних

    502 / 504 — відповідає шлюз

    500 / 503 — відповідає застосунок

    502 Bad Gateway за означенням віддає сервер, який діє як шлюз або проксі й отримав невалідну відповідь від внутрішнього сервера. 504 Gateway Timeout — той самий сервер у тій самій ролі, коли не дочекався відповіді вчасно. Обидва означають «зламалася ланка за посередником»: застосунок цих кодів міг не віддавати взагалі й навіть не знати про запит.

    А 503 Service Unavailable до їхньої родини не належить, хоч його регулярно туди зараховують: він означає, що сам сервер тимчасово не здатен обробити запит через перевантаження чи планові роботи. Різниця не косметична: 502/504 відправляють шукати за шлюзом, 503 — до самого сервісу. 500 Internal Server Error лишається узагальненою помилкою: сервер не знає, як опрацювати ситуацію, і не має доречнішого коду 5xx.

    І останнє: є посередники, які фільтрують чи перенаправляють трафік без відома відправника — на рівні протоколу вони не відрізняються від на шляху. Тому запит крізь корпоративний фаєрвол чи публічний Wi-Fi може повестися інакше без жодної зміни в застосунку.

    Наша практика (не канон). Масові однотипні 502/503/504 одразу після релізу — частіше стан середовища, ніж баг продукту: у процесі, стенд «прогрівається», база під навантаженням. Статистики під це немає, тож правило в нас процедурне: спершу переконайся, що середовище піднялося, і лише потім заводь баг на продукт.

    Типові помилки

    • Виглядає як помилка парсингу, а насправді 204 — тіла в цій відповіді й не мало бути.
    • Виглядає як зелений тест на створення, а насправді асерт прибитий до 200 — зламається, щойно ендпоінт почне віддавати 201.
    • Виглядає як «редиректу немає», а насправді клієнт пішов за ним сам і показав фінальний 200.
    • Виглядає як «POST перенаправився і зламався», а насправді це штатна поведінка 301/302 — метод підмінено на GET, тіло втрачено; зберігають їх 307 і 308.
    • Виглядає як проблема прав, а насправді 401 — користувач не автентифікований. Дзеркально: 403 повторним логіном не лікується.
    • Виглядає як «прав вистачає, бо 403 не прийшов», а насправді сервер приховав ресурс під 404.
    • Виглядає як 400 на помилковому Content-Type, а насправді канонічний код тут 415.
    • Виглядає як баг застосунку, а насправді 502/504 — зламана ланка за шлюзом, якої застосунок не віддавав.
    • Виглядає як застаріла копія при 304, а насправді копія валідна.

    Підсумок

    1. Перша цифра — стратегія реакції: 4xx означає «виправляй запит», 5xx — «сервер не впорався з валідним запитом». Фразу поруч із кодом не асертять: вона необовʼязкова.
    2. Код читають у парі з методом і тілом: 201 із Location на створення, 204 і 304 без тіла, 405 про метод, 415 про формат вмісту.
    3. У родині 3xx постійність і збереження методу — різні осі: 307/308 зберігають метод і тіло, 301/302 дозволяють підмінити метод на GET разом із втратою тіла.
    4. 401 — «не впізнав», 403 — «впізнав і відмовляю». Але 403 може бути не про права, а замість нього сервер має право віддати 404.
    5. 502 і 504 віддає посередник, 503 — сам сервер. Застосунок цих кодів міг не бачити, тож шукати причину в його коді марно.

    Можливі питання

    • «Чим 401 відрізняється від 403 Перевіряють, чи розводиш автентифікацію й авторизацію і чи знаєш про WWW-Authenticate. Сильна відповідь додає застереження: 403 не обовʼязково про ролі, а замість нього може прийти 404.
    • «301 проти 302 — у чому різниця?» Дивляться на дві осі одразу: постійність і долю методу з тілом. Згадка 307/308 — сильний хід.
    • «Що станеться з POST, якщо сервер відповість 301 Питання на розуміння: метод може стати GET, тіло — зникнути.
    • «Що означає 304 і хто його ініціює?» Слухають, чи знаєш, що запит умовний і починається з клієнта з валідатором.
    • «Прийшов 502 — де шукаєш причину?» Перевіряють, чи відрізняєш коди посередника від кодів застосунку.
    • «Чому 404 не завжди означає “немає такого запису”?» Хочуть почути розрізнення ресурсу й ендпоінта плюс варіант із прихованою забороною.
    • «Коли сервер має віддати 422, а коли 400 Класика на межу «не зрозумів» / «зрозумів, але так не можна».

    Джерела

    Де живе код і що каже перша цифра

    • RFC 9110 — HTTP Semantics — означення статус-коду, діапазон 100–599, класи відповідей і межа між 4xx і 5xx.
    • MDN — HTTP response status codes — довідник класів і окремих кодів із типовими причинами появи.
    • RFC 9112 — HTTP/1.1 (message syntax) — вимога до клієнта ігнорувати reason phrase як ненадійний канал.
    • RFC 9114 — HTTP/3 — у HTTP/3 спосіб переносити версію й reason phrase не визначено.
    • RFC 9113 — HTTP/2 — статус у HTTP/2 несе єдиний псевдозаголовок :status.

    2xx: успіх теж буває різним

    Метод — друга вісь, з якою код читають у парі

    Тіло відповіді: коли воно є, а коли його немає

    • MDN — HTTP messages — будова повідомлення й те, які методи мають тіло запиту.
    • RFC 9110 — HTTP SemanticsContent-Type і Content-Length, заборона тіла у відповіді на HEAD, семантика тіла в GET.
    • MDN — HTTP response status codes204 і 304 як відповіді без тіла зі значущими заголовками.

    4xx: сервер зрозумів і відмовив

    401 проти 403: хто ти й що тобі можна

    • RFC 9110 — HTTP Semantics — означення 401 і 403, вимога WWW-Authenticate, заборона з причин поза обліковими даними й право віддати 404 замість 403.
    • MDN — HTTP authentication407 із парою Proxy-Authenticate/Proxy-Authorization і поведінка при валідних, але недостатніх даних.
    • MDN — HTTP response status codes401 як «не автентифіковано», 403 як відомий серверу клієнт без прав.
    • ISTQB Glossary — автентифікація як процедура встановлення особи, авторизація як наданий дозвіл.

    OAuth 2.0: звідки в потоці беруться свої коди

    Редиректи: 301, 302, 307, 308

    • MDN — Redirections in HTTPLocation, постійні й тимчасові редиректи, доля методу й тіла в 301/302/303/307/308, 300, петля редиректів, ціна зайвого запиту, meta-refresh і JS-редирект.
    • RFC 9110 — HTTP Semantics — заміна посилань як пропозиція, яку зазвичай ігнорують; нормативна заборона змінювати метод для 307; вік коду 308.
    • curl — manual page — без прапорця -L клієнт за редиректом не йде.

    Редиректи: 301, 302, 307, 308

    • RFC 9111 — HTTP Caching — умовний запит із валідатором, пари ETag/If-None-Match і Last-Modified/If-Modified-Since, зміст 304.
    • RFC 9110 — HTTP Semantics — при 304 клієнт уже має валідне подання ресурсу.
    • MDN — Redirections in HTTP304 усередині класу 3xx як перенаправлення на локальну копію.
    • MDN — HTTP caching — ревалідація збереженої відповіді та її результат.

    Ресурс і ендпоінт: чому 404 неоднозначний

    Посередники: 502 і 504 — і чому 503 не з їхньої родини

    • RFC 9110 — HTTP Semantics — три форми посередника, означення 502 і 504 через роль шлюзу, 503 як стан самого сервера, перехоплювальні вузли поза вибором клієнта.
    • MDN — HTTP response status codes500 як узагальнена серверна помилка й типові причини 502, 503, 504.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.