Steven
Steven4 мин. чтения

Транскрипция в реальном времени, когда файрвол блокирует WebSocket

Корпоративные сети обожают пропускать HTTPS и втихую убивать upgrade до WebSocket. Это незаметно ломает транскрипцию в реальном времени. GeekBye v2.0.8 автоматически переключается на чисто-HTTPS транспорт — а его выпуск вскрыл баг, который сделал бы всю функцию бесполезной.

Транскрипция
Сети
Инженерия
Релизы GeekBye
Транскрипция в реальном времени, когда файрвол блокирует WebSocket

Есть один конкретный, доводящий до бешенства способ, которым транскрипция в реальном времени отказывает в корпоративной сети. Ваш Wi-Fi в порядке. HTTPS работает — вы можете открыть любой сайт. Но транскрипция в реальном времени просто... не запускается, и ничто не говорит вам почему.

Виновник — класс корпоративного proxy, которое пропускает обычный HTTPS-трафик, но блокирует upgrade до WebSocket — рукопожатие, превращающее HTTPS-соединение в постоянный двусторонний канал, который нужен транскрипции в реальном времени. Для proxy WebSocket выглядит как немониторируемый туннель наружу из сети, поэтому оно его убивает. Для вас транскрипция незаметно сломана.

GeekBye v2.0.8 добавил автоматический fallback ровно на этот случай — и его создание вскрыло баг, из-за которого вся функция не делала бы ничего.

Почему fallback, а не просто повтор

Мы уже справляемся с нестабильными сетями. Если ваше соединение обрывается посреди сессии, GeekBye переподключается с backoff и буферизует ваше аудио, чтобы ничего не пропало, — это отдельная функция, описанная в почему ваш ИИ-ассистент для заметок останавливается на плохом Wi-Fi.

Но заблокированный WebSocket — это не нестабильное соединение. Повтор того же WebSocket против proxy, которое отказывает WebSocket'ам, отказывает одинаково каждый раз, вечно. Единственное исправление — другой транспорт, который выглядит как простой HTTPS, что proxy уже пропускает.

Так что v2.0.8 переключается на чисто-HTTPS транспорт через тот же аутентифицированный endpoint:

  • Вниз (транскрипты, возвращающиеся к вам): server-sent events — долгоживущий HTTPS-ответ, который proxy видит как обычную потоковую загрузку.
  • Вверх (ваше аудио, уходящее наружу): батчевые POST-запросы, каждый несёт фрагмент аудио с порядковым номером, чтобы сервер мог собрать их по порядку, даже если сеть их переставит.

Никакого постоянного сокета, ничего, что выглядит как туннель, — только HTTPS-запросы и ответы. Если proxy позволяет вам пользоваться сайтом, оно позволяет и это.

Баг, который выпустил бы мёртвую функцию

Вот часть, ради которой стоит читать. Fallback должен срабатывать, когда WebSocket-соединение исчерпывает свои попытки с сигнатурой заблокированного транспорта — каждая попытка отказывает на upgrade, никаких проблем с auth или лимитом, как минимум одно отклонение в форме proxy. Proxy, блокирующее WebSocket, обычно отвечает на upgrade HTTP 403 Forbidden или 407.

Проблема: в нашем коде соединения уже было правило, что 403 означает фатальную ошибку аутентификации — остановись, покажи её пользователю, не повторяй. Что верно почти везде. Но это означало, что 403 от блокирующего proxy — ровно тот сигнал, который должен был запустить fallback — вместо этого выбрасывалось как фатальная ошибка до того, как логика fallback вообще могла запуститься. Только сырой обрыв соединения (закрытие 1006) проваливался дальше к fallback. Так что функция работала бы для редкого случая и втихую отказывала бы для своей настоящей главной цели: корпоративного proxy.

