feat: добавить Image Gateway с кешем Souin
- добавлена сборка Caddy с Souin, Otter и NutsDB - добавлена конфигурация dev, prod и test Docker Compose - настроено кеширование через Otter L1 и NutsDB L2 - добавлены e2e-тесты Bun для кеша, restart и purge - добавлена документация по запуску, API кеша и тестам
This commit is contained in:
272
docs/usage-guide.md
Normal file
272
docs/usage-guide.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Гайд использования
|
||||
|
||||
Руководство по кейсам — копируй, вставляй, проверяй. Без обдумывания.
|
||||
|
||||
## 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` и перезапустить.
|
||||
Reference in New Issue
Block a user