Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «HTTP і продуктивність»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

Запит складається з рядка запиту, заголовків і (необов'язково) тіла:

POST /api/orders HTTP/1.1
Host: shop.example.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
Idempotency-Key: 8f14e45f-ceea-467f-a0d6-2a1e6c0c4f11

{"product_id": 42, "qty": 2}
  • метод (GET, POST, PATCH...) - що зробити;
  • шлях і рядок запиту - з яким ресурсом;
  • заголовки - метадані: формат, автентифікація, кешування;
  • тіло - дані (для GET тіла зазвичай немає).

Відповідь - рядок статусу, заголовки, тіло:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/orders/1057
Cache-Control: no-store

{"data": {"id": 1057, "status": "new"}}

Заголовки, які варто знати для API:

Заголовок Навіщо
Content-Type формат тіла, яке надсилається
Accept формат, який клієнт хоче отримати
Authorization облікові дані (Bearer-токен)
Location адреса створеного ресурсу (з 201) чи статусу операції (з 202)
Cache-Control, ETag кешування й умовні запити
Retry-After коли повторити (з 429, 503)
X-Request-Id / traceparent наскрізний ідентифікатор для логів і трасування

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

  • Content-Type не відповідає тілу - сервер не розбере JSON, надісланий як text/plain;
  • відсутній Accept: application/json у запитах до Laravel - при помилці валідації замість JSON 422 прийде редирект на попередню сторінку;
  • статус 200 з {"error": ...} у тілі - клієнти, проксі й моніторинг орієнтуються на код статусу, тож помилка має бути помилкою на рівні HTTP;
  • власні заголовки з префіксом X- - застарілий звичай (RFC 6648); нові заголовки називають без нього.

Налагодження: curl -i показує заголовки відповіді, curl -v - і запиту; у браузері - вкладка Network.

Докладніше в документації: Огляд HTTP

Два заголовки, які часто плутають, описують різні напрямки:

  • Content-Type - формат тіла цього повідомлення. У запиті - що надсилає клієнт, у відповіді - що повертає сервер;
  • Accept - формат, який клієнт готовий отримати у відповідь.
POST /api/reports
Content-Type: application/json
Accept: text/csv

{"from": "2026-09-01", "to": "2026-09-30"}

Клієнт надсилає JSON, а хоче отримати CSV.

Узгодження вмісту (content negotiation) - сервер обирає формат відповіді за Accept:

Accept: application/json;q=1.0, text/csv;q=0.5

q - вага переваги від 0 до 1. Сервер повертає найкращий з підтримуваних форматів, а якщо не підтримує жоден - 406 Not Acceptable. Якщо сервер не розуміє формат тіла запиту - 415 Unsupported Media Type.

Інші заголовки узгодження:

  • Accept-Language - мова (повідомлення помилок, перекладені поля);
  • Accept-Encoding - стиснення (gzip, br, zstd);
  • у відповіді - Vary: Accept, Accept-Language, щоб кеші й CDN зберігали окремі копії для різних варіантів.

Медіатипи для API:

  • application/json - основний;
  • application/problem+json - помилки за RFC 9457;
  • application/vnd.api+json - JSON:API;
  • multipart/form-data - завантаження файлів;
  • власні «вендорні» типи з версією (application/vnd.shop.v2+json) - один зі способів версіонування.

У Laravel:

  • $request->expectsJson() / wantsJson() перевіряють Accept - від нього залежить, чи поверне Laravel помилки валідації й винятки як JSON;
  • $request->isJson() - чи тіло запиту JSON (за Content-Type);
  • $request->getAcceptableContentTypes() - список з урахуванням ваг.

Практичний підсумок: API-клієнти мають завжди надсилати Accept: application/json, а сервер - завжди виставляти правильний Content-Type відповіді з кодуванням (application/json для JSON вважається UTF-8 за специфікацією).

Докладніше в документації: Узгодження вмісту

Пагінація зі зміщенням (offset) - номер сторінки:

GET /api/posts?page=3&per_page=20
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 40;

Курсорна пагінація - «продовжити після цього запису»:

GET /api/posts?cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wOS0zMCJ9
SELECT * FROM posts WHERE created_at < '2026-09-30 12:00:00' ORDER BY created_at DESC LIMIT 20;

Курсор - закодоване значення ключа сортування останнього елемента попередньої сторінки.

Порівняння:

Зміщення Курсор
перехід на довільну сторінку так ні, лише вперед/назад
загальна кількість сторінок так (потрібен COUNT(*)) зазвичай ні
швидкість на далеких сторінках падає: база читає й відкидає OFFSET рядків стабільна: пошук за індексом
нові записи під час гортання дублікати й пропуски коректно

Проблема зміщення з даними, що змінюються: поки користувач на сторінці 2, з'явилося 5 нових постів. Сторінка 3 тепер починається на 5 записів «раніше» - частину постів він побачить двічі. Курсор прив'язаний до значення, а не до позиції, і такої проблеми не має.

У Laravel:

Post::latest()->paginate(20);         // зміщення + COUNT(*), номери сторінок
Post::latest()->simplePaginate(20);   // зміщення без COUNT(*), лише «далі/назад»
Post::latest()->cursorPaginate(20);   // курсор

cursorPaginate вимагає, щоб сортування було за унікальною комбінацією колонок (зазвичай додають id як останній ключ) і щоб для неї був індекс.

Що обрати:

  • адмінки, таблиці з переходом на сторінку N, невеликі обсяги - зміщення;
  • стрічки, нескінченний скрол, мобільні застосунки, синхронізація й експорт великих обсягів - курсор.

У відповіді API варто віддавати посилання на наступну сторінку (links.next чи заголовок Link), а не змушувати клієнта збирати URL самостійно - тоді механізм пагінації можна змінити без зміни клієнтів.

Докладніше в документації: Laravel: курсорна пагінація

HEAD - те саме, що GET, але без тіла відповіді: сервер повертає лише статус і заголовки.

HEAD /api/exports/2026-09.csv

HTTP/1.1 200 OK
Content-Type: text/csv
Content-Length: 48211337
Last-Modified: Wed, 01 Oct 2026 03:00:00 GMT
ETag: "a1b2c3"

Навіщо:

  • дізнатися розмір файлу перед завантаженням (смуга прогресу, перевірка місця);
  • перевірити, чи ресурс існує чи змінився (ETag, Last-Modified) без передачі вмісту;
  • перевірка посилань - сканери битих посилань і моніторинг доступності.

Заголовки відповіді на HEAD мають бути такими самими, як для GET. У Laravel маршрути GET автоматично відповідають і на HEAD - фреймворк просто відкидає тіло.

OPTIONS - запит про можливості ресурсу: які методи підтримуються.

OPTIONS /api/orders/42

HTTP/1.1 204 No Content
Allow: GET, PATCH, DELETE

Головне застосування на практиці - попередній запит CORS (preflight). Перед «непростим» запитом з іншого джерела (методи PUT/PATCH/DELETE, заголовок Authorization, Content-Type: application/json) браузер сам надсилає OPTIONS:

OPTIONS /api/orders/42
Origin: https://app.example.com
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: authorization, content-type

Сервер відповідає дозволами (Access-Control-Allow-*), і лише потім іде справжній запит. У Laravel це робить middleware HandleCors з config/cors.php.

Що варто знати:

  • обидва методи безпечні й ідемпотентні - не повинні змінювати стан;
  • preflight - додатковий запит на кожен «непростий» запит з іншого джерела. Access-Control-Max-Age дозволяє браузеру кешувати дозвіл і не повторювати OPTIONS щоразу;
  • автентифікація для OPTIONS: браузер не надсилає Authorization у preflight. Якщо middleware автентифікації відхиляє OPTIONS з 401, CORS-запити ламаються - preflight має оброблятися до автентифікації (як це й робить глобальний HandleCors);
  • 405 Method Not Allowed разом із заголовком Allow - правильна відповідь на непідтримуваний метод.

Докладніше в документації: Метод HEAD

JSON добре стискається: повторювані ключі й структура дають зменшення у 5-10 разів. Для мобільних клієнтів і великих списків це суттєва економія часу й трафіку.

Як це працює:

GET /api/products
Accept-Encoding: zstd, br, gzip

HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding
  1. клієнт перелічує в Accept-Encoding алгоритми, які розуміє;
  2. сервер обирає один, стискає тіло й повідомляє про це в Content-Encoding;
  3. клієнт (браузер, fetch, HTTP-клієнт) розпаковує автоматично.

Vary: Accept-Encoding обов'язковий для кешів і CDN - інакше стиснена копія може дістатися клієнту, що її не розуміє.

Алгоритми:

Алгоритм Особливість
gzip підтримується всюди, швидкий, середнє стиснення
br (Brotli) краще стиснення за gzip, особливо для тексту; підтримка в усіх сучасних браузерах
zstd (Zstandard) дуже швидке стиснення й розпакування при хорошому коефіцієнті; підтримується в Chrome і Firefox, Safari - з 2025 року

Сервер обирає найкращий з того, що підтримує клієнт, і зазвичай лишає gzip як запасний варіант.

Де вмикати: не в PHP, а на рівні вебсервера чи CDN - Nginx (gzip, модуль brotli), Caddy (encode zstd gzip), Cloudflare. Там стиснення ефективніше й не займає воркери PHP.

Що варто знати:

  • малі відповіді не стискають: для тіла в кількасот байтів накладні витрати більші за виграш (типовий поріг - 1 КБ);
  • вже стиснене (зображення, архіви, PDF) повторно не стискають;
  • рівень стиснення - компроміс з процесором: для динамічних відповідей - середній рівень, максимальний - лише для статики, стисненої заздалегідь;
  • BREACH: стиснення відповідей, що містять і секрет (CSRF-токен), і дані, підконтрольні атакувальнику, на HTTPS дає змогу вгадувати секрет за розміром відповіді. Для JSON API з токенами в заголовках, а не в тілі, ризик невеликий, але про атаку варто знати;
  • тіло запиту клієнти зазвичай не стискають - Content-Encoding у запиті сервер має підтримувати окремо.

Перевірка: curl -H 'Accept-Encoding: br' -I https://api.example.com/products - у відповіді має бути Content-Encoding.

Докладніше в документації: Стиснення в HTTP