запрос на анализ: XHR onerror при загрузке >100KB
This commit is contained in:
@@ -0,0 +1,102 @@
|
|||||||
|
# Запрос на анализ: XHR onerror («Сеть») при POST /upload с телом > 100KB
|
||||||
|
|
||||||
|
**Дата:** 2026-06-17
|
||||||
|
**Версия:** v1.40
|
||||||
|
**ТОЛЬКО АНАЛИЗ. КОД НЕ ПРАВИТЬ.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Симптом
|
||||||
|
|
||||||
|
Браузер отправляет XHR POST на `/upload` с JSON-телом ~200KB (base64 файла). Прогресс 100% (данные ушли). Затем `xhr.onerror` → «Сеть». Сервер иногда успевает обработать (логи подтверждают), иногда нет.
|
||||||
|
|
||||||
|
Прямой curl с такими же данными — иногда HTTP 200, иногда ReadTimeout.
|
||||||
|
|
||||||
|
## Что ПРОБОВАЛИ (40 версий)
|
||||||
|
|
||||||
|
| v | Подход | Результат |
|
||||||
|
|---|---|---|
|
||||||
|
| 1.0-1.13 | FormData multipart POST | ✗ Сеть |
|
||||||
|
| 1.14-1.20 | Чанки 50KB, пул БД, keepalive | ✗ |
|
||||||
|
| 1.21-1.25 | JSON/base64, TEXT вместо BYTEA | частично ✓ |
|
||||||
|
| 1.26 | `original_bytes DROP NOT NULL` + `original_b64 TEXT` | ✓ РАБОТАЛО (!) |
|
||||||
|
| 1.27-1.36 | camelot, tabula, libreoffice для PDF | регресс загрузки |
|
||||||
|
| 1.37 | Логи в памяти | ✗ |
|
||||||
|
| 1.38-1.39 | Redis очередь (RPUSH → 200 сразу) | ✗ |
|
||||||
|
| 1.40 | Сырой regex вместо get_json() | ✗ |
|
||||||
|
|
||||||
|
## Текущий код (самое быстрое что смогли)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# app.py — НЕТ JSON-парсинга, НЕТ base64.decode(), НЕТ DB INSERT
|
||||||
|
def _upload_json_impl(self):
|
||||||
|
raw = request.get_data(as_text=True)
|
||||||
|
import re
|
||||||
|
m = re.search(r'"data"\s*:\s*"([^"]*)"', raw)
|
||||||
|
b64 = m.group(1)
|
||||||
|
m = re.search(r'"filename"\s*:\s*"([^"]*)"', raw)
|
||||||
|
filename = m.group(1) if m else ""
|
||||||
|
m = re.search(r'"cid"\s*:\s*"([^"]*)"', raw)
|
||||||
|
cid = m.group(1) if m else None
|
||||||
|
|
||||||
|
# Контракт — синхронно (быстро)
|
||||||
|
if not cid:
|
||||||
|
conn = db.connect()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("INSERT INTO contracts ... RETURNING id")
|
||||||
|
cid = cur.fetchone()[0]
|
||||||
|
conn.commit()
|
||||||
|
db.put_conn(conn)
|
||||||
|
|
||||||
|
# Файл — в Redis (мгновенно, без DB)
|
||||||
|
redis_client.push({"filename": filename, "b64": b64, "mime": mime, "cid": str(cid)})
|
||||||
|
return jsonify({"contract_id": str(cid)})
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# db.py — пул с keepalive
|
||||||
|
ThreadedConnectionPool(minconn=2, maxconn=10, keepalives=1, connect_timeout=10)
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// upload.html — XHR без ретраев
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/upload');
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
xhr.timeout = 30000;
|
||||||
|
xhr.onerror = function() { reject(new Error('Сеть')); };
|
||||||
|
xhr.send(JSON.stringify({filename: file.name, data: b64, cid: cid || null}));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инфраструктура
|
||||||
|
|
||||||
|
```
|
||||||
|
Браузер → ddos-guard → nginx/Ingress → gunicorn → Flask
|
||||||
|
↓
|
||||||
|
Redis (RPUSH) → воркер (BRPOP → DB)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Платформа: pythonk8s.services.ngcloud.ru (managed)
|
||||||
|
- Redis: redisk8s....svc.cluster.local
|
||||||
|
- PostgreSQL: postgresqlk8s-master...svc.cluster.local
|
||||||
|
- Пул БД: 2-10 соединений, keepalive
|
||||||
|
|
||||||
|
## Тесты curl
|
||||||
|
|
||||||
|
```
|
||||||
|
100KB #1: HTTP:000
|
||||||
|
100KB #2: HTTP:000
|
||||||
|
100KB #3: HTTP:200
|
||||||
|
100KB #4: HTTP:000
|
||||||
|
100KB #5: HTTP:000
|
||||||
|
|
||||||
|
10KB: всегда HTTP 200
|
||||||
|
```
|
||||||
|
|
||||||
|
## Вопросы
|
||||||
|
|
||||||
|
1. Почему `request.get_data()` для 200KB тела иногда убивает соединение ещё до ответа? Это на уровне gunicorn/Flask/Werkzeug?
|
||||||
|
2. Может ли nginx/Ingress иметь `client_max_body_size` или `proxy_request_buffering` который режет тело?
|
||||||
|
3. Почему v1.26 работал, а v1.40 с тем же `/upload` (без изменений в логике) — нет?
|
||||||
|
4. Может ли проблема быть в HTTP/2 vs HTTP/1.1? curl с `--http2` иногда помогает на этом хостинге.
|
||||||
|
5. Redis внутри кластера — может ли `redis.Redis()` блокироваться на DNS-резолве `.svc.cluster.local`?
|
||||||
Reference in New Issue
Block a user