LoadTest: проверка загрузки файлов (Flask, site/app.py)

This commit is contained in:
naeel
2026-08-17 17:05:20 +04:00
commit 3f1c69a723
5 changed files with 435 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
"""
LoadTest - сервис проверки загрузки файлов на платформу Nubes (Flask).
ЦЕЛЬ: проверить, доходят ли файлы (в т.ч. большие) до бэкенда или нет.
Файл принимается в ПАМЯТЬ (без записи на диск) и возвращается МАКСИМУМ статистики.
Обязательная структура для платформы Nubes:
repo/
├── requirements.txt
└── site/
├── app.py <- этот файл (точка входа: python site/app.py)
├── static/style.css
└── templates/index.html
"""
import hashlib
import time
from flask import Flask, jsonify, render_template, request
# Явно указываем папки шаблонов и статики -
# платформа запускает `python site/app.py` из корня репо, иначе Flask не найдёт шаблоны.
app = Flask(__name__, template_folder="templates", static_folder="static")
# ВАЖНО: НЕ ставим MAX_CONTENT_LENGTH (по умолчанию лимита нет).
# Цель - проверить РЕАЛЬНЫЕ лимиты платформы/прокси, а не собственный лимит Flask.
@app.route("/")
def index():
"""Главная страница - форма выбора файла и загрузки."""
return render_template("index.html")
@app.route("/health")
def health():
"""Проверка живости для платформы."""
return "ok"
@app.route("/upload", methods=["POST"])
def upload():
"""Приём файла в память + сбор максимальной статистики."""
t0 = time.time() # момент начала обработки запроса на сервере
# Метаданные запроса (заголовки, адрес клиента) - для диагностики.
meta = {
"server_start": time.strftime("%Y-%m-%d %H:%M:%S"),
"remote_addr": request.remote_addr or "",
"content_length_header": request.headers.get("Content-Length", ""),
"content_type_header": request.headers.get("Content-Type", ""),
"user_agent": request.headers.get("User-Agent", ""),
}
# Валидация: должен прийти multipart-файл в поле "file".
if "file" not in request.files:
return jsonify({"ok": False, "error": "Нет поля 'file' в запросе", "meta": meta}), 400
f = request.files["file"]
if not f or f.filename == "":
return jsonify({"ok": False, "error": "Пустое имя файла", "meta": meta}), 400
# Читаем ВЕСЬ файл в память и замеряем время чтения.
t_read = time.time()
data = f.read()
read_s = time.time() - t_read
size = len(data) # сколько байт РЕАЛЬНО дошло до сервера
total_s = time.time() - t0 # полное время обработки запроса на сервере
result = {
"ok": True,
"file": {
"name": f.filename,
"mime": f.mimetype or "",
"size_bytes": size,
"size_mb": round(size / (1024 * 1024), 3),
},
"server": {
"read_ms": round(read_s * 1000, 2), # время чтения в память
"total_ms": round(total_s * 1000, 2), # полное время обработки
"speed_mb_s": round(size / (1024 * 1024) / read_s, 3) if read_s > 0 else 0,
},
"md5": hashlib.md5(data).hexdigest(), # контрольная сумма принятых байт
"meta": meta,
}
return jsonify(result)
if __name__ == "__main__":
# ОБЯЗАТЕЛЬНО для платформы: `python site/app.py` должен стартовать сервер.
# debug=False - прод-режим, без reloader (иначе поднимается второй процесс).
app.run(host="0.0.0.0", port=5000, debug=False)