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).

Схожі статті

Ми видалили 5 000 рядків аудіокоду — і транскрипти почали з'являтися двічі
Steven
Steven5 хв читання

Ми видалили 5 000 рядків аудіокоду — і транскрипти почали з'являтися двічі

GeekBye v1.6 вирвав два вбудовані Swift-транскрайбери й замінив їх одним уніфікованим конвеєром — чиста різниця понад 5 000 видалених рядків, зроблено просто тоді, коли люди були посеред зустрічі в застосунку. А потім транскрипти почали з'являтися двічі, бо баг переїхав на той єдиний шар, який досі вважав, що двигунів два.

Інженерія
Транскрипція
Архітектура
Баг з навушниками-вкладишами: коли співрозмовник зникав з нашої транскрипції
Steven
Steven6 хв читання

Баг з навушниками-вкладишами: коли співрозмовник зникав з нашої транскрипції

Двоканальна транскрипція працювала бездоганно на динаміках ноутбука та на повнорозмірних навушниках. Потім хтось вставив навушники-вкладиші — і людина, з якою він говорив, просто зникала з транскрипції, бо Chromium мовчки приглушував наше аудіо тієї миті, коли співрозмовник починав говорити. Одинадцять виправлень провалилися. Дванадцяте змінило всю архітектуру.

Інженерія
Аудіо
Транскрипція
День, коли наш застосунок влаштував DDoS самому собі
Steven
Steven5 хв читання

День, коли наш застосунок влаштував DDoS самому собі

Черга відкладених завантажень, випущена вся разом при запуску, перетворила кожен клієнт GeekBye на маленьку атаку denial-of-service на наші власні сервери. Виправлення — і драбина liveness з’єднання, яку воно змусило нас побудувати — одна з найкорисніших речей, яких навчив нас v2.

Надійність
Мережі
Інженерія