Можно ли указать кастомный заголовок в HTTP запросе
Что проверяют
Отличаете ли вы «можно написать в коде» от «браузер это отправит». Заголовок добавляется одной строкой, но над ним есть два ограничителя: список заголовков, которые браузер выставляет сам, и CORS. Вопрос почти всегда задают, чтобы вывести на предполётный запрос.
Как отвечать
Да, произвольный заголовок задаётся прямо в вызове:
fetch(url, { headers: { "X-Request-Id": id } });Дальше нужны три уточнения.
Часть заголовков задаёт сам браузер, и перезаписать их из JavaScript нельзя: Host, Origin, Referer, Cookie, Connection, Content-Length. Ошибки при этом не будет, значение просто молча не уедет, отсюда частое «я же передал, а на сервере пусто».
Если запрос кросс-доменный, любой заголовок вне безопасного списка (Accept, Accept-Language, Content-Language и Content-Type с ограниченным набором значений) делает запрос непростым. Браузер сначала шлёт OPTIONS на тот же адрес, и сервер обязан вернуть Access-Control-Allow-Headers со списком, куда входит ваш заголовок. Не вернул, основной запрос вообще не уйдёт.
Симметрично с ответом: чтобы прочитать свой заголовок через response.headers, сервер должен перечислить его в Access-Control-Expose-Headers. По умолчанию видно лишь несколько безопасных.
Про имя стоит добавить, что префикс X- больше не рекомендуется: RFC 6648 его отменил, потому что «временные» заголовки живут годами и буква перестаёт что-либо значить.
Где спотыкаются
Отвечают «да, конечно» и замолкают. Интервьюер ждал CORS, и молчание про OPTIONS читается как отсутствие опыта с чужим API: на своём домене всё работает, а на стороннем падает без внятной ошибки.
Пытаются «разрешить» заголовок на клиенте: ставят mode: "no-cors" и удивляются, что стало хуже. Этот режим не снимает ограничение, а наоборот выкидывает небезопасные заголовки и запрещает читать тело ответа.
Забывают про кэш. Если ответ зависит от кастомного заголовка, сервер обязан вернуть Vary с его именем, иначе промежуточный кэш отдаст следующему клиенту чужой вариант. Рядом стоит помнить и про Access-Control-Max-Age: он задаёт, как долго браузер не повторяет предполётный запрос.