
Транскрипция в реальном времени, когда файрвол блокирует WebSocket
Корпоративные сети обожают пропускать HTTPS и втихую убивать upgrade до WebSocket. Это незаметно ломает транскрипцию в реальном времени. GeekBye v2.0.8 автоматически переключается на чисто-HTTPS транспорт — а его выпуск вскрыл баг, который сделал бы всю функцию бесполезной.
Есть один конкретный, доводящий до бешенства способ, которым транскрипция в реальном времени отказывает в корпоративной сети. Ваш 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 работает, потому что мы видели, как он переключается.
Три переносимых урока
- «Работает на нестабильном Wi-Fi» и «работает за враждебным proxy» — это разные гарантии. Одной нужно переподключение; другой нужен другой транспорт. Смешивать их — значит оставить целую популяцию корпоративных пользователей втихую сломанной.
- Ваша классификация ошибок может спрятать вашу же функцию от неё самой. Правило, верное в 99% случаев (403 = фатальный auth), было ровно неверным для того 1%, ради которого эта функция и существовала. Когда вы добавляете fallback, проверьте, может ли условие срабатывания вообще дойти до fallback.
- Тестируйте противника, а не только счастливый путь. Единственный честный тест «выживает proxy, блокирующее WebSocket» — это proxy, блокирующее WebSocket. Мы его построили.
GeekBye v2.0.8 выпустил HTTPS-fallback за флагом и проверенным. О соседней с ним работе над надёжностью смотрите почему ваш ИИ-ассистент для заметок останавливается на плохом Wi-Fi, а о соседних релизах этой серии — почему ваш ИИ-ассистент для заметок останавливает запись посреди встречи (v2.0.9) и почему ИИ-транскрипция перевирает технические термины (v2.0.11).