HTTP протокол

Зачем нужно

HTTP (HyperText Transfer Protocol) — основной протокол передачи данных в вебе. Каждый раз, когда браузер загружает страницу, отправляет форму или SPA делает API-запрос — используется HTTP. Понимание HTTP необходимо для работы с API, отладки сетевых проблем и оптимизации производительности.

Где используется

  • Загрузка веб-страниц (HTML, CSS, JS, изображения)
  • API-запросы (REST, GraphQL)
  • Отправка форм
  • Загрузка/скачивание файлов
  • WebSocket-хендшейк начинается как HTTP

Структура HTTP-запроса

POST /api/users HTTP/1.1          ← Стартовая строка (метод, путь, версия)
Host: example.com                  ← Заголовки
Content-Type: application/json
Authorization: Bearer eyJ...
Content-Length: 45
                                   ← Пустая строка (разделитель)
{"name": "Антон", "age": 25}     ← Тело запроса (body)

Структура HTTP-ответа

HTTP/1.1 201 Created              ← Стартовая строка (версия, статус, текст)
Content-Type: application/json
Set-Cookie: session=abc123
Cache-Control: no-cache
                                   ← Пустая строка
{"id": 1, "name": "Антон"}       ← Тело ответа

HTTP-методы

// GET — получить данные (без тела запроса)
fetch('/api/users');
fetch('/api/users/42');

// POST — создать ресурс
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Антон', email: 'a@mail.ru' }),
});

// PUT — полная замена ресурса
fetch('/api/users/42', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Антон', email: 'new@mail.ru', age: 26 }),
});

// PATCH — частичное обновление ресурса
fetch('/api/users/42', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'new@mail.ru' }),
});

// DELETE — удалить ресурс
fetch('/api/users/42', { method: 'DELETE' });

// HEAD — как GET, но без тела ответа (только заголовки)
// OPTIONS — узнать допустимые методы (используется в CORS preflight)

Свойства методов

Метод Идемпотентный Безопасный Тело запроса Тело ответа
GET Да Да Нет Да
POST Нет Нет Да Да
PUT Да Нет Да Да
PATCH Нет Нет Да Да
DELETE Да Нет Опционально Опционально
HEAD Да Да Нет Нет

Идемпотентный — повторный запрос даёт тот же результат. Безопасный — не изменяет данные на сервере.

Коды состояния (Status Codes)

1xx — Информационные

100 Continue          — сервер готов принять тело запроса
101 Switching Protocols — переключение протокола (WebSocket upgrade)

2xx — Успех

200 OK                — запрос выполнен успешно
201 Created           — ресурс создан (после POST)
204 No Content        — успех, но тело ответа пустое (после DELETE)

3xx — Перенаправление

301 Moved Permanently — ресурс переехал навсегда (SEO: обновить ссылки)
302 Found             — временное перенаправление
304 Not Modified      — ресурс не изменился (используй кэш)
307 Temporary Redirect — временный redirect, метод сохраняется
308 Permanent Redirect — постоянный redirect, метод сохраняется

4xx — Ошибка клиента

400 Bad Request       — неверный запрос (невалидный JSON, не те параметры)
401 Unauthorized      — не авторизован (нет/невалидный токен)
403 Forbidden         — доступ запрещён (авторизован, но нет прав)
404 Not Found         — ресурс не найден
405 Method Not Allowed — метод не поддерживается для этого URL
409 Conflict          — конфликт (ресурс уже существует)
422 Unprocessable Entity — валидация не прошла
429 Too Many Requests — превышен лимит запросов (rate limiting)

5xx — Ошибка сервера

500 Internal Server Error — общая ошибка сервера
502 Bad Gateway           — сервер-прокси получил невалидный ответ
503 Service Unavailable   — сервер временно недоступен
504 Gateway Timeout       — прокси не дождался ответа от upstream

Заголовки (Headers)

Заголовки запроса

