API Gateway
Что такое API Gateway
API Gateway — единая точка входа для клиентов.
Зачем:
- Один домен для фронта.
- Авторизация в одном месте.
- Rate limiting общий.
- Маршрутизация к сервисам.
- Логирование запросов.
Пример:
api.example.com/* → Laravel
api.example.com/ws/* → Node.js
api.example.com/ai/* → Python
Зачем нужен Gateway, если есть Nginx
Nginx — балансировщик и reverse proxy. API Gateway — умная маршрутизация + бизнес-логика на входе.
Gateway умеет:
- Проверять JWT.
- Лимитировать по пользователю.
- Трансформировать запросы.
- Собирать метрики.
Nginx умеет:
- Терминировать SSL.
- Отдавать статику.
- Балансировать.
- Кэшировать.
Часто Nginx = Gateway. Если логики мало — хватит Nginx.
Nginx как Gateway
Маршрутизация по сервисам:
upstream laravel {
server 127.0.0.1:9000;
}
upstream nodejs {
server 127.0.0.1:3000;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# Laravel API
location /api/ {
fastcgi_pass laravel;
include fastcgi_params;
}
# WebSocket
location /ws/ {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Node.js API
location /realtime/ {
proxy_pass http://nodejs;
}
}
Что даёт: один домен, разные сервисы.
Маршрутизация по путям
Правило:
/api/*— Laravel (бизнес-логика)./ws/*— Node.js (WebSocket)./ai/*— Python (ML)./files/*— статика.
В Laravel ничего не меняется — он обслуживает /api/*.
Node.js слушает /ws/* и /realtime/*.
Авторизация на Gateway
Вариант 1 — на Laravel:
Client → Gateway → Laravel → Sanctum → Service
Плюсы: одна логика. Минусы: Node.js не может проверить.
Вариант 2 — на Gateway (Nginx + Lua / Kong):
location /api/ {
access_by_lua_block {
local jwt = require "resty.jwt"
local token = ngx.req.get_headers()["Authorization"]
-- валидация JWT
}
proxy_pass http://laravel;
}
Плюсы: все сервисы защищены. Минусы: сложнее.
Вариант 3 — общий Redis:
Laravel сохраняет токен в Redis, Node.js читает:
const session = await redis.get(`session:${token}`);
if (!session) return res.status(401).json({ error: 'Unauthorized' });
Плюсы: просто. Минусы: доп. запрос в Redis.
Rate limiting на Gateway
Nginx:
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
location /api/ {
limit_req zone=api burst=200 nodelay;
proxy_pass http://laravel;
}
Что даёт: не более 100 запросов в секунду с одного IP.
Для WebSocket — отдельный лимит:
limit_conn_zone $binary_remote_addr zone=ws:10m;
location /ws/ {
limit_conn ws 5; # 5 соединений на IP
}
Отдача статики
Nginx отдаёт напрямую:
location /storage/ {
alias /var/www/app/storage/app/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Что даёт: не грузить PHP и Node статикой.
CORS на Gateway
Проблема: фронт с app.example.com, API на api.example.com.
Решение 1 — Nginx:
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = 'OPTIONS') {
return 204;
}
fastcgi_pass laravel;
}
Решение 2 — Laravel middleware:
class Cors
{
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', config('app.frontend_url'));
$response->headers->set('Access-Control-Allow-Credentials', 'true');
return $response;
}
}
⚠️ Нельзя одновременно * и credentials: true.
Стриминг ответов
Проблема: AI-ответ генерируется 30 секунд.
Решение — SSE / стрим через Gateway:
location /ai/ {
proxy_pass http://python;
proxy_buffering off; # не буферизировать
proxy_read_timeout 300s;
chunked_transfer_encoding on;
}
Что даёт: клиент видит ответ по кусочкам.
В Laravel:
return response()->stream(function () {
foreach ($this->generateTokens() as $token) {
echo "data: " . json_encode($token) . "\n\n";
ob_flush();
flush();
}
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no', # отключить буфер Nginx
]);
Логирование на Gateway
log_format json_combined escape=json
'{"time":"$time_iso8601",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"duration":$request_time,'
'"upstream":"$upstream_addr",'
'"user_id":"$http_x_user_id"}';
access_log /var/log/nginx/api.log json_combined;
Что даёт: структурированные логи для Grafana / Loki.
Health-check
Каждый сервис:
location /health {
access_log off;
proxy_pass http://laravel/up;
proxy_connect_timeout 1s;
proxy_read_timeout 2s;
}
Что проверяет мониторинг:
curl -f https://api.example.com/health || alert
WebSocket через Gateway
Ключевые настройки:
location /ws/ {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # 1 час
proxy_send_timeout 3600s;
}
⚠️ Без Upgrade и Connection WebSocket не работает.
Sticky sessions (если несколько Node.js):
upstream nodejs {
ip_hash; # один клиент → один сервер
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
Правило: для WebSocket — sticky, или Redis adapter в Socket.IO.
Кэширование на Gateway
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api:10m max_size=1g;
location /api/public/ {
proxy_cache api;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_pass http://laravel;
add_header X-Cache-Status $upstream_cache_status;
}
Что даёт: публичные GET-запросы кэшируются на Nginx.
Практика: настроить Gateway
1. Nginx-конфиг:
upstream laravel { server 127.0.0.1:9000; }
upstream nodejs { server 127.0.0.1:3000; }
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
location /api/ {
limit_req zone=api burst=200 nodelay;
fastcgi_pass laravel;
include fastcgi_params;
}
location /ws/ {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
location /storage/ {
alias /var/www/app/storage/app/public/;
expires 30d;
}
location /health {
access_log off;
return 200 'ok';
}
}
2. Проверить:
nginx -t
systemctl reload nginx
curl -I https://api.example.com/health
3. Проверить WebSocket:
websocat wss://api.example.com/ws
Частые ошибки
1. WebSocket без Upgrade:
404 или 400 на handshake
Решение: заголовки Upgrade и Connection.
2. CORS + credentials:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Решение: конкретный домен, не *.
3. Один upstream на все сервисы:
Laravel и Node.js на одном порту
Решение: разные upstream, разные location.
4. Нет таймаута для WS:
Соединение рвётся через 60 сек
Решение: proxy_read_timeout 3600s.
5. Забыли health-check:
Load balancer не знает, что сервис упал
Решение: /health endpoint.
6. Нет rate limit:
DDoS кладёт сервис
Решение: limit_req_zone.
Итог
- Gateway — единая точка входа.
- Nginx — обычно хватает.
- Маршрутизация по путям:
/api,/ws,/ai. - Авторизация — на Laravel или Redis.
- Rate limiting — обязательно.
- WebSocket —
Upgrade+Connection+ большой timeout. - Статика — отдавать напрямую.
- Health-check — для мониторинга.
- CORS — конкретный origin.
Дальше — RabbitMQ.