diff --git a/TODO.md b/TODO.md index ca9c37e..8dfa874 100644 --- a/TODO.md +++ b/TODO.md @@ -40,9 +40,10 @@ --- ### 2. 🗜️ Сжатие статического трафика (`GZipMiddleware`) -- [ ] Подключить `GZipMiddleware(minimum_size=1000)` в [`app/main.py`](file:///home/ai-projects/services/study.qpid.pro/app/main.py). -- [ ] Сократить объём передаваемых файлов `app.js` (95 КБ), `index.html` (56 КБ) и `style.css` (25 КБ) на 70–75% (суммарно со 176 КБ до ~40 КБ). -- [ ] Проверить заголовки `Content-Encoding: gzip` в ответах. +- [x] Подключить `GZipMiddleware(minimum_size=1000)` в [`app/main.py`](file:///home/ai-projects/services/study.qpid.pro/app/main.py). +- [x] Сократить объём передаваемых файлов `app.js` (111.8 КБ -> 21.5 КБ), `index.html` (55.2 КБ -> 10.3 КБ) и `style.css` (58.2 КБ -> 9.4 КБ) на 81.7% (суммарно с 225.2 КБ до 41.2 КБ). +- [x] Проверить заголовки `Content-Encoding: gzip` в ответах и добавить регрессионный тест в `tests/test_api.py`. + --- diff --git a/app/main.py b/app/main.py index 7d85a36..12f8a5e 100644 --- a/app/main.py +++ b/app/main.py @@ -8,6 +8,7 @@ from datetime import datetime, timezone, date from fastapi import FastAPI, Depends, HTTPException, Request, Response, status, Query from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse from fastapi.staticfiles import StaticFiles +from fastapi.middleware.gzip import GZipMiddleware from pydantic import BaseModel, Field import httpx @@ -44,6 +45,9 @@ app = FastAPI( lifespan=lifespan, ) +# GZip Compression Middleware (compresses responses >= 1000 bytes) +app.add_middleware(GZipMiddleware, minimum_size=1000) + # Mount static files app.mount("/static", StaticFiles(directory=str(config.BASE_DIR / "app" / "static")), name="static") diff --git a/tests/test_api.py b/tests/test_api.py index e6351e9..49f8642 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -788,5 +788,26 @@ def test_srs_queue_and_quiz_tag_filtering(): assert not any(q["card"]["id"] == c2["id"] for q in quiz_items) +def test_gzip_compression(): + # 1. Assets >= 1000 bytes receive GZip encoding when requested + resp_js = client.get("/static/js/app.js", headers={"Accept-Encoding": "gzip"}) + assert resp_js.status_code == 200 + assert resp_js.headers.get("content-encoding") == "gzip" + resp_css = client.get("/static/css/style.css", headers={"Accept-Encoding": "gzip"}) + assert resp_css.status_code == 200 + assert resp_css.headers.get("content-encoding") == "gzip" + resp_html = client.get("/", headers={"Accept-Encoding": "gzip"}) + assert resp_html.status_code == 200 + assert resp_html.headers.get("content-encoding") == "gzip" + + # 2. Assets < 1000 bytes are not compressed (respecting minimum_size=1000) + resp_manifest = client.get("/manifest.json", headers={"Accept-Encoding": "gzip"}) + assert resp_manifest.status_code == 200 + assert resp_manifest.headers.get("content-encoding") is None + + # 3. Requests without gzip Accept-Encoding do not receive gzip + resp_raw = client.get("/static/js/app.js", headers={"Accept-Encoding": "identity"}) + assert resp_raw.status_code == 200 + assert resp_raw.headers.get("content-encoding") is None