Научите вашу программу на Go говорить так же, как Chrome

Если вам когда-либо доводилось путешествовать за границей и заказывать кофе фразой из учебника, вы знаете тот взгляд, которым бариста награждает вас в ответ. Ваша грамматика безупречна. Каждое слово есть в словаре. И тем не менее, уже через полсекунды все в кафе понимают, что вы приезжий.
Стандартный HTTP-клиент Go страдает примерно от той же проблемы. Это отличное решение по умолчанию: надёжное, аккуратное и готовое к серьёзной работе сразу после установки. Однако когда ваша программа обращается к серверу, она явно выглядит как программа на Go. В большинстве случаев это никого не беспокоит. Тем не менее некоторые веб-сайты, CDN и тестовые стенды внимательно смотрят на то, кто стучится в дверь, и ведут себя совершенно по-разному в зависимости от того, с каким типом клиента, по их мнению, они имеют дело.
Именно поэтому мы создали gosuda.org/chromenet. Вместо того чтобы обучать стандартный клиент Go правдоподобно имитировать браузер, chromenet позволяет программе на Go взаимодействовать с веб-сетью при помощи того же сетевого движка, который используется в Google Chrome, то есть собственного сетевого кода Chromium. Нужно ли вам проверить, как ваши тестовые стенды обрабатывают браузерный трафик, или установить связь с сайтом, защищённым требовательным CDN, общение через движок Chrome устраняет целый слой неопределённости.
Переписывать приложение для использования этой библиотеки не нужно. В вашем коде она работает как прямая замена http.Transport. Вы продолжаете использовать http.Client привычным образом, а chromenet берёт на себя взаимодействие на нижнем уровне.
Чтобы попробовать последний релиз, установите его командой go get:
1go get gosuda.org/chromenet@v0.2.10157081033800
Затем передайте его транспорт своему клиенту:
1client := http.Client{
2 Transport: chromenet.NewTransport(chromenet.Options{}),
3}
В этом релизе весь спектр современного веб-трафика проходит через движок Chrome. Теперь поддерживаются обычные веб-запросы, WebSocket и WebTransport. Предыдущие версии корректно обрабатывали только обычные запросы, тогда как WebSocket и WebTransport были заглушками. Это означало, что первое приветствие у входной двери звучало как Chrome, но при переходе на другой стиль общения ваше прикрытие могло раскрыться. Данный релиз заменяет заглушки полноценной реализацией.
Постойте, если мы используем собственный сетевой код Chromium, означает ли это, что ваша компактная сборка на Go превратится в масштабную головную боль с C-компилятором? К счастью, нет. Ваш код на Go остаётся простым кодом на Go, без cgo и без необходимости в C-компиляторе.
Вместо того чтобы встраивать код браузера непосредственно в исполняемый файл, chromenet запускает движок как небольшую отдельную программу рядом с вашей программой на Go. Представьте, что вы наняли свободно говорящего местного переводчика, который идёт рядом с вами, а не пытаетесь перестроить собственные голосовые связки. Поскольку движок работает в отдельной программе, сбой внутри него не может повалить ваш сервис.

Ваша программа на Go остаётся простой программой на Go, а небольшая вспомогательная программа передаёт трафик через сетевой движок Chromium.
На вашей рабочей станции или серверах также ничего дополнительно устанавливать не нужно. Бинарные файлы движка для Linux, macOS и Windows как для архитектур Intel/AMD, так и ARM поставляются прямо внутри модуля Go и проверяются перед запуском.
Размещение бинарных файлов внутри модуля, безусловно, вызывает вопрос о доверии, и мы хотели дать на него прямой ответ. Бинарные файлы собираются воспроизводимым образом из конкретного релиза Chromium, в настоящее время Chromium 157, поэтому любой может пересобрать их и получить абсолютно идентичные байты, а каждый релиз подтверждается GitHub. Чтобы не отставать от мира браузеров без ручной работы, запланированное задание отслеживает стабильный канал Chromium и автоматически обновляет версию, как только выходит новая версия Chrome.
А что насчёт uTLS?
Если вы ранее пытались сделать клиент на Go похожим на браузер, вы, вероятно, сталкивались с uTLS. Это замечательная работа: библиотека на Go, которая позволяет изменить первое сообщение защищённого соединения так, чтобы оно напоминало Chrome, Firefox или любой другой браузер на ваш выбор. Она небольшая, написана на чистом Go, и рядом с вашей программой ничего дополнительно не работает.
Проблема в том, насколько далеко заходит этот «костюм». uTLS охватывает рукопожатие, то есть первые фразы разговора. Всё остальное по-прежнему нужно создавать самостоятельно: уровень HTTP/2 приходится настраивать и тонко подстраивать вручную, HTTP/3 представляет собой отдельную задачу, а WebSocket или WebTransport требуют ещё больше дополнительного кода. Пресеты Chrome к тому же являются копиями, поэтому кто-то должен поддерживать их в актуальном состоянии каждый раз, когда Chrome меняет свои привычки. Это похоже на то, как если бы вы подражали акценту собеседника, а язык его тела по-прежнему выдавал бы вас.
chromenet идёт противоположным путём. Он не имитирует Chrome, а поручает собственному движку Chrome вести переговоры, поэтому рукопожатие, способ повторного использования соединений, переход на другие протоколы и транспорты поступают из одного источника и изменяются согласованно при обновлении Chromium.
Этот компромисс честен. uTLS легче и позволяет создать любое приветствие, которое вы только можете себе представить, тогда как chromenet добавляет вспомогательную программу размером 25 МБ и предоставляет поведение Chrome, а не собственное. Если вам нужна минимальная зависимость и полный контроль над рукопожатием, uTLS по-прежнему отличный выбор.
Любая архитектура предполагает компромиссы, и здесь мы хотим честно обозначить ограничения. Включение полноценного сетевого движка браузера не назовёшь компактным: каждый бинарный файл движка занимает около 25 МБ. На macOS бинарные файлы не прошли нотаризацию. А на Linux вашей системе требуется glibc версии 2.31 или новее. Если эти ограничения подходят для вашей конфигурации, вы получаете настоящую сетевую подсистему Chrome, заменив всего одно поле транспорта.
Проект находится по адресу github.com/gosuda/chromenet. Если вы знакомы с его родственными проектами в открытом семействе gosuda, gosuda.org/hqq и gosuda.org/sqlite3, их дух будет вам близок: взять мощные и тяжёлые инструменты из более широкого мира программного обеспечения и сделать так, чтобы они органично чувствовали себя в обычном Go.
Итак, если вы обращаетесь к uTLS потому, что хотите, чтобы ваша программа на Go звучала как браузер, попробуйте chromenet. Он может стать хорошей альтернативой uTLS, особенно когда вам нужно больше, чем HTTPS-запросы: HTTP/2, HTTP/3, WebSocket и WebTransport работают через один и тот же движок Chrome без необходимости писать клеевой код с вашей стороны.