fetch('/api/data', {
  headers: {
    // Тип содержимого
    'Content-Type': 'application/json',

    // Авторизация
    'Authorization': 'Bearer eyJhbGciOiJIUzI1...',

    // Что клиент принимает
    'Accept': 'application/json',
    'Accept-Language': 'ru,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',

    // Кэширование
    'Cache-Control': 'no-cache',
    'If-None-Match': '"abc123"',  // ETag для 304

    // Куки
    'Cookie': 'session=xyz; theme=dark',

    // Пользовательские
    'X-Request-ID': 'uuid-123',
  },
});

Заголовки ответа

Content-Type: application/json; charset=utf-8
Content-Length: 1234
Cache-Control: max-age=3600, public
ETag: "abc123"
Set-Cookie: session=xyz; HttpOnly; Secure; SameSite=Strict
Access-Control-Allow-Origin: https://example.com
X-RateLimit-Remaining: 99

HTTP/2

HTTP/1.1:
  - Один запрос за соединение (или pipelining, но с head-of-line blocking)
  - Заголовки в текстовом виде
  - Нет приоритетов

HTTP/2:
  - Мультиплексирование: много запросов по одному соединению
  - Бинарный протокол (быстрее парсить)
  - Сжатие заголовков (HPACK)
  - Server Push: сервер отправляет ресурсы до запроса
  - Приоритизация потоков
HTTP/1.1: 6 параллельных соединений к одному домену
┌──req1──resp1──┐ ┌──req2──resp2──┐ ┌──req3──resp3──┐
├──req4──resp4──┤ ├──req5──resp5──┤ ├──req6──resp6──┤

HTTP/2: 1 соединение, все запросы параллельно
┌──req1──req2──req3──req4──req5──req6──┐
│  resp3  resp1  resp5  resp2  resp4  resp6  │
└──────────────────────────────────────┘

HTTP/3

HTTP/2 → TCP (head-of-line blocking на уровне TCP)
HTTP/3 → QUIC (UDP) — нет blocking, быстрее на плохих сетях

Преимущества HTTP/3:
- 0-RTT connection setup (мгновенное подключение при повторном визите)
- Нет head-of-line blocking
- Миграция соединения при смене сети (Wi-Fi → 4G)
- Встроенное шифрование (TLS 1.3)

Практический пример: работа с fetch

// Полный пример с обработкой ошибок
async function apiRequest(url, options = {}) {
  const config = {
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
    ...options,
  };

  if (options.body && typeof options.body === 'object') {
    config.body = JSON.stringify(options.body);
  }

  const response = await fetch(url, config);

  // fetch НЕ бросает ошибку при 4xx/5xx
  if (!response.ok) {
    const error = await response.json().catch( => ({}));
    throw new Error(error.message || `HTTP ${response.status}`);
  }

  // 204 No Content — нет тела
  if (response.status === 204) return null;

  return response.json();
}

// Использование
try {
  const user = await apiRequest('/api/users', {
    method: 'POST',
    body: { name: 'Антон', email: 'a@mail.ru' },
  });
  console.log('Создан:', user);
} catch (err) {
  console.error('Ошибка:', err.message);
}

Частые ошибки

  1. PUT вместо PATCH — PUT заменяет ресурс целиком, PATCH обновляет частично
  2. GET с телом — технически возможно, но на практике серверы/прокси могут отбросить тело
  3. Не проверяют response.ok — fetch не бросает ошибку при 404/500
  4. Путают 401 и 403 — 401 = «кто ты?» (нет токена), 403 = «тебе нельзя» (есть токен, нет прав)
  5. Нет Content-Type — сервер не знает как парсить тело запроса
  6. Не обрабатывают сетевые ошибки — fetch бросает ошибку только при сетевых проблемах, не при HTTP-ошибках

Практика

  1. Отправить GET, POST, PUT, PATCH, DELETE запросы через fetch
  2. Изучить запросы в DevTools → Network: заголовки, статусы, тело
  3. Написать обёртку над fetch с обработкой ошибок и retry
  4. Проверить HTTP/2: открыть сайт → DevTools → Network → Protocol
  5. Реализовать кэширование через ETag и If-None-Match

Связанные темы

Ресурсы