HTTP-заголовки — метаинформация запроса и ответа
Заголовки в HTTP — это пары «ключ-значение», которые несут метаинформацию о запросе или ответе. Они определяют формат тела, аутентификацию, кэширование, кодировку, и десятки других вещей. Junior, который читает заголовки, отлаживает API за минуты. Junior, который их игнорирует, тратит часы и пишет в Stack Overflow вопросы вроде «почему мой POST возвращает 415».
В этом уроке разберём базовый набор заголовков, без которых HTTP не работает, плюс несколько специфических для DE.
Базовые свойства заголовков
Прежде чем разбирать конкретные — несколько технических деталей:
Заголовки запроса
Что Junior шлёт на сервер. Вот основные:
Host — куда идём
Host — единственный обязательный заголовок в HTTP/1.1. Без него запрос невалиден.
GET /api/users HTTP/1.1
Host: api.example.com
Зачем: один сервер (один IP-адрес) может обслуживать несколько доменов (api.example.com, www.example.com, admin.example.com). По заголовку Host сервер понимает, какой сайт вы запрашиваете. Это называется virtual hosting.
В HTTP/2 и HTTP/3 заголовок Host заменён на специальный pseudo-header :authority, но смысл тот же. curl и библиотеки сами добавляют его на основе URL.
Content-Type — что в теле запроса
Когда вы шлёте тело (POST/PUT/PATCH), сервер должен знать, как его парсить. Это говорится через Content-Type.
# JSON:
curl -X POST https://httpbin.org/post \
-H 'Content-Type: application/json' \
-d '{"name":"Alice"}'
# Form-encoded (стандартная HTML-форма):
curl -X POST https://httpbin.org/post \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'name=Alice&age=30'
# Plain text:
curl -X POST https://httpbin.org/post \
-H 'Content-Type: text/plain' \
-d 'just some text'
Если не указать Content-Type явно, curl по умолчанию ставит application/x-www-form-urlencoded. Если ваш API ждёт JSON, а вы прислали без Content-Type — получите 415 Unsupported Media Type или 400 Bad Request. Это самая частая причина ‘не работает мой POST на ровном месте’.
Content-Length — размер тела
Размер тела в байтах. Curl и библиотеки сами вычисляют и добавляют:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 28
{"name":"Alice","age":30}
Если заявленный Content-Length не совпадает с реальным размером тела — сервер закроет соединение с ошибкой. Альтернатива — Transfer-Encoding: chunked для случаев, когда размер не известен заранее.
Accept — что готов принять в ответе
Accept говорит серверу, какие форматы ответа клиент понимает. Сервер выбирает подходящий (это называется content negotiation).
# Я хочу только JSON:
curl -H 'Accept: application/json' https://api.example.com/users/42
# Я хочу JSON, или XML если JSON нет:
curl -H 'Accept: application/json, application/xml;q=0.9' https://api.example.com/users/42
# Я приму что угодно:
curl -H 'Accept: */*' https://api.example.com/users/42
q=0.9 — это quality factor, приоритет. Максимум 1.0 (по умолчанию), минимум 0. Сервер выбирает наиболее предпочитаемый формат, который умеет.
В REST API обычно сервер всё равно отдаёт JSON, и Accept — формальность. Но если API поддерживает XML legacy-режим, или умеет CSV — Accept важен.
Accept-Encoding — компрессия
Какие алгоритмы сжатия поддерживает клиент. Сервер сжимает ответ — клиент разжимает. Экономия трафика 5-20x для текста (HTML, JSON).
Компрессия на лету: gzip, zstd и друзьяcurl -v -H 'Accept-Encoding: gzip' https://api.github.com/users/torvalds 2>&1 | grep -E 'content-encoding|content-length'
# > Accept-Encoding: gzip
# < content-encoding: gzip
# < content-length: 1234 <- сжатый размер. Без gzip было бы 4-5K
Распространённые алгоритмы:
- gzip — старый, везде поддерживается
- deflate — почти gzip, но другая обёртка
- br (Brotli) — современный, лучше сжимает (на ~20% лучше gzip), Chrome+Firefox+Safari поддерживают
- zstd — самый новый (Facebook), пока редко в API
Curl автоматически разжимает ответ, если поставить флаг --compressed:
curl --compressed https://api.github.com/users/torvalds
# Шлёт Accept-Encoding: gzip, deflate, br и сам разжимает
Authorization — кто я
Главный заголовок аутентификации. Подробно — модуль 8. Базово:
# Bearer token (OAuth2, JWT):
curl -H 'Authorization: Bearer ghp_abc123def456...' https://api.github.com/user
# Basic auth (login:password в base64):
curl -H 'Authorization: Basic dXNlcjpwYXNz' https://httpbin.org/basic-auth/user/pass
# или эквивалентно:
curl -u user:pass https://httpbin.org/basic-auth/user/pass
# API key (часто кастомные заголовки, не Authorization):
curl -H 'X-API-Key: my_secret_key' https://api.example.com/data
User-Agent — кто этот клиент
Идентификация клиента: имя, версия, иногда платформа.
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ...
User-Agent: curl/8.4.0
User-Agent: python-requests/2.34.0
User-Agent: MyETLBot/1.0 ([email protected])
Многие API логируют User-Agent для аналитики. Некоторые (GitHub, Reddit) требуют осмысленный User-Agent — без него возвращают 403. Хорошая практика для своих ETL — поставить узнаваемое имя плюс контакт:
import requests
session = requests.Session()
session.headers.update({
'User-Agent': 'MyCompany-ETL/1.0 ([email protected])',
})
Если что-то сломается — owner API сможет связаться с вами.
Заголовки ответа
Что сервер шлёт обратно. Многие пересекаются с request, но есть и специфические.
Content-Type — что в теле ответа
Тот же заголовок, что и в запросе, но теперь говорит о теле ответа.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"name":"torvalds"}
charset=utf-8 — параметр заголовка. Указывает кодировку текста. Для JSON по умолчанию UTF-8 (RFC 8259), но многие явно указывают.
Content-Length и Transfer-Encoding
Размер ответа. Один из них обязателен (иначе клиент не знает, когда остановиться):
Content-Length: 1234— известный размер.Transfer-Encoding: chunked— потоковая передача, размер заранее неизвестен (например, генерируется в процессе).
Date — время сервера
Время на сервере в момент ответа, в формате RFC 7231:
Date: Thu, 14 May 2026 12:34:56 GMT
Полезно для отладки расхождений по времени и для кэширования (сравнить с Last-Modified).
Server — что за сервер
Идентификация сервера. Часто скрывают (security through obscurity):
Server: nginx/1.25.0
Server: gunicorn/19.9.0
Server: cloudflare
Cache-Control — как кэшировать
Главный заголовок управления кэшированием. Подробно — модуль 3.
Cache-Control: no-cache # каждый раз проверять у сервера
Cache-Control: no-store # вообще не кэшировать (sensitive data)
Cache-Control: max-age=3600 # кэшировать 1 час
Cache-Control: public, max-age=86400 # публичный кэш, 1 день
Cache-Control: private, max-age=300 # только в браузере, 5 минут
Location — куда идти дальше
Используется в редиректах (3xx) и при создании ресурса (201):
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-url
HTTP/1.1 201 Created
Location: /api/users/42
ETag — версия ресурса
Уникальный идентификатор текущей версии ресурса. Используется для conditional requests и кэширования. Подробно — модуль 3.
ETag: "abc123def456"
# Клиент шлёт его обратно для проверки:
# If-None-Match: "abc123def456"
# Сервер ответит 304 Not Modified, если ресурс не изменился
Cookie / Set-Cookie
Cookie — механизм, который позволяет серверу хранить данные у клиента и получать их обратно на каждом запросе. Основа большинства веб-сессий.
Важные атрибуты Set-Cookie:
HttpOnly— JavaScript не может прочитать (защита от XSS).Secure— отправляется только по HTTPS.SameSite=Strict|Lax|None— когда отправлять на cross-site запросы (защита от CSRF).Max-Age=3600— TTL в секундах. Без него — session cookie (живёт до закрытия браузера).Expires=...— старый формат TTL, через дату.Domain=...; Path=...— на какие URL отправлять.
# Получить cookie:
curl -i https://httpbin.org/cookies/set?session=abc123
# < Set-Cookie: session=abc123; Path=/
# Отправить cookie вручную:
curl -H 'Cookie: session=abc123' https://httpbin.org/cookies
# {"cookies": {"session": "abc123"}}
# С --cookie-jar curl сохраняет cookies в файл:
curl -c cookies.txt https://httpbin.org/cookies/set?session=abc123
# А потом отдаёт обратно через -b:
curl -b cookies.txt https://httpbin.org/cookies
В Python requests.Session() автоматически работает с cookies — сохраняет из ответов и шлёт обратно. Подробно — модуль 6.
import requests
s = requests.Session()
s.get('https://httpbin.org/cookies/set?session=abc123')
# Cookie сохранился в s.cookies
response = s.get('https://httpbin.org/cookies')
print(response.json()) # {"cookies": {"session": "abc123"}}
Кастомные X-* заголовки
curl -v, --resolve, --trace: глубокая диагностика APIAPI часто добавляют свои заголовки для специфической функциональности. Исторически — с префиксом X-. RFC 6648 (2012) рекомендовал отказаться от X-, но de facto префикс остался.
Юзайте кастомные заголовки осознанно. Не выдумывайте X-My-Internal-Flag, если хватает body. Они полезны для метаданных, которые не вписываются в payload.
Как смотреть заголовки в реальной работе
Несколько полезных команд:
# Только заголовки ответа (HEAD):
curl -I https://api.github.com/users/torvalds
# Заголовки запроса и ответа (verbose):
curl -v https://api.github.com/users/torvalds
# Конкретный заголовок:
curl -sI https://api.github.com/users/torvalds | grep -i 'content-type'
# В Python с requests:
# >>> response.headers -- case-insensitive dict
# >>> response.headers['content-type']
# >>> response.headers.get('x-ratelimit-remaining')
В curl флаг -i показывает заголовки ответа в составе вывода (но не верхние request headers, для них нужен -v).
Попробуй сам
Поэкспериментируйте с заголовками:
# 1. Httpbin echo -- увидеть, какие заголовки ваш клиент шлёт по умолчанию:
curl https://httpbin.org/headers
# Вернёт JSON со всеми вашими headers, включая User-Agent, Accept
# 2. Те же заголовки, но добавим свои:
curl -H 'X-Custom-Header: my-value' \
-H 'Accept: application/json' \
-H 'User-Agent: MyClient/1.0' \
https://httpbin.org/headers
# 3. POST с разными Content-Type:
curl -X POST https://httpbin.org/post \
-H 'Content-Type: application/json' \
-d '{"key":"value"}'
curl -X POST https://httpbin.org/post \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'key=value'
# Сравните, как httpbin парсит -- поле "json" vs "form" в ответе
# 4. Проверить gzip-компрессию:
curl --compressed -I https://api.github.com/users/torvalds
# < Content-Encoding: gzip
# 5. Cookie:
curl -c cookies.txt -b cookies.txt \
https://httpbin.org/cookies/set?session=test123
# В cookies.txt появится файл с cookie. Откройте текстовым редактором -- увидите формат