4.0 KiB
4.0 KiB
Запрос на анализ: 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() | ✗ |
Текущий код (самое быстрое что смогли)
# 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)})
# db.py — пул с keepalive
ThreadedConnectionPool(minconn=2, maxconn=10, keepalives=1, connect_timeout=10)
// 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
Вопросы
- Почему
request.get_data()для 200KB тела иногда убивает соединение ещё до ответа? Это на уровне gunicorn/Flask/Werkzeug? - Может ли nginx/Ingress иметь
client_max_body_sizeилиproxy_request_bufferingкоторый режет тело? - Почему v1.26 работал, а v1.40 с тем же
/upload(без изменений в логике) — нет? - Может ли проблема быть в HTTP/2 vs HTTP/1.1? curl с
--http2иногда помогает на этом хостинге. - Redis внутри кластера — может ли
redis.Redis()блокироваться на DNS-резолве.svc.cluster.local?