Пользовательские HTTP-заголовки в BotBrowser: флаг, профиль или CDP
Добавляйте заголовки запросов своего приложения через --bot-custom-headers, профиль или команды CDP и сохраняйте согласованность User-Agent и Sec-CH-UA, которыми управляет профиль.
BotBrowser Team
Нужна поддерживаемая продуктовая документация?
У этой статьи есть соответствующая страница в центре документации. Используйте docs для каноничного сценария настройки, актуальных флагов и долгосрочной справки.
Статья о том, как добавлять собственные заголовки запросов в BotBrowser через флаг, поле профиля или команды CDP. BotBrowser доставляет заголовки приложения, но не заставит сервер принять их, не отменит предварительный запрос CORS и не должен переписывать User-Agent и Sec-CH-UA, которыми управляет профиль.
Краткое резюме
Выбирайте один способ на каждый заголовок: флаг для статического набора, configs.customHeaders для набора профиля, команды CDP уровня браузера для значений, которые меняются в ходе сеанса. Проверяйте результат на эхо-точке: заданные заголовки должны прийти с точными значениями, а User-Agent и Sec-CH-UA остаться согласованными с профилем.
Содержание руководства
- Стандартные и пользовательские заголовки
- Выбор между флагом, настройкой профиля и командами CDP
- Запуск со статическим набором заголовков
- Изменение заголовков во время работы и по контекстам браузера
- Проверка заголовков и предварительный запрос CORS
- Воспроизводимая проверка заголовков
- Где BotBrowser подходит и где его возможности заканчиваются
Стандартные и пользовательские заголовки
Каждый HTTP-запрос браузера несёт набор полей заголовка. Одни описывают сам браузер: User-Agent называет браузер и его версию, Sec-CH-UA и родственные поля структурированно описывают бренд и платформу, а Accept-Language перечисляет языковые предпочтения. Другие описывают приложение: токен авторизации, идентификатор сборки, имя арендатора или подсказку маршрутизации, которую шлюз читает до того, как запрос дойдёт до приложения. Первая группа должна оставаться согласованной с выбранной вами идентичностью браузера. Вторую вы определяете целиком сами.
Именно из-за такого разделения пользовательские заголовки выделены в отдельную возможность. Когда профиль загружен, BotBrowser поддерживает стандартные заголовки, которыми управляет профиль, например User-Agent и Sec-CH-UA, в соответствии с идентичностью браузера из этого профиля. Пользовательские заголовки располагаются рядом с ними. Они предназначены для значений, специфичных для приложения. Пользовательский заголовок с тем же именем, что и у существующего, может его переопределить, поэтому не включайте в пользовательский набор имена, которыми управляет профиль, например User-Agent и Sec-CH-UA. Если страница должна выглядеть как конкретный браузер, одиночный заголовок, который этому противоречит, создаёт именно то несоответствие, которое потом трудно объяснить, поэтому правило простое: стандартные заголовки приходят из профиля, заголовки приложения приходят от вас.
Семантика полей определена в RFC 9110. Имена полей нечувствительны к регистру, поэтому X-App-Version и x-app-version обозначают одно и то же поле. Значение поля является строкой, а если одно и то же имя встречается несколько раз, получатель вправе рассматривать эти строки как единый список через запятую. На практике эти детали важны: набор заголовков, который выглядит корректно в объекте JSON, может быть прочитан иначе шлюзом, который нормализует имена, объединяет дубликаты или отклоняет необычные символы.
Типичные применения пользовательских заголовков делятся на несколько групп:
- Подсказки аутентификации и авторизации, например ключ API или предъявительский токен, которого шлюз ждёт в каждом вызове.
- Метки приложения, например номер сборки, идентификатор арендатора или имя флага функции, по которому бэкенд выбирает маршрут.
- Переключатели тестовых и предпродакшен-сред, например заголовок, выбирающий канареечное развёртывание или включающий подробные ответы в принадлежащей вам тестовой среде.
- Соглашения мобильных веб-представлений, например значение
X-Requested-With, которое сайт ожидает от контейнера приложения.
Пользовательские заголовки не предназначены для описания сетевого пути. Заголовки вроде X-Forwarded-For и X-Real-IP относятся к инфраструктуре прокси и балансировщиков нагрузки, а правильно настроенный сервер не придаёт значения тому, что выставил оператор браузера. Установка таких заголовков из браузера лишь создаёт путаницу, поэтому используйте имена, специфичные для вашего приложения.
За значения заголовков отвечает оператор. Заголовок, несущий секретные данные для входа, следует хранить как любой другой секрет: не записывайте его в общие журналы, меняйте по расписанию, которое задаёт ваш бэкенд, и отправляйте только тем источникам, которые должны его получать. BotBrowser отправляет заданные вами заголовки в HTTP- и HTTPS-запросах, поэтому объём того, что вы настраиваете, равен объёму того, что оказывается открытым.
Выбор между флагом, настройкой профиля и командами CDP
В документации BotBrowser описаны три способа задать пользовательские заголовки, и правильный выбор зависит от того, как часто меняется набор и кто им владеет. Все три требуют лицензии PRO.
Первый способ: флаг запуска --bot-custom-headers. Он принимает объект JSON, ключи которого являются именами заголовков, а значения строками. Это самый простой вариант для статического набора: заголовки фиксируются при старте браузера, действуют с самого первого запроса, а команда запуска остаётся единственным местом, где их можно прочитать. Он подходит для задачи по расписанию, запуска в CI или службы, которая всегда обращается к одному бэкенду с одной и той же меткой приложения.
Второй способ: поле профиля configs.customHeaders. Здесь заголовки хранятся в конфигурации профиля, а не в команде запуска. Это удобно, когда набор принадлежит профилю, а не запуску, например когда один профиль всегда представляет один контейнер приложения и должен нести одну и ту же метку везде, где используется. Заголовки путешествуют вместе с файлом профиля, поэтому любой загрузчик, который его открывает, получает то же поведение без дополнительных аргументов.
Третий способ: команды CDP уровня браузера BotBrowser.setCustomHeaders, BotBrowser.getCustomHeaders, BotBrowser.addCustomHeader, BotBrowser.removeCustomHeader и BotBrowser.clearCustomHeaders. Они изменяют набор заголовков во время работы браузера, без перезапуска. В документации команды CDP названы самым гибким из трёх способов, поэтому они естественно подходят для токена, который меняется в ходе сеанса, или для заголовка, который должен отличаться между двумя переходами.
Краткое руководство по выбору:
- Набор не меняется в течение запуска: используйте флаг.
- Набор принадлежит конкретному профилю и должен идти вместе с ним: используйте
configs.customHeaders. - Набор меняется, пока браузер открыт: используйте команды CDP в сеансе уровня браузера.
- Разным контекстам браузера нужны разные наборы: передавайте флаг через флаги отдельных контекстов, как показано ниже.
Три способа могут сосуществовать, поэтому лучше заранее решить, какой из них отвечает за конкретный заголовок. В документации они перечислены по приоритету: сначала команды CDP, затем флаг командной строки, затем поле профиля. И всё же, если одно и то же имя определено в двух местах, проверьте, какое значение отправляется, прежде чем на это пожалуется сервер. Практичное соглашение: постоянные метки держать в профиле или во флаге, а команды CDP оставлять для значений, которые действительно динамические.
Одно терминологическое замечание помогает при чтении документации. Набор, заменённый через setCustomHeaders, заменяет предыдущий пользовательский набор, а не стандартные заголовки, которыми управляет профиль. addCustomHeader и removeCustomHeader затрагивают одну пользовательскую запись, а clearCustomHeaders очищает пользовательский набор. Не используйте их, чтобы удалять или переписывать User-Agent или Sec-CH-UA; оставьте эти имена профилю.
Для файлов cookie вообще не используйте заголовок Cookie. В BotBrowser есть отдельный флаг --bot-cookies с импортом в пределах контекста, и это поддерживаемый путь для состояния cookie. Если обращаться с cookie как с пользовательским заголовком, будут пропущены область действия и срок жизни, которыми cookie должны обладать.
Запуск со статическим набором заголовков
Для статического набора передайте объект JSON во флаг запуска. В оболочке одинарные кавычки вокруг значения сохраняют JSON целым:
chrome --bot-profile="path/to/profile.enc" \
--bot-custom-headers='{"X-App-Version":"2.1.0","X-Tenant":"acme-test"}'
При запуске из кода правило кавычек меняется. Одинарные кавычки в примере для оболочки нужны только оболочке. В JavaScript аргумент попадает прямо в процесс, поэтому значением должна быть голая строка JSON. Если добавить кавычки снаружи, они станут частью значения, и браузер получит текст, не являющийся корректным JSON.
const headers = { 'X-App-Version': '2.1.0', 'X-Tenant': 'acme-test' };
// Верно: аргумент состоит из флага, знака равенства и текста JSON
args.push('--bot-custom-headers=' + JSON.stringify(headers));
// Неверно: одинарные кавычки становятся частью значения
args.push(`--bot-custom-headers='${JSON.stringify(headers)}'`);
Полный запуск через Playwright выглядит так:
const { chromium } = require('playwright-core');
(async () => {
const headers = { 'X-Auth': 'token123', 'X-Client': 'webapp' };
const browser = await chromium.launch({
executablePath: 'path/to/botbrowser/chrome',
headless: true,
args: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers=' + JSON.stringify(headers)],
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Версия для Puppeteer отличается только вызовом запуска:
const puppeteer = require('puppeteer-core');
(async () => {
const headers = { 'X-Auth': 'token123' };
const browser = await puppeteer.launch({
executablePath: 'path/to/botbrowser/chrome',
headless: true,
args: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers=' + JSON.stringify(headers)],
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Если вы предпочитаете хранить заголовки вместе с профилем, поместите тот же объект в configs.customHeaders в конфигурации профиля. Значение является обычным объектом из пар строк, как и во флаге, а значение по умолчанию это пустой объект. Если значение есть и в профиле, и во флаге, проверьте результат на эхо-точке, прежде чем полагаться на какое-либо из них, как описано в разделе о проверке.
Некоторые детали стоит решить до первого запуска:
- Используйте только строковые значения. Числа и логические значения записывайте как строки, потому что значения заголовков являются текстом.
- Ограничивайте имена обычными символами токена. Пробелы и знаки препинания в имени отклоняют многие серверы и посредники на пути.
- Держите общий размер разумным. В документации конкретный предел не задан, но каждый заголовок отправляется с каждым запросом, а серверы и шлюзы применяют собственные ограничения на размер заголовков.
- Не помещайте секреты в команду запуска, которая хранится в общих скриптах или видна в списке процессов. Читайте их из окружения или хранилища секретов в момент запуска.
В настройках в духе веб-представления Android в документации показан тот же флаг для воспроизведения значения X-Requested-With, которое отправил бы контейнер приложения. Схема та же: один объект JSON, переданный через флаг и применяемый к HTTP- и HTTPS-запросам.
Изменение заголовков во время работы и по контекстам браузера
Статического флага недостаточно, когда значение меняется в ходе сеанса, например короткоживущий токен, который обновляется, или заголовок, который должен отличаться на двух этапах сценария. Для этого BotBrowser предоставляет команды CDP. Их нужно отправлять в сеанс уровня браузера, а не в сеанс уровня страницы.
В Playwright откройте сеанс уровня браузера и отправьте команду:
const cdpSession = await browser.newBrowserCDPSession();
// Заменяет весь пользовательский набор
await cdpSession.send('BotBrowser.setCustomHeaders', {
headers: { 'X-Requested-With': 'com.example.app', 'X-Session': 'first' },
});
// Читает текущий пользовательский набор
const current = await cdpSession.send('BotBrowser.getCustomHeaders');
console.log(current.headers);
// Добавляет или обновляет одну запись
await cdpSession.send('BotBrowser.addCustomHeader', {
name: 'X-Session',
value: 'refreshed',
});
// Удаляет одну запись или очищает пользовательский набор
await cdpSession.send('BotBrowser.removeCustomHeader', { name: 'X-Session' });
await cdpSession.send('BotBrowser.clearCustomHeaders');
В Puppeteer сеанс уровня браузера создаётся из цели браузера:
const cdpSession = await browser.target().createCDPSession();
await cdpSession.send('BotBrowser.setCustomHeaders', {
headers: { 'X-Requested-With': 'com.example.app' },
});
await cdpSession.send('BotBrowser.addCustomHeader', {
name: 'X-Token',
value: 'my-token',
});
Если отправить любую из этих команд в сеанс уровня страницы, вызов завершится ошибкой ProtocolError: 'BotBrowser.setCustomHeaders' wasn't found. Эта ошибка обычно означает, что сеанс создан из страницы, а не из браузера. Переключитесь на сеанс уровня браузера, и команда будет найдена.
Пять команд покрывают весь жизненный цикл пользовательского набора:
setCustomHeadersзаменяет все пользовательские заголовки переданным вами объектом.getCustomHeadersвозвращает текущий пользовательский набор, что удобно для записи того, на что вы собираетесь опираться.addCustomHeaderдобавляет или обновляет одну запись по имени.removeCustomHeaderудаляет одну запись по имени.clearCustomHeadersудаляет все пользовательские заголовки.
Надёжная схема такова: применить новый набор, затем начать переход, который от него зависит, а когда нужно быть уверенным в текущем состоянии, прочитать набор обратно через getCustomHeaders.
Иногда разным задачам нужны разные заголовки в одном браузере. BotBrowser позволяет это, назначая заголовки по контекстам браузера. Флаги отдельных контекстов передаются через BotBrowser.setBrowserContextFlags, и флаг пользовательских заголовков входит в число флагов, которые может нести контекст:
const client = await browser.target().createCDPSession();
const contextA = await browser.createBrowserContext();
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: contextA._contextId,
botbrowserFlags: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers={"X-Requested-With":"com.app.one"}'],
});
const contextB = await browser.createBrowserContext();
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: contextB._contextId,
botbrowserFlags: ['--bot-profile=path/to/profile.enc', '--bot-custom-headers={"X-Requested-With":"com.app.two"}'],
});
Здесь каждый контекст несёт собственный набор заголовков, поэтому две одновременно открытые страницы отправляют разные метки приложения. Помните то же правило кавычек: внутри массива каждая запись является одним аргументом, поэтому текст JSON пишется без дополнительных кавычек оболочки.
Проверка заголовков и предварительный запрос CORS
Всегда проверяйте настройку заголовков на точке, которая возвращает то, что получила. Для быстрой проверки подойдёт публичный эхо-сервис вроде httpbin, а ещё лучше предпродакшен-точка под вашим контролем, потому что она заодно показывает, что делает с значениями ваш собственный шлюз.
const page = await context.newPage();
await page.goto('https://httpbin.org/headers');
const headersText = await page.textContent('body');
console.log('Received headers:', headersText);
Хорошая проверка состоит из трёх частей:
- Убедитесь, что каждый настроенный вами пользовательский заголовок присутствует в ответе с точно тем значением, которое вы задали.
- Убедитесь, что
User-AgentиSec-CH-UAпо-прежнему соответствуют загруженному профилю, что показывает: стандартный набор не затронут. - Откройте панель сети в инструментах разработчика браузера и проверьте, что заголовки присутствуют на разных видах запросов страницы, таких как документ, скрипты, изображения и вызовы fetch.
Если заголовок не появился, причина обычно одна из трёх. JSON может быть некорректным, чаще всего из-за описанной выше ошибки с кавычками. Команда могла уйти в сеанс уровня страницы вместо сеанса уровня браузера. Или посредник между браузером и эхо-точкой мог удалить или переименовать поле, поэтому прямое сравнение с сервисом под вашим контролем так ценно.
Второе поведение, которое нужно понимать, это CORS. Браузер обрабатывает кросс-доменный запрос иначе, чем запрос к тому же источнику. Для многих кросс-доменных запросов браузер сначала отправляет предварительный запрос методом OPTIONS. В нём передаются Access-Control-Request-Method и Access-Control-Request-Headers, где перечислены метод и заголовки вне безопасного списка, которые собирается использовать настоящий запрос. Сервер отвечает заголовками Access-Control-Allow-Origin, Access-Control-Allow-Methods и Access-Control-Allow-Headers, и только если ответ покрывает запрос, браузер продолжает. В руководстве MDN по CORS описан весь обмен, включая Access-Control-Max-Age, который позволяет браузеру переиспользовать ответ на предварительный запрос в течение какого-то времени.
Обычная причина срабатывания: нестандартные заголовки. Небольшая группа заголовков, таких как Accept, Accept-Language и Content-Language, входит в безопасный список и сама по себе предварительного запроса не вызывает. Заголовок вроде X-Auth-Token или X-Tenant в эту группу не входит. Когда браузер добавляет такой заголовок к кросс-доменному запросу, может быть отправлен предварительный запрос. Если целевой сервер не указывает имя заголовка в Access-Control-Allow-Headers, запрос может завершиться ошибкой. В документации BotBrowser сказано то же самое: добавление нестандартных заголовков может вызвать предварительный запрос на некоторых сайтах, и целевой сервер должен их разрешать.
Из этого следует несколько практических выводов:
- Если целевой сервер принадлежит вам, добавьте имена заголовков в его ответ
Access-Control-Allow-Headersи решите, как долго можно кэшировать ответ на предварительный запрос. - Если сервер вам не принадлежит, изменить его ответы вы не можете. Выберите другой заголовок или другой подход, например передайте значение в месте, которое сервер уже принимает.
- Запросы к тому же источнику не требуют предварительного запроса, поэтому проверка на вашем собственном источнике может пройти, а тот же заголовок на другом источнике не сработает. Проверяйте на источнике, который будете реально использовать.
- Ответ на предварительный запрос, разрешающий заголовок, не авторизует сам запрос. Аутентификация и авторизация остаются отдельными решениями сервера.
Размер тоже имеет значение. Серверы, шлюзы и CDN ограничивают общий размер заголовков запроса. Большой набор пользовательских заголовков или одно слишком длинное значение может вызвать отказ, не связанный ни с CORS, ни с BotBrowser. Если запросы начали завершаться ошибкой после роста набора, сначала уменьшите набор, а потом ищите причину в другом месте.
Где BotBrowser подходит и где его возможности заканчиваются
BotBrowser поддерживает добавление пользовательских заголовков HTTP-запросов через --bot-custom-headers, через поле профиля configs.customHeaders и через команды CDP уровня браузера BotBrowser.*CustomHeaders. Заголовки применяются к HTTP- и HTTPS-запросам и могут назначаться по контекстам браузера, поэтому команда может отправлять документированный набор заголовков приложения, пока профиль сохраняет согласованность стандартных заголовков, таких как User-Agent и Sec-CH-UA. Управление заголовками требует лицензии PRO. BotBrowser не может заставить сервер принять добавленные заголовки, авторизовать по ним запрос или доверять им, не может отменить предварительный запрос CORS и серверные ограничения на заголовки и размер, а также не заменяет вашу собственную схему заголовков и аутентификации. Значение заголовка определяет приложение, которое его читает, поэтому серверная сторона остаётся вашей ответственностью.
На практике это значит относиться к возможности как к средству доставки для уже продуманной схемы заголовков. Решите, какие имена ожидает бэкенд, каким источникам можно их получать, как выдаётся и меняется токен и как должен выглядеть отклонённый запрос. Затем используйте флаг, поле профиля или команды CDP, чтобы доставить эту схему из браузера. Если схема продумана, доставка проста. Если схема неясна, никакой выбор флага это не исправит.
Стоит помнить несколько границ:
- Используйте пользовательские заголовки для добавления значений приложения. Не включайте в пользовательский набор стандартные заголовки, которыми управляет профиль, например
User-AgentиSec-CH-UA. - Заголовки инфраструктуры прокси, такие как
X-Forwarded-For, выходят за рамки того, что рекомендуется в этом руководстве, потому что они описывают сетевой путь, которым браузер не управляет. - Файлы cookie относятся к флагу для cookie, а не к пользовательскому заголовку
Cookie. - Каждый добавленный заголовок виден серверу, которому он отправляется, и любому посреднику на пути, поэтому считайте набор открытым для этих сторон.
По смежным настройкам: руководство по настройке прокси объясняет, как устроена сетевая маршрутизация, User-Agent и Client Hints объясняет, как стандартные заголовки идентичности следуют за профилем, а мультиаккаунтный браузер посвящён разделению контекстов, когда каждому нужен собственный набор заголовков.
Воспроизводимая проверка заголовков
Ниже используется локальный HTTP-fixture через loopback, поэтому наблюдение можно повторить без отправки секрета или тестового значения стороннему сервису. Fixture предоставляет /echo, возвращает 302 для /redirect и отвечает на OPTIONS списком разрешённых имён. Команда намеренно меньше полноценного запуска браузера: она проверяет протокол и диагностику, а флаг BotBrowser и CDP-вызовы остаются описанными выше способами доставки.
echo custom=custom-http-headers-20261017 ua=fixture-UA/1.0 sec=\"Fixture\";v=\"1\"
redirect status=200 custom=custom-http-headers-20261017
preflight status=204 allow=x-test-run
Первая строка показывает, что значение приложения, поле user-agent и поле формы client hints вместе пришли на контролируемый приёмник. Вторая важна потому, что перенаправление может изменить источник или привести к удалению заголовка по правилам браузера и сервера; этот fixture сохранил пользовательский заголовок при перенаправлении на том же хосте. Третья строка является ответом сервера, а не разрешением браузера: allow=x-test-run означает, что приёмник включил имя в список preflight, но не аутентифицировал конечный запрос.
Используйте вывод как дерево диагностики. Если в строке echo нет custom, проверьте JSON-кавычки и область действия флага. Если echo работает, но cross-origin запрос останавливается до вызова приложения, проверьте Access-Control-Allow-Headers и политику источника. Если первый запрос проходит, а строка перенаправления отличается, сравните цель, флаги контекста и шлюз, который может переписывать поля. Fixture записывает User-Agent и поле формы Sec-CH-UA, чтобы показать полученные сервером значения; это не оценка детектора и не утверждение, что Node.js является движком BotBrowser. Файл evidence сохраняет хэши Node и стабильного профиля, чтобы повторить тот же локальный протокольный тест.
Что добавлено по сравнению со справочной страницей
Справочная страница описывает варианты настройки. Добавленное сравнение проверяет значение профиля, запуска и CDP контекста в исходном запросе, CORS preflight и перенаправлении. Несовпадение на одном переходе указывает на область запроса или политику сервера.
Источники
Открытые материалы, использованные для утверждений о заголовках, CORS и BotBrowser:
- RFC 9110: HTTP Semantics
- MDN: Cross-Origin Resource Sharing (CORS)
- Документация BotBrowser о пользовательских HTTP-заголовках
Практический итог
Статический набор задавайте флагом, набор профиля держите в configs.customHeaders, а изменяемые значения отправляйте командами CDP в сеансе уровня браузера. Принимайте настройку, только когда эхо-точка показала нужные заголовки на документе, скриптах и вызовах fetch, а стандартные заголовки профиля остались нетронутыми; при отказе CORS меняйте разрешения сервера, а не набор.
Эта статья входит в руководство: Обзор настройки прокси: карта сетевых решений браузера
Похожие статьи
User-Agent и Client Hints: одна согласованная идентичность
СетьЗаголовки Fetch Metadata: контекст CSRF и приватность
ОтпечаткиClient Hints Fingerprinting: HTTP-заголовки и идентичность
ПлатформаГраницы CORS в браузере: заголовки, preflight, учетные данные и кэш
СетьСетевая согласованность браузера для защиты приватности
СетьPAC Request Policy для согласованной маршрутизации
Переведите BotBrowser из исследований в продакшн
Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.