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.
Два заголовки, які часто плутають, описують різні напрямки:
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 самостійно - тоді механізм пагінації можна змінити без зміни клієнтів.
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- правильна відповідь на непідтримуваний метод.
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
- клієнт перелічує в
Accept-Encodingалгоритми, які розуміє; - сервер обирає один, стискає тіло й повідомляє про це в
Content-Encoding; - клієнт (браузер,
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.