168 lines
6.3 KiB
Markdown
168 lines
6.3 KiB
Markdown
# Обход DPI РКН для передачи аудио на Groq Whisper
|
||
|
||
**Дата:** 2026-05-22
|
||
**Итог:** WebSocket — тупик. Рабочее решение — `fetch POST` с `multipart/form-data`.
|
||
|
||
---
|
||
|
||
## Результаты тестов DPI (Россия -> Германия, proxy.kube5s.ru)
|
||
|
||
| Метод | Размер | Результат |
|
||
|-------|--------|-----------|
|
||
| HTTP POST с JSON-телом | 40KB JSON | ✅ |
|
||
| HTTP POST с JSON-телом | 133KB JSON | ✅ |
|
||
| HTTP POST с JSON-телом | 200KB JSON | ⚠️ погранично |
|
||
| HTTP POST multipart/form-data | 30-150KB blob | ✅ все |
|
||
| WebSocket text-фрейм | <=45KB JSON | ✅ |
|
||
| WebSocket text-фрейм | >45KB JSON | ❌ |
|
||
| WebSocket чанки по 14KB + задержки | любой | ❌ |
|
||
| WebSocket binary-фрейм | любой | ❌ |
|
||
|
||
**Вывод:** DPI целенаправленно блокирует WebSocket-трафик. Обычный HTTPS POST проходит до ~130KB JSON или 150KB multipart.
|
||
|
||
---
|
||
|
||
## Рабочее решение (v62+)
|
||
|
||
### Архитектура
|
||
|
||
```
|
||
Браузер (РФ) proxy.kube5s.ru (Германия) api.groq.com
|
||
| | |
|
||
| POST /openai/v1/audio/ | |
|
||
| transcriptions | |
|
||
| Content-Type: multipart/ | |
|
||
| form-data | |
|
||
| Authorization: Bearer KEY | |
|
||
| file=<blob.webm> | |
|
||
| model=whisper-large-v3 | |
|
||
| language=it ---------------->| |
|
||
| | прокси пробрасывает ------>|
|
||
| | Whisper
|
||
| | <------- JSON {text} -----|
|
||
| <------- JSON {text} --------| |
|
||
```
|
||
|
||
### Ключевые моменты
|
||
|
||
1. Никаких WebSocket — DPI их палит
|
||
2. Никакого base64 — Blob идёт как есть в multipart
|
||
3. Никакого JSON для аудио — бинарные данные в FormData
|
||
4. Сервер НЕ менялся — универсальный Flask-прокси форвардит всё в Groq
|
||
|
||
### Клиент (index.html v62)
|
||
|
||
```javascript
|
||
async function transcribe(blob) {
|
||
const form = new FormData();
|
||
form.append('file', blob, 'audio.webm');
|
||
form.append('model', 'whisper-large-v3');
|
||
form.append('language', 'it');
|
||
|
||
const r = await fetch(
|
||
'https://proxy.kube5s.ru/openai/v1/audio/transcriptions',
|
||
{ method: 'POST',
|
||
headers: { 'Authorization': 'Bearer ' + GROQ_API_KEY },
|
||
body: form }
|
||
);
|
||
const data = await r.json();
|
||
return { text: data.text || '', error: data.error ? data.error : null };
|
||
}
|
||
```
|
||
|
||
### Сервер (proxy.py) — без изменений
|
||
|
||
Универсальный прокси: любой метод, любой путь -> api.groq.com.
|
||
Пробрасывает Authorization и Content-Type, стримит ответ.
|
||
|
||
### Nginx (groq-proxy.conf)
|
||
|
||
```nginx
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8765;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
proxy_request_buffering off;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Тупиковые подходы
|
||
|
||
### WebSocket + base64 (v45-v61)
|
||
|
||
Идея: кодировать аудио в base64, отправлять текстовыми WS-фреймами.
|
||
|
||
Почему провалилось:
|
||
- Одиночный фрейм до ~45KB JSON проходил, больше — нет
|
||
- Чанки по 14-30KB с задержками — DPI обрывал соединение на ~3-4 фрейме
|
||
- Задержки между фреймами (150-200мс) не помогали
|
||
- Результат хаотичный: 3 фрейма ОК -> 4 таймаут -> 5 ОК -> 8 таймаут
|
||
- DPI детектит WebSocket-паттерн и режет независимо от содержимого
|
||
|
||
---
|
||
|
||
## Фонетический анализ: таймаут decodeAudioData
|
||
|
||
decodeAudioData в Chrome может навсегда зависнуть на WebM/Opus >2с (баг Chrome, не DPI).
|
||
|
||
```javascript
|
||
const phonTimeout = new Promise(resolve =>
|
||
setTimeout(() => resolve({ pitchHz:0, pitchStability:0, voiceClarity:0, phonScore:0 }), 15000)
|
||
);
|
||
[whisperResult, phon] = await Promise.all([
|
||
transcribe(blob),
|
||
Promise.race([phoneticAnalysis(blob), phonTimeout])
|
||
]);
|
||
```
|
||
|
||
При таймауте — фонетика показывает нули, транскрипция работает.
|
||
|
||
---
|
||
|
||
## Файлы и деплой
|
||
|
||
```
|
||
Сервер (Vultr 95.179.252.111):
|
||
/opt/groq-proxy/proxy.py — HTTP прокси (Flask/gunicorn :8765) ОСНОВНОЙ
|
||
/opt/groq-proxy/ws_server.py — WS сервер (:8766) БОЛЬШЕ НЕ ИСПОЛЬЗУЕТСЯ
|
||
/etc/nginx/conf.d/groq-proxy.conf
|
||
|
||
Клиент (K8s capire.kube5s.ru):
|
||
~/lang/index.html — SPA
|
||
~/lang/deploy_lang.sh — деплой
|
||
|
||
Команды:
|
||
ssh -i ~/.ssh/vultr_openssh root@95.179.252.111
|
||
cd ~/lang && bash deploy_lang.sh
|
||
systemctl restart groq-http
|
||
```
|
||
|
||
---
|
||
|
||
## Хронология версий
|
||
|
||
| Версия | Подход | Результат |
|
||
|--------|--------|-----------|
|
||
| v34-v44 | WAV->WebM, HTTP chunked | ❌ |
|
||
| v45-v47 | WS binary, чанки base64 | ❌ |
|
||
| v48 | WS один JSON base64 (~30KB) | ✅ короткие |
|
||
| v49-v53 | Фиксы WS race condition | ❌ откачены |
|
||
| v54-v59 | Чанки base64 в WS | ❌ |
|
||
| v60-v61 | Чанки 14KB + задержки | ❌ |
|
||
| **v62** | **fetch POST multipart/form-data** | **✅ РАБОТАЕТ** |
|
||
|
||
---
|
||
|
||
## Тестовые скрипты (в репозитории)
|
||
|
||
| Файл | Что тестирует |
|
||
|------|---------------|
|
||
| test_http.py | HTTP POST JSON разных размеров |
|
||
| test_multipart.py | HTTP POST multipart/form-data |
|
||
| test_chunks.py | WS чанки с задержками |
|
||
| test_threshold.py | Порог DPI для кол-ва WS фреймов |
|
||
| test_hello.py | Минимальный WS: connect + 1 сообщение |
|
||
| test_single.py | Один большой WS фрейм |
|