Files
image-gateway/docs/usage-guide.md
S.Gromov 0751c4b469 feat: добавить Image Gateway с кешем Souin
- добавлена сборка Caddy с Souin, Otter и NutsDB

- добавлена конфигурация dev, prod и test Docker Compose

- настроено кеширование через Otter L1 и NutsDB L2

- добавлены e2e-тесты Bun для кеша, restart и purge

- добавлена документация по запуску, API кеша и тестам
2026-05-04 12:18:37 +03:00

273 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Гайд использования
Руководство по кейсам — копируй, вставляй, проверяй. Без обдумывания.
## 1. Запуск
```bash
cp .env.example .env
docker compose -f docker-compose.dev.yml up -d --build
```
Проверка:
```bash
curl -s -o /dev/null -w "%{http_code}" \
"http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/plain/https://picsum.photos/200/200"
# → 200
```
## 2. Обработка изображений
### Resize — вписать в ширину 800px
```bash
curl -s -o /tmp/fit800.jpg \
"http://localhost:8888/unsafe/resize:fit:800:0:0/q:80/plain/https://picsum.photos/1200/800"
# → файл /tmp/fit800.jpg, ширина = 800px
```
### Resize — вписать в квадрат 200x200
```bash
curl -s -o /tmp/fit200.jpg \
"http://localhost:8888/unsafe/resize:fit:200:200:0/q:80/plain/https://picsum.photos/1200/800"
# → файл 200x?, пропорции сохранены
```
### Resize — заполнить 400x300 (с обрезкой)
```bash
curl -s -o /tmp/fill400.jpg \
"http://localhost:8888/unsafe/resize:fill:400:300:0/g:ce/q:80/plain/https://picsum.photos/1200/800"
# → файл точно 400x300, лишнее обрезано по центру
```
### Crop — обрезка 200x200
```bash
curl -s -o /tmp/crop200.jpg \
"http://localhost:8888/unsafe/crop:200:200/plain/https://picsum.photos/1200/800"
# → файл 200x200
```
### WebP — конвертация формата
```bash
curl -s -D - -o /tmp/webp.webp \
"http://localhost:8888/unsafe/format:webp/q:80/plain/https://picsum.photos/800/600" \
2>&1 | grep Content-Type
# → Content-Type: image/webp
```
### AVIF
```bash
curl -s -D - -o /tmp/avif.avif \
"http://localhost:8888/unsafe/format:avif/q:80/plain/https://picsum.photos/800/600" \
2>&1 | grep Content-Type
# → Content-Type: image/avif
```
### Качество — сравнить q:10 vs q:100
```bash
curl -s -o /tmp/q10.jpg \
"http://localhost:8888/unsafe/resize:fit:400:0:0/q:10/plain/https://picsum.photos/800/600"
curl -s -o /tmp/q100.jpg \
"http://localhost:8888/unsafe/resize:fit:400:0:0/q:100/plain/https://picsum.photos/800/600"
ls -la /tmp/q10.jpg /tmp/q100.jpg
# → q10 ~4KB, q100 ~50KB — разница ~12x
```
### Комбинированные — resize + WebP + качество
```bash
curl -s -o /tmp/combined.webp \
"http://localhost:8888/unsafe/resize:fit:800:0:0/g:ce/q:75/format:webp/plain/https://picsum.photos/1200/800"
# → 800px по ширине, WebP, качество 75
```
### base64url — закодированный source URL
```bash
B64=$(echo -n "https://picsum.photos/800/600" | base64 -w0 | tr '+/' '-_' | tr -d '=')
curl -s -o /tmp/b64.jpg "http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/$B64"
# → работает как plain/, но без /plain/ префикса
```
## 3. Кеширование
### MISS → HIT — базовая проверка
```bash
URL="http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/plain/https://picsum.photos/400/300"
# Первый запрос — MISS (обработка через imgproxy)
curl -s -o /dev/null -D - -w "\ntime: %{time_total}s\n" "$URL" | grep -E "Cache-Status|time"
# → Cache-Status: Souin; fwd=uri-miss; stored
# → time: 0.5s
# Второй запрос — HIT (из кеша)
curl -s -o /dev/null -D - -w "\ntime: %{time_total}s\n" "$URL" | grep -E "Cache-Status|time"
# → Cache-Status: Souin; hit; ttl=31535999; detail=DEFAULT
# → time: 0.001s
```
### Что значит Cache-Status
```
Cache-Status: Souin; hit; ... → из кеша
Cache-Status: Souin; fwd=uri-miss; stored; key=GET-... → первый запрос, закешировано
Cache-Status: Souin; fwd=uri-miss; detail=UNCACHEABLE-... → не закешировано (ошибка upstream)
```
### Разные размеры = разные ключи
```bash
SRC="https://picsum.photos/id/42/800/600"
curl -s -o /dev/null "http://localhost:8888/unsafe/resize:fit:200:0:0/q:80/plain/$SRC"
curl -s -o /dev/null "http://localhost:8888/unsafe/resize:fit:400:0:0/q:80/plain/$SRC"
curl -s "http://localhost:2019/souin-api/souin/"
# → 2 ключа: ...resize:fit:200... и ...resize:fit:400...
```
## 4. Purge кеша
> Все purge-запросы идут через Caddy Admin API на порту **2019**.
### Purge — сбросить всё
```bash
# Закешировать
curl -s -o /dev/null "http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/plain/https://picsum.photos/400/300"
# Purge
curl -s -w "status: %{http_code}\n" -X PURGE http://localhost:2019/souin-api/souin/flush
# → status: 204
# Проверить — снова MISS
curl -s -o /dev/null -D - "http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/plain/https://picsum.photos/400/300" \
| grep Cache-Status
# → Cache-Status: Souin; fwd=uri-miss; stored
```
### Purge — конкретное изображение (все размеры)
```bash
# Закешировать 2 размера
SRC="https://picsum.photos/id/77/800/600"
curl -s -o /dev/null "http://localhost:8888/unsafe/resize:fit:200:0:0/q:80/plain/$SRC"
curl -s -o /dev/null "http://localhost:8888/unsafe/resize:fit:400:0:0/q:80/plain/$SRC"
# Purge по regex (id/77)
curl -s -w "status: %{http_code}\n" -X PURGE "http://localhost:2019/souin-api/souin/.*id/77.*"
# → status: 204
# Оба размера сброшены
```
### Purge — конкретный размер
```bash
curl -s -w "status: %{http_code}\n" -X PURGE \
"http://localhost:2019/souin-api/souin/.*resize:fit:200.*id/77.*$"
# → status: 204 — только 200px сброшен, 400px остался
```
## 5. Caddy Admin API (Souin)
Порт **2019**, только localhost.
### Список закешированных ключей
```bash
curl -s http://localhost:2019/souin-api/souin/
# → ["GET-http-localhost:8888-/unsafe/resize:fit:200:0:0/q:80/plain/https://..."]
```
### Purge — все методы
```bash
# Полный сброс
curl -X PURGE http://localhost:2019/souin-api/souin/flush
# → 204
# По regex
curl -X PURGE "http://localhost:2019/souin-api/souin/.*example.com/photo.jpg"
# → 204
# Точное совпадение (с $ в конце)
curl -X PURGE "http://localhost:2019/souin-api/souin/.*resize:fit:800.*photo\.jpg$"
# → 204
```
## 6. Signed vs Unsigned
### Unsigned — по умолчанию
Ключи не заданы → `/unsafe/` работает:
```bash
# С /unsafe/ → работает
curl -s -o /dev/null -w "%{http_code}" \
"http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/plain/https://picsum.photos/200/200"
# → 200
# Без /unsafe/ → тоже работает (ключи не заданы, подпись не проверяется)
curl -s -o /dev/null -w "%{http_code}" \
"http://localhost:8888/resize:fit:100:0:0/q:80/plain/https://picsum.photos/200/200"
# → 200
```
### Signed — включить подпись
1. Сгенерировать ключи:
```bash
openssl rand -hex 32 # → KEY
openssl rand -hex 32 # → SALT
```
2. Добавить в `.env`:
```env
IMGPROXY_KEY=<сгенерированный_ключ>
IMGPROXY_SALT=<сгенерированная_соль>
```
3. Перезапустить:
```bash
docker compose -f docker-compose.dev.yml restart imgproxy
```
4. Проверить:
```bash
# Без /unsafe/ и без подписи → ошибка
curl -s -o /dev/null -w "%{http_code}" \
"http://localhost:8888/resize:fit:100:0:0/q:80/plain/https://picsum.photos/200/200"
# → 403
# С /unsafe/ → тоже ошибка (ключи заданы, unsafe запрещён)
curl -s -o /dev/null -w "%{http_code}" \
"http://localhost:8888/unsafe/resize:fit:100:0:0/q:80/plain/https://picsum.photos/200/200"
# → 403
```
5. Подписать URL:
```bash
KEY="<сгенерированный_ключ>"
SALT="<сгенерированная_соль>"
PATH_URL="/resize:fit:100:0:0/q:80/plain/https://picsum.photos/200/200"
SIG=$(echo -n "$SALT" | xxd -r -p | openssl dgst -sha256 -hmac "$(echo -n "$KEY" | xxd -r -p)" -binary | base64 | tr '+/' '-_' | tr -d '=' | head -c 32)
curl -s -o /dev/null -w "%{http_code}" "http://localhost:8888/${SIG}${PATH_URL}"
# → 200
```
6. Вернуть unsigned — убрать ключи из `.env` и перезапустить.