βββββββ βββ βββββββ βββββββββ βββββββ ββββββ ββββββββββββββββββββ βββ ββββββ βββ βββ
βββββββββββββββββββββββββββββ ββββββββ ββββββββββββββββββββββββββββ βββββββββββββββ ββββ
ββββββββββββββ βββ βββ βββ ββββββββββββ βββ ββββββ βββ ββ βββββββββββ βββββββ
ββββββββββββββ βββ βββ βββ βββββββββββ βββ ββββββ ββββββββββββββββββ βββββ
βββ βββββββββββββββ βββ ββββββββββββ βββ βββ βββββββββββββββββββββ βββ βββ
βββ ββββββ βββββββ βββ βββββββ βββ βββ βββ ββββββββ ββββββββ βββ βββ βββ
Riot API Gateway β ProStaff Ecosystem
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROSTAFF RIOT GATEWAY β Go 1.23 β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Centralized Riot Games API gateway for the ProStaff ecosystem. β
β Token bucket rate limiting Β· Two-tier cache Β· Circuit breaker β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βΆ Features (click to expand)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β [β ] Global App Rate Limiting β Single token bucket for the API key β
β [β ] Two-tier Cache β L1 LRU in-process + L2 Redis β
β [β ] Negative Cache β 404s cached in L1 (short TTL per resource) β
β [β ] Circuit Breaker β Per (region, endpoint): match β summoner β
β [β ] Retry with Backoff β 5xx retried 3Γ (0/100ms/500ms) before open β
β [β ] Internal JWT Auth β aud-validated; user tokens rejected β
β [β ] Regional Routing β Auto-resolves Match-V5 routing region β
β [β ] Graceful Degradation β Redis down? L1 cache keeps serving β
β [β ] Request ID Propagation β X-Request-ID for cross-service log correl. β
β [β ] Build Info in /health β version, commit, built_at via ldflags β
β [β ] Structured JSON Logging β slog JSON, compatible with log aggregators β
β [β ] Graceful Shutdown β 5s drain on SIGTERM β
β [β ] Docker Ready β Multi-stage build, image < 20MB β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 01 Β· Quick Start β
β 02 Β· Technology Stack β
β 03 Β· Architecture β
β 04 Β· API Endpoints β
β 05 Β· Configuration β
β 06 Β· Cache & Rate Limiting β
β 07 Β· Development β
β 08 Β· Integration with ProStaff β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Copy env and fill in values
cp .env.example .env
# Start gateway + Redis
docker compose up -d
# Check health
curl http://localhost:4444/healthResponse:
{
"status": "ok",
"redis": "ok",
"circuit_breakers": {}
}ββββββββββββββββββββββββ¦βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LAYER β TECHNOLOGY β
β βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Language β Go 1.23 β
β HTTP Router β Gorilla Mux v1.8 β
β Authentication β JWT HS256 (golang-jwt/jwt v5) β
β Rate Limiting β golang.org/x/time/rate (token bucket) β
β Cache L1 β hashicorp/golang-lru v2 (LRU + TTL + GC) β
β Cache L2 β Redis 7 (go-redis/v9) β
β Circuit Breaker β Custom 3-state state machine β
β Config β godotenv + os.Getenv β
β Logging β log/slog (JSON handler, stdlib Go 1.21+) β
β Container β Docker multi-stage (Alpine, image < 20MB) β
ββββββββββββββββββββββββ©βββββββββββββββββββββββββββββββββββββββββββββββββββββ
prostaff-front (vinext) ββββββββββββββββββββββββββββ
prostaff-mobile(vue.js\quasar+capacitor) βββββββββββ€
ArenaBR (NextJS) ββββββββββββββββββββββββββββββββββ€
Scrims (NextJS) ββββββββββββββββββββββββββββββββββ€
ββββββββββββββββββ
βΌ
prostaff-api (Rails) βββββββββββββββββββββββββββββββ
ProStaff-Scraper (Python) ββββββββββββββββββββββββββ€
ββββββββββββ
βΌ
[prostaff-riot-gateway :4444]
ββββββββββββββββββββββββ
β JWT InternalAuth β aud-validated service identity
β AppLimiter (global) β single token bucket per API key
β RegionBreakers β circuit breaker per (region,endpoint)
β MemoryCache (L1) β LRU, max 10k entries, negative cache
β RedisCache (L2) β shared across instances
ββββββββββββββββββββββββ
β
βΌ
Riot Games API
(br1, na1, euw1, kr, ...)
1. Validate JWT β 401 if missing, invalid, or wrong aud
2. Validate region β 400 if not in allowed list
3. Check L1 negative cache β 404 if resource was confirmed absent recently
4. Check L1 cache β return in < 2ms if hit
5. Check L2 Redis β populate L1, return if hit
6. Check circuit breaker β 503 if (region, endpoint) circuit open
7. Acquire rate limiter β blocks until token available (global)
8. Call Riot API β 5s timeout, retry 5xx up to 3Γ with backoff
9. On success β populate L1 + L2, return 200
10. On 404 β cache negative in L1, return 404
11. On persistent failure β trip circuit breaker, return 502
Riot Match-V5 uses regional routing instead of server routing. The gateway resolves automatically:
br1, na1, la1, la2 β americas
euw1, eun1, tr1, ru β europe
kr, jp1 β asia
oc1 β sea
Base URL: http://riot-gateway:4444
Auth header: Authorization: Bearer <internal-jwt> (all /riot/* endpoints)
# Public
GET /health
# Summoner / Account
GET /riot/summoner/{region}/by-puuid/{puuid}
GET /riot/summoner/{region}/by-riot-id/{gameName}/{tagLine} β preferred
GET /riot/summoner/{region}/by-name/{name} β 410 Gone (Riot deprecated 2024)
GET /riot/account/{region}/{riotId}/{tagline}
GET /riot/account/{region}/by-puuid/{puuid}
# Ranked
GET /riot/league/{region}/by-summoner/{summonerId}
GET /riot/league/{region}/by-puuid/{puuid}
# Matches (auto-resolves routing region)
GET /riot/matches/{region}/{puuid}/ids?count=20&queue=420&start=0
GET /riot/match/{region}/{matchId}
# Champion Mastery
GET /riot/mastery/{region}/{puuid}/top?count=10
{
"status": "ok",
"version": "v1.0.0",
"commit": "abc1234",
"built_at": "2026-04-20T01:00:00Z",
"redis": "ok",
"circuit_breakers": {
"br1:summoner": "closed",
"americas:match": "open"
}
}All configuration via environment variables (.env):
# Gateway
PORT=4444
# Must be different from prostaff-api user JWT secret β generate with: openssl rand -hex 32
# Tokens must include aud: "prostaff-riot-gateway"
INTERNAL_JWT_SECRET=<dedicated-gateway-secret>
# Riot API
RIOT_API_KEY=RGAPI-...
RIOT_API_TIMEOUT=5s
# Rate Limiting (Riot dev key: 20/s, 100/2min β single global bucket)
RIOT_RATE_LIMIT_PER_SECOND=20
RIOT_RATE_LIMIT_BURST=20
RIOT_RATE_LIMIT_PER_2MIN=100
# Cache L1 (in-process LRU)
CACHE_L1_MAX_SIZE=10000 # max entries before LRU eviction
# Cache L2 (Redis)
REDIS_URL=redis://redis:6379/1 # db 1, separate from prostaff-api db 0
CACHE_ENABLED=true
# Circuit Breaker
CIRCUIT_BREAKER_THRESHOLD=5 # consecutive failures to open circuit
CIRCUIT_BREAKER_TIMEOUT=60 # failure counting window (seconds)
CIRCUIT_BREAKER_COOLDOWN=30 # seconds before half-open probe
# Logging
LOG_LEVEL=info # debug | info | warn | errorβββββββββββββββββββββββββ¦βββββββββββ¦βββββββββββ¦βββββββββββββββ
β Resource β L1 TTL β L2 TTL β 404 (L1) β
β ββββββββββββββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββββββ£
β summoner by riot-id β 10 min β 10 min β 30s β
β summoner by PUUID β 10 min β 10 min β 2 min β
β account (riot ID) β 1 h β 1 h β 2 min β
β league entries β 5 min β 5 min β 30s β
β match IDs list β 5 min β 5 min β 30s β
β match detail β 1 h β 24 h β 5 min β
β champion mastery β 30 min β 1 h β 30s β
βββββββββββββββββββββββββ©βββββββββββ©βββββββββββ©βββββββββββββββ
Match detail has a 24h L2 TTL because match data is immutable once the game ends. 404s are cached only in L1 β they're short-lived and should not persist across instances.
# Build (Go not required locally, uses Docker)
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine go build ./cmd/server/
# Lint (staticcheck)
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine sh -c \
"go install honnef.co/go/tools/cmd/staticcheck@latest 2>/dev/null && staticcheck ./..."
# go vet
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine go vet ./...
# Run tests
docker run --rm -v $(pwd):/app -w /app golang:1.23-alpine go test ./...
# Start with docker compose
docker compose up -d
# View logs
docker compose logs -f gateway
# Check health
curl http://localhost:4444/healthprostaff-riot-gateway/
βββ cmd/server/main.go β entry point, router wiring, graceful shutdown
βββ internal/
β βββ auth/
β β βββ jwt.go β ServiceClaims, ValidateServiceToken
β β βββ middleware.go β InternalAuth JWT middleware
β βββ cache/
β β βββ memory.go β L1: hashicorp/golang-lru + TTL + negative cache
β β βββ redis.go β L2: go-redis/v9, graceful fallback
β β βββ ttl.go β TTL + negative TTL constants per resource type
β βββ circuit/
β β βββ breaker.go β 3-state machine, RegionBreakers per (region,endpoint)
β βββ config/
β β βββ config.go β typed config, godotenv + os.Getenv
β βββ handlers/
β β βββ base.go β shared fetch pipeline (neg cacheβL1βL2βriot)
β β βββ health.go β GET /health with version + circuit breaker states
β β βββ summoner.go β summoner (by-riot-id, by-puuid) + account endpoints
β β βββ league.go β ranked/league endpoints
β β βββ matches.go β Match-V5 IDs + detail
β β βββ mastery.go β champion mastery
β βββ middleware/
β β βββ requestid.go β X-Request-ID propagation (UUID v4 fallback)
β βββ ratelimit/
β β βββ limiter.go β global AppLimiter (1s + 2min), region validation
β βββ riot/
β β βββ client.go β HTTP client, retry backoff, circuit integration
β βββ webutils/
β βββ json_helpers.go β WriteJSON, RawJSON, ErrorJSON
βββ Dockerfile β multi-stage, Alpine final image
βββ docker-compose.yml β gateway + Redis
βββ .env.example
βββ go.mod
# docker-compose.yml (prostaff-api)
riot-gateway:
image: prostaff-riot-gateway:latest
build:
context: ../prostaff-riot-gateway
dockerfile: Dockerfile
ports:
- "4444:4444"
environment:
- INTERNAL_JWT_SECRET=${RIOT_GATEWAY_JWT_SECRET} # dedicated secret, not the user JWT
- RIOT_API_KEY=${RIOT_API_KEY}
- REDIS_URL=redis://redis:6379/1
- PORT=4444
depends_on:
redis:
condition: service_healthy
networks:
- prostaff_network
restart: unless-stopped# Before
BASE_URL = "https://#{region}.api.riotgames.com"
headers = { "X-Riot-Token" => ENV["RIOT_API_KEY"] }
# After
GATEWAY_URL = ENV.fetch("RIOT_GATEWAY_URL", "http://riot-gateway:4444")
headers = { "Authorization" => "Bearer #{internal_jwt}" }
def internal_jwt
payload = {
iss: "prostaff-api",
aud: "prostaff-riot-gateway",
sub: "service-account",
service: "prostaff-api",
exp: 1.hour.from_now.to_i
}
JWT.encode(payload, ENV.fetch("RIOT_GATEWAY_JWT_SECRET"), "HS256")
endGATEWAY_URL = os.getenv("RIOT_GATEWAY_URL", "http://riot-gateway:4444")
headers = {"Authorization": f"Bearer {generate_internal_jwt()}"}
response = requests.get(
f"{GATEWAY_URL}/riot/match/americas/{match_id}",
headers=headers,
timeout=10
)
# generate_internal_jwt must include aud: "prostaff-riot-gateway"Last Updated: 2026-04-20 Go Version: 1.23 Cache Strategy: L1 LRU (hashicorp/golang-lru) + L2 Redis + negative cache Rate Limit: Global token bucket per API key (20 req/s / 100 req/2min, configurable)