Мы поймали это при закалке релиза, а не в продакшене. Исправление: 403/407 на upgrade-этапе WebSocket теперь трактуется как восстановимое, чтобы цикл соединения мог исчерпаться в fallback, — тогда как подлинный отказ аутентификации (который приходит иначе, после успешного upgrade) по-прежнему отказывает быстро, ровно как раньше. Регрессионный тест теперь закрепляет это различие: 403 от заблокированного proxy должно уйти в fallback; настоящее 403 auth — нет.

Остальная закалка пошла той же параноидальной линией: таймаут на каждом исходящем POST, чтобы proxy, которое принимает запрос, но никогда не отвечает, не могло застопорить аудиопоток, и гарантия, что подлинная проблема входа никогда не может быть втихую замаскирована машинерией fallback.

Мы протестировали это против настоящего враждебного proxy

Функцию, весь смысл которой — выживать во враждебных сетях, нельзя проверить одними юнит-тестами — у юнит-тестов нет proxy. Прежде чем включить её, мы прогнали реальное приложение через локальное reverse proxy, настроенное делать ровно то, что делают корпоративные proxy: пропускать HTTPS, отклонять upgrade'ы WebSocket с 403.

След в логах — это чек: четыре заблокированные попытки WebSocket, распознанная сигнатура исчерпания, автоматическое переключение на HTTPS-транспорт, а затем здоровая 96-секундная сессия транскрипции по чистому HTTPS — 66 сегментов транскрипта, ноль потерь. Failover работает, потому что мы видели, как он переключается.

Три переносимых урока

  1. «Работает на нестабильном Wi-Fi» и «работает за враждебным proxy» — это разные гарантии. Одной нужно переподключение; другой нужен другой транспорт. Смешивать их — значит оставить целую популяцию корпоративных пользователей втихую сломанной.
  2. Ваша классификация ошибок может спрятать вашу же функцию от неё самой. Правило, верное в 99% случаев (403 = фатальный auth), было ровно неверным для того 1%, ради которого эта функция и существовала. Когда вы добавляете fallback, проверьте, может ли условие срабатывания вообще дойти до fallback.
  3. Тестируйте противника, а не только счастливый путь. Единственный честный тест «выживает proxy, блокирующее WebSocket» — это proxy, блокирующее WebSocket. Мы его построили.

GeekBye v2.0.8 выпустил HTTPS-fallback за флагом и проверенным. О соседней с ним работе над надёжностью смотрите почему ваш ИИ-ассистент для заметок останавливается на плохом Wi-Fi, а о соседних релизах этой серии — почему ваш ИИ-ассистент для заметок останавливает запись посреди встречи (v2.0.9) и почему ИИ-транскрипция перевирает технические термины (v2.0.11).

Похожие статьи

День, когда наше приложение устроило DDoS самому себе
Steven
Steven5 мин. чтения

День, когда наше приложение устроило DDoS самому себе

Копившиеся отложенные загрузки, выпущенные все разом при запуске, превратили каждый клиент GeekBye в маленькую атаку denial-of-service на наши собственные серверы. Исправление — и лестница liveness соединения, которую оно заставило нас построить — одна из самых полезных вещей, которым научил нас v2.

Надёжность
Сети
Инженерия
Почему ИИ-транскрипция перевирает технические термины (и как мы это исправили)
Steven
Steven6 мин. чтения

Почему ИИ-транскрипция перевирает технические термины (и как мы это исправили)

Живая сессия услышала «what is the pointer in C++» как «what is the point in life». Вот путь расследования от того транскрипта до GeekBye v2.0.11 — keyterm biasing, гонка, обрывавшая соединение, и день, когда наш собственный фикс сработал против нас.

Транскрипция
Надёжность
Инженерия
Почему запись экрана захватывает не тот монитор (и как мы это исправили)
Steven
Steven4 мин. чтения

Почему запись экрана захватывает не тот монитор (и как мы это исправили)

На конфигурации с двумя мониторами GeekBye записывал и снимал скриншоты с основного дисплея независимо от того, на каком экране вы работали. Фикс уместился в одну небольшую функцию — но первая её версия была неверной, и код-ревью поймало почему.

Запись экрана
Несколько дисплеев
Инженерия