API Gateway
API Gateway نقطه ورود مرکزی همه درخواستها به سیستم میکروسرویس است. مثل یک «دربان» تمام ترافیک ورودی را مدیریت و به سرویس مناسب route میکند.
۴.۱ مقدمه
API Gateway نقطه ورود مرکزی همه درخواستها به سیستم میکروسرویس است. مثل یک «دربان» تمام ترافیک ورودی را مدیریت و به سرویس مناسب route میکند.
هدف این فصل: درک نقش API Gateway، آشنایی با ابزارهای رایج (Kong، Traefik، NGINX)، پیادهسازی یک Gateway ساده با FastAPI، و مفاهیم Rate Limiting، Authentication، و Load Balancing.
۴.۲ API Gateway چیست؟
API Gateway یک سرور است که بین client ها و میکروسرویسها قرار میگیرد. مسئولیتهای آن:
بدون API Gateway:
[Client] ──┬──► User Service
├──► Product Service
├──► Order Service
└──► Payment Service
مشکلات:
- Client باید آدرس همه سرویسها را بداند
- Authentication در هر سرویس تکرار میشود
- CORS, Rate Limiting, Logging پراکنده
با API Gateway:
[Client] ──► [API Gateway] ──┬──► User Service
├──► Product Service
├──► Order Service
└──► Payment Service
مزایا:
- یک نقطه ورود واحد
- Authentication مرکزی
- Logging و Monitoring متمرکز
- Rate Limiting سراسری
۴.۳ مسئولیتهای API Gateway
🚦 Routing
هدایت درخواستها به سرویس مناسب بر اساس URL path، header، یا method.
/api/users/* → user-service:8001
/api/products/* → product-service:8002
🔐 Authentication & Authorization
بررسی JWT یا API key قبل از forward کردن درخواست. سرویسها فرض میکنند کاربر معتبر است.
⏱️ Rate Limiting
محدود کردن تعداد درخواست هر کاربر برای جلوگیری از abuse و DDoS.
⚖️ Load Balancing
توزیع ترافیک بین چند instance یک سرویس برای scalability.
🔄 Request/Response Transformation
تغییر header، rewrite کردن body، تبدیل فرمت (مثلاً XML به JSON).
💾 Caching
cache کردن پاسخهای پرتکرار برای کاهش بار سرویسها.
📊 Logging & Monitoring
ثبت تمام درخواستها در یک نقطه برای analytics و debugging.
🛡️ Security
HTTPS termination، CORS، WAF، IP whitelist/blacklist.
🔌 Circuit Breaking
قطع موقت ترافیک به سرویسهای ناسالم برای جلوگیری از cascading failure.
📦 API Composition
ترکیب پاسخ چند سرویس در یک response واحد (Aggregation).
۴.۴ مقایسه ابزارهای رایج
| ابزار | زبان | سادگی | Performance | کاربرد |
|---|---|---|---|---|
| NGINX | C | متوسط | خیلی بالا | Reverse proxy، load balancing سبک |
| Traefik | Go | بالا | بالا | Container-native، Docker/Kubernetes |
| Kong | Lua/Go | متوسط | بالا | Enterprise، plugin-rich |
| Envoy | C++ | پایین | خیلی بالا | Service Mesh (Istio)، gRPC |
| HAProxy | C | متوسط | خیلی بالا | Load Balancer قدرتمند |
| Tyk | Go | بالا | بالا | API management کامل |
| FastAPI Custom | Python | عالی برای dev | متوسط | Custom logic، تیم Python |
۴.۵ NGINX به عنوان API Gateway
NGINX سبک، سریع و بسیار محبوب است. برای ترافیک بالا با feature های پایه گزینه عالی است.
پیکربندی پایه
# /etc/nginx/conf.d/gateway.conf
# Upstream definitions
upstream user_service {
least_conn;
server user-service-1:8001 weight=1;
server user-service-2:8001 weight=1;
server user-service-3:8001 weight=1;
}
upstream product_service {
server product-service-1:8002;
server product-service-2:8002;
}
upstream order_service {
server order-service:8003;
}
# Rate limiting zone
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m;
# Cache
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=1g inactive=60m;
server {
listen 80;
listen 443 ssl http2;
server_name api.shop.com;
# SSL
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Logging
access_log /var/log/nginx/api_access.log combined;
error_log /var/log/nginx/api_error.log warn;
# Common headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-ID $request_id;
# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Auth endpoints - rate limit شدید
location /api/auth/ {
limit_req zone=auth_limit burst=10 nodelay;
proxy_pass http://user_service/auth/;
}
# User endpoints
location /api/users/ {
limit_req zone=api_limit burst=20 nodelay;
# Auth check via subrequest
auth_request /auth-check;
auth_request_set $user_id $upstream_http_x_user_id;
proxy_set_header X-User-ID $user_id;
proxy_pass http://user_service/users/;
}
# Product endpoints with caching
location /api/products/ {
limit_req zone=api_limit burst=50 nodelay;
# Cache GET requests
proxy_cache api_cache;
proxy_cache_methods GET HEAD;
proxy_cache_valid 200 5m;
proxy_cache_key "$request_method$request_uri$http_authorization";
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://product_service/products/;
}
# Order endpoints (no cache, requires auth)
location /api/orders/ {
limit_req zone=api_limit burst=20 nodelay;
auth_request /auth-check;
auth_request_set $user_id $upstream_http_x_user_id;
proxy_set_header X-User-ID $user_id;
proxy_pass http://order_service/orders/;
}
# Internal auth check endpoint
location = /auth-check {
internal;
proxy_pass http://user_service/auth/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
# Health check
location /health {
access_log off;
return 200 "healthyn";
}
# Default 404
location / {
return 404 "Not Found";
}
}
اجرای NGINX با Docker
# docker-compose.yml
version: "3.8"
services:
api-gateway:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
- nginx_cache:/var/cache/nginx
depends_on:
- user-service
- product-service
- order-service
networks:
- microservices
volumes:
nginx_cache:
networks:
microservices:
external: true
۴.۶ Traefik — Container-Native Gateway
Traefik برای محیطهای container ایدهآل است. به طور خودکار سرویسها را از Docker یا Kubernetes کشف میکند و dashboard زیبایی دارد.
پیکربندی Static
# traefik.yml
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
docker:
exposedByDefault: false
network: microservices
certificatesResolvers:
letsencrypt:
acme:
email: admin@shop.com
storage: /acme/acme.json
httpChallenge:
entryPoint: web
log:
level: INFO
filePath: /var/log/traefik/traefik.log
accessLog:
filePath: /var/log/traefik/access.log
metrics:
prometheus:
addEntryPointsLabels: true
addServicesLabels: true
تنظیم سرویسها با Labels
# docker-compose.yml
version: "3.8"
services:
traefik:
image: traefik:v3.0
command:
- "--configFile=/etc/traefik/traefik.yml"
ports:
- "80:80"
- "443:443"
- "8080:8080" # dashboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./acme:/acme
networks:
- microservices
user-service:
image: shop/user-service:latest
deploy:
replicas: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.users.rule=Host(`api.shop.com`) && PathPrefix(`/api/users`)"
- "traefik.http.routers.users.entrypoints=websecure"
- "traefik.http.routers.users.tls.certresolver=letsencrypt"
- "traefik.http.services.users.loadbalancer.server.port=8001"
- "traefik.http.middlewares.users-stripprefix.stripprefix.prefixes=/api/users"
- "traefik.http.routers.users.middlewares=users-stripprefix,auth-jwt,rate-limit"
networks:
- microservices
product-service:
image: shop/product-service:latest
deploy:
replicas: 5
labels:
- "traefik.enable=true"
- "traefik.http.routers.products.rule=Host(`api.shop.com`) && PathPrefix(`/api/products`)"
- "traefik.http.routers.products.entrypoints=websecure"
- "traefik.http.services.products.loadbalancer.server.port=8002"
- "traefik.http.middlewares.products-stripprefix.stripprefix.prefixes=/api/products"
# Health check
- "traefik.http.services.products.loadbalancer.healthcheck.path=/health"
- "traefik.http.services.products.loadbalancer.healthcheck.interval=30s"
networks:
- microservices
# Middlewares
# Rate limiting (همه سرویسها)
# JWT authentication
Middleware ها
# dynamic.yml
http:
middlewares:
# Rate limiting
rate-limit:
rateLimit:
average: 100
burst: 200
period: 1m
# JWT Authentication
auth-jwt:
forwardAuth:
address: "http://auth-service:8001/verify"
authResponseHeaders:
- "X-User-ID"
- "X-User-Role"
# CORS
cors:
headers:
accessControlAllowOriginList:
- "https://shop.com"
- "https://app.shop.com"
accessControlAllowMethods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
accessControlAllowHeaders:
- "Content-Type"
- "Authorization"
accessControlMaxAge: 3600
# Compress
compress:
compress: {}
# Retry
retry:
retry:
attempts: 3
initialInterval: "100ms"
# Circuit Breaker
circuit-breaker:
circuitBreaker:
expression: "NetworkErrorRatio() > 0.5"
checkPeriod: "10s"
fallbackDuration: "30s"
recoveryDuration: "10s"
۴.۷ Kong Gateway
Kong یک API Gateway قدرتمند است که بر روی NGINX ساخته شده و plugin های فراوانی دارد.
اجرای Kong با Docker
# docker-compose.yml
version: "3.8"
services:
kong-database:
image: postgres:15
environment:
POSTGRES_DB: kong
POSTGRES_USER: kong
POSTGRES_PASSWORD: kongpass
volumes:
- kong-db:/var/lib/postgresql/data
networks:
- kong-net
kong-migrations:
image: kong:3.5
command: kong migrations bootstrap
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_USER: kong
KONG_PG_PASSWORD: kongpass
depends_on:
- kong-database
networks:
- kong-net
kong:
image: kong:3.5
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_USER: kong
KONG_PG_PASSWORD: kongpass
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: 0.0.0.0:8001
ports:
- "8000:8000" # Proxy
- "8443:8443" # Proxy SSL
- "8001:8001" # Admin API
- "8444:8444" # Admin SSL
depends_on:
- kong-database
- kong-migrations
networks:
- kong-net
- microservices
konga: # Web UI
image: pantsel/konga
ports:
- "1337:1337"
environment:
DB_ADAPTER: postgres
DB_HOST: kong-database
DB_USER: kong
DB_PASSWORD: kongpass
DB_DATABASE: konga
depends_on:
- kong-database
networks:
- kong-net
volumes:
kong-db:
networks:
kong-net:
microservices:
external: true
تعریف Service و Route
# تعریف upstream برای load balancing
curl -X POST http://localhost:8001/upstreams
-d "name=user-upstream"
# اضافه کردن target ها
curl -X POST http://localhost:8001/upstreams/user-upstream/targets
-d "target=user-service-1:8001"
-d "weight=100"
curl -X POST http://localhost:8001/upstreams/user-upstream/targets
-d "target=user-service-2:8001"
-d "weight=100"
# تعریف Service
curl -X POST http://localhost:8001/services
-d "name=user-service"
-d "host=user-upstream"
-d "port=8001"
-d "path=/"
# تعریف Route
curl -X POST http://localhost:8001/services/user-service/routes
-d "name=user-route"
-d "paths[]=/api/users"
-d "strip_path=true"
فعالسازی Plugin ها
# Rate limiting
curl -X POST http://localhost:8001/services/user-service/plugins
-d "name=rate-limiting"
-d "config.minute=100"
-d "config.hour=10000"
-d "config.policy=redis"
-d "config.redis_host=redis"
# JWT Authentication
curl -X POST http://localhost:8001/services/user-service/plugins
-d "name=jwt"
# CORS
curl -X POST http://localhost:8001/services/user-service/plugins
-d "name=cors"
-d "config.origins=https://shop.com"
-d "config.methods=GET,POST,PUT,DELETE"
-d "config.credentials=true"
# Logging به Datadog
curl -X POST http://localhost:8001/plugins
-d "name=datadog"
-d "config.host=datadog-agent"
-d "config.port=8125"
# Request transformer
curl -X POST http://localhost:8001/services/user-service/plugins
-d "name=request-transformer"
-d "config.add.headers=X-API-Version:v1"
# Circuit Breaker
curl -X POST http://localhost:8001/services/user-service/plugins
-d "name=proxy-cache"
-d "config.strategy=memory"
-d "config.cache_ttl=300"
plugin های محبوب Kong
- rate-limiting: محدودیت تعداد درخواست
- jwt: احراز هویت با JWT
- oauth2: OAuth 2.0
- key-auth: API Key
- cors: CORS headers
- request-transformer: تغییر header/body درخواست
- response-transformer: تغییر header/body پاسخ
- ip-restriction: whitelist/blacklist IP
- bot-detection: شناسایی bot
- proxy-cache: caching پاسخ
- prometheus: metrics
- zipkin: distributed tracing
۴.۸ پیادهسازی Custom Gateway با FastAPI
گاهی نیاز به منطق سفارشی دارید که با ابزارهای آماده ممکن نیست. در این صورت میتوانید یک Gateway سفارشی بسازید.
# gateway/main.py
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.responses import Response
from fastapi.middleware.cors import CORSMiddleware
import httpx
import jwt
import time
from typing import Dict, Optional
import redis.asyncio as aioredis
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="API Gateway", version="1.0.0")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["https://shop.com", "https://app.shop.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Service registry
SERVICES: Dict[str, str] = {
"users": "http://user-service:8001",
"products": "http://product-service:8002",
"orders": "http://order-service:8003",
"payments": "http://payment-service:8004",
}
# Redis برای rate limiting
redis_client: Optional[aioredis.Redis] = None
@app.on_event("startup")
async def startup():
global redis_client
redis_client = await aioredis.from_url("redis://redis:6379")
@app.on_event("shutdown")
async def shutdown():
if redis_client:
await redis_client.close()
# JWT Configuration
JWT_SECRET = "your-secret-key"
JWT_ALGORITHM = "HS256"
class RateLimiter:
"""Rate limiter با Redis (Token Bucket)"""
def __init__(self, redis: aioredis.Redis, max_requests: int, window: int):
self.redis = redis
self.max_requests = max_requests
self.window = window
async def is_allowed(self, key: str) -> bool:
current = await self.redis.incr(key)
if current == 1:
await self.redis.expire(key, self.window)
return current <= self.max_requests
# Authentication
async def verify_jwt(request: Request) -> Optional[dict]:
"""احراز هویت JWT"""
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return None
token = auth_header.split(" ")[1]
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
# Public paths (نیاز به auth ندارند)
PUBLIC_PATHS = [
"/api/users/auth/login",
"/api/users/auth/register",
"/api/products", # GET only
"/health",
]
def is_public(path: str, method: str) -> bool:
if path in PUBLIC_PATHS:
return True
if method == "GET" and path.startswith("/api/products"):
return True
return False
# Main routing
@app.api_route("/api/{service}/{path:path}",
methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def gateway(service: str, path: str, request: Request):
"""Main gateway routing"""
# 1. Service exists?
if service not in SERVICES:
raise HTTPException(status_code=404, detail=f"Service {service} not found")
full_path = f"/api/{service}/{path}"
# 2. Authentication
user_data = None
if not is_public(full_path, request.method):
user_data = await verify_jwt(request)
if not user_data:
raise HTTPException(status_code=401, detail="Authentication required")
# 3. Rate Limiting
client_id = user_data["user_id"] if user_data else request.client.host
rate_limiter = RateLimiter(redis_client, max_requests=100, window=60)
rate_key = f"rate_limit:{client_id}:{service}"
if not await rate_limiter.is_allowed(rate_key):
raise HTTPException(
status_code=429,
detail="Rate limit exceeded",
headers={"Retry-After": "60"}
)
# 4. Forward to service
target_url = f"{SERVICES[service]}/{path}"
# Headers
headers = dict(request.headers)
headers.pop("host", None)
if user_data:
headers["X-User-ID"] = str(user_data["user_id"])
headers["X-User-Role"] = user_data.get("role", "user")
# Request ID for tracing
request_id = headers.get("X-Request-ID", f"gw-{int(time.time() * 1000)}")
headers["X-Request-ID"] = request_id
# Get body
body = await request.body()
# Forward
start = time.time()
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.request(
method=request.method,
url=target_url,
headers=headers,
content=body,
params=dict(request.query_params),
)
# Log
duration = (time.time() - start) * 1000
logger.info(
f"[{request_id}] {request.method} {full_path} -> "
f"{response.status_code} ({duration:.0f}ms)"
)
# Return
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers),
media_type=response.headers.get("content-type")
)
except httpx.TimeoutException:
logger.error(f"[{request_id}] Timeout calling {target_url}")
raise HTTPException(status_code=504, detail="Gateway timeout")
except httpx.ConnectError:
logger.error(f"[{request_id}] Cannot connect to {target_url}")
raise HTTPException(status_code=502, detail="Service unavailable")
# Health check
@app.get("/health")
async def health():
return {"status": "healthy", "services": list(SERVICES.keys())}
# Aggregation endpoint مثال
@app.get("/api/dashboard")
async def dashboard(user: dict = Depends(verify_jwt)):
"""ترکیب داده از چند سرویس"""
user_id = user["user_id"]
async with httpx.AsyncClient(timeout=10.0) as client:
# درخواستهای موازی
import asyncio
user_resp, orders_resp, recent_products = await asyncio.gather(
client.get(f"{SERVICES['users']}/users/{user_id}"),
client.get(f"{SERVICES['orders']}/orders?user_id={user_id}&limit=5"),
client.get(f"{SERVICES['products']}/products?recent=true&limit=10"),
return_exceptions=True
)
return {
"user": user_resp.json() if not isinstance(user_resp, Exception) else None,
"recent_orders": orders_resp.json() if not isinstance(orders_resp, Exception) else [],
"recent_products": recent_products.json() if not isinstance(recent_products, Exception) else []
}
۴.۹ استراتژیهای Rate Limiting
۱. Fixed Window
تعداد درخواست در یک پنجره ثابت زمانی (مثلاً ۱۰۰ درخواست در دقیقه).
async def fixed_window(redis, key: str, limit: int, window: int):
current = await redis.incr(key)
if current == 1:
await redis.expire(key, window)
return current <= limit
عیب: در مرز پنجره میتواند ۲ برابر مجاز اجازه دهد.
۲. Sliding Window
پنجره متحرک — دقیقتر است.
async def sliding_window(redis, key: str, limit: int, window: int):
now = time.time()
pipeline = redis.pipeline()
pipeline.zremrangebyscore(key, 0, now - window)
pipeline.zadd(key, {str(now): now})
pipeline.zcount(key, now - window, now)
pipeline.expire(key, window)
results = await pipeline.execute()
return results[2] <= limit
۳. Token Bucket
هر کاربر یک «سطل» با تعداد token مشخص دارد. هر درخواست یک token مصرف میکند. token ها با نرخ ثابت refill میشوند.
مزایا: اجازه burst میدهد.
۴. Leaky Bucket
درخواستها در صف وارد میشوند و با نرخ ثابت پردازش میشوند.
تنظیم محدودیتهای متفاوت
RATE_LIMITS = {
"anonymous": {"requests": 10, "window": 60}, # 10/min
"user": {"requests": 100, "window": 60}, # 100/min
"premium": {"requests": 1000, "window": 60}, # 1000/min
"admin": {"requests": 10000, "window": 60}, # 10000/min
}
def get_rate_limit(user_data: Optional[dict]) -> tuple:
if not user_data:
plan = "anonymous"
elif user_data.get("role") == "admin":
plan = "admin"
elif user_data.get("subscription") == "premium":
plan = "premium"
else:
plan = "user"
config = RATE_LIMITS[plan]
return config["requests"], config["window"]
۴.۱۰ بهترین تجربیات
- Stateless نگه دارید. Gateway نباید state کاربر را نگه دارد.
- Health Check برای هر سرویس. سرویسهای ناسالم را از rotation خارج کنید.
- Timeout مناسب. هیچگاه بدون timeout request نزنید (پیشنهاد: 30 ثانیه).
- Retry با احتیاط. فقط برای 5xx و idempotent operations.
- Circuit Breaker. از cascading failure جلوگیری میکند.
- Logging کامل. request ID، latency، status code، user ID.
- Distributed Tracing. از روز اول OpenTelemetry را اضافه کنید.
- Caching هوشمند. فقط GET requests، با key مناسب.
- API Versioning. URL یا header. استاندارد بمانید.
- Documentation خودکار. Swagger UI برای کل API.
- Security Headers. CSP، X-Frame-Options، X-Content-Type-Options.
- Compression. gzip یا brotli برای کاهش bandwidth.
- HTTPS اجباری. در production هیچگاه HTTP نه!
- API Key مدیریت کنید. برای third-party clients.
- Monitoring فعال. Prometheus metrics، Grafana dashboards.
۴.۱۱ خلاصه فصل
آنچه آموختیم:
- API Gateway نقطه ورود مرکزی همه درخواستها به میکروسرویسهاست
- مسئولیتهای اصلی: Routing، Auth، Rate Limiting، Load Balancing، Caching، Logging
- NGINX برای ترافیک بالا با feature های پایه
- Traefik برای container-native و Kubernetes
- Kong برای enterprise با plugin های فراوان
- FastAPI Custom برای منطق سفارشی
- استراتژیهای Rate Limiting: Fixed Window، Sliding Window، Token Bucket، Leaky Bucket
- Authentication معمولاً در Gateway انجام میشود تا سرویسها از آن آزاد باشند