В предыдущих двух главах заголовки уже мелькали. Время разложить их по полкам: что действительно нужно ставить руками, что — задача браузера, и на какие «запрещённые» заголовки браузер не обратит внимания.
Кто что ставит
На исходящем запросе заголовки делятся на три группы.
1. Ставим мы. Это полезная нагрузка для бэкенда: Content-Type, Accept, Authorization, кастомные X-*.
2. Ставит браузер автоматически. Технические и часть служебных: Host, User-Agent, Content-Length, Connection, Cookie, Origin, Referer и другие. Их перечислять руками не надо, и попытка перезаписать Host или Cookie ничего не даст — браузер их не пустит ради безопасности.
3. «Forbidden header names». Список заголовков, которые из JS поставить нельзя в принципе. К нему относятся Origin, Cookie, Set-Cookie, Date, Connection и ряд других. Это сделано, чтобы JS не подделывал источник запроса.
Главные четыре
Content-Type
В исходящем запросе говорит серверу, в каком формате тело. На POST/PUT/PATCH с JSON всегда application/json. На загрузке файла через FormData — не ставим, браузер сам.
В ответе от сервера говорит, в каком формате тело пришло. Чаще всего application/json; charset=utf-8.
Accept
Говорит серверу, в каком формате клиент хочет получить ответ:
fetch('/api/posts', {
headers: {
'Accept': 'application/json',
},
});
Если сервер умеет читать несколько форматов (JSON, XML, CSV) — он посмотрит на Accept и выберет подходящий. В REST API в 99% этот заголовок — application/json; в большинстве случаев его можно опустить, потому что сервер и так отдаёт JSON по умолчанию.
Authorization
Идентификация клиента. Несколько схем — в зависимости от того, что требует API. Все подробно — в модуле 7, здесь только формат:
// Bearer-токен (самый частый случай):
headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...' }
// Basic auth (логин:пароль в base64):
headers: { 'Authorization': 'Basic ' + btoa('user:password') }
// API key через кастомный заголовок (вариант некоторых сервисов):
headers: { 'X-API-Key': 'abc123...' }
Кастомные X-заголовки
Заголовки с префиксом X- исторически использовались для всего нестандартного. RFC 6648 рекомендовал перестать ставить такой префикс — но в индустрии X-* по-прежнему встречается на каждом шагу:
- X-Request-ID — уникальный id запроса для трассировки в логах;
- X-API-Key — альтернатива Authorization для простых API;
- X-Client-Version — версия клиента, чтобы серверу было удобно дебажить;
- X-CSRF-Token — защита от CSRF-атак в форменных API.
Ставим, если API в документации просит. Свои выдумывать без необходимости — не надо.
Как заголовки передаются в fetch
Самый частый способ — передать объект:
fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
});
Альтернативно можно передать массив пар или объект Headers:
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.append('X-Custom', 'value');
fetch(url, { headers });
Имена заголовков HTTP не чувствительны к регистру: Content-Type и content-type — одно и то же. Объект Headers нормализует их сам.
Чтение заголовков ответа
В response.headers — объект Headers с заголовками ответа. У него есть .get(name), .has(name), итерация:
const response = await fetch('/api/posts');
const contentType = response.headers.get('Content-Type');
const remaining = response.headers.get('X-RateLimit-Remaining');
for (const [name, value] of response.headers) {
console.log(name, value);
}
Имена при чтении тоже нечувствительны к регистру.
CORS-нюанс с чтением заголовков
Когда запрос идёт на другой домен (cross-origin), JS может прочитать ответные заголовки только из «безопасного» списка: Content-Type, Cache-Control, ещё несколько. Все остальные — в том числе Authorization, X-* — доступны только если сервер явно их разрешил через Access-Control-Expose-Headers. Подробно про CORS — в модуле 8.
В итоге
Из всего зоопарка заголовков фронтенд в повседневной работе руками ставит две-три штуки: Content-Type на POST/PUT/PATCH с JSON, Authorization на запросы к защищённым эндпоинтам, иногда кастомные X-*, если их требует конкретный API. Остальное либо ставит браузер, либо нам всё равно.