An API Gateway built in Go using Gin, ported from the .NET YARP-based gateway.
In fact, it's not the next generation gateway, it's the prev generation. Because Solar Network is built by pure Go at the v2, and migrated to .NET at v3, now we planned to move some core services to Go again.
- Reverse Proxy Routing - Routes requests to backend microservices
- Health Monitoring - Background health checks every 10 seconds
- Service Discovery - Redis-backed leased instance registry over gRPC
- Readiness Gating - Returns 503 if core services are unhealthy
- CORS Support - Allows all origins with custom headers
- Special Routes - Fully configurable route system via
routes - Route Transforms - Strips service prefix, adds
/apiprefix - Maintenance Mode - Returns 503 globally or per configured services
Edit configs/config.toml:
siteUrl = "https://solian.app"
[cache]
serializer = "JSON"
[services]
ring = { http = "http://ring:5000", grpc = "ring:5001" }
pass = { http = "http://pass:5000", grpc = "pass:5001" }
drive = { http = "http://drive:5000", grpc = "drive:5001" }
sphere = { http = "http://sphere:5000", grpc = "sphere:5001" }
develop = { http = "http://develop:5000", grpc = "develop:5001" }
insight = { http = "http://insight:5000", grpc = "insight:5001" }
zone = { http = "http://zone:5000", grpc = "zone:5001" }
messager = { http = "http://messager:5000", grpc = "messager:5001" }
wallet = { http = "http://wallet:5000", grpc = "wallet:5001" }
ideask = { http = "http://ideask:5000", grpc = "ideask:5001" }
[endpoints]
serviceNames = ["ring", "pass", "drive", "sphere", "develop", "insight", "zone", "messager", "wallet", "ideask"]
coreServiceNames = ["ring", "pass", "drive", "sphere"]
[rateLimit]
requestsPerMinute = 120
burstAllowance = 10
[health]
checkIntervalSeconds = 10
[discovery]
enabled = true
prefix = "blade:discovery"
leaseSeconds = 30
leaderLeaseSeconds = 15
registrationToken = "replace-with-a-service-secret"
[server]
port = "6000"
readTimeout = 60
writeTimeout = 60
[websocket]
enabled = true
path = "/ws"
keepAliveSeconds = 60
maxMessageBytes = 4096
allowedDeviceAlternatives = ["watch"]
[maintenance]
enabled = false
mode = "full" # "full" or "service"
services = [] # used when mode = "service"[maintenance]
enabled = true
mode = "full" # blocks all proxied requests with 503[maintenance]
enabled = true
mode = "service" # blocks only listed services
services = ["sphere", "drive"]Legacy key maintaince is also supported for backward compatibility.
| Variable | Description | Default |
|---|---|---|
CONFIG_PATH |
Path to config file | configs/config.toml |
GIN_MODE |
debug or release |
debug |
ZEROLOG_PRETTY |
Enable pretty logging | false |
When discovery.enabled is set, Blade requires cache.redisUrl and a non-empty
discovery.registrationToken. Services register an HTTP/gRPC endpoint through
DyServiceDiscoveryService/Register, then renew the lease before it expires.
Only the Redis-elected Blade replica probes registered HTTP endpoints; all
replicas read the same healthy instance set for proxy routing. Send the service
secret as authorization: Bearer <registrationToken> for registration, renewal,
and deregistration calls.
Configured [services] targets remain a fallback until that service has a
registered instance, allowing incremental migration.
The gateway supports fully configurable special routes:
[[routes]]
path = "/.well-known/openid-configuration"
service = "pass"
target = "/auth/.well-known/openid-configuration"
prefix = false
[[routes]]
path = "/activitypub"
service = "sphere"
target = "/activitypub"
prefix = true # true for wildcard matching| Field | Description |
|---|---|
path |
Source path to match (e.g., /ws, /.well-known/openid-configuration) |
service |
Target service name |
target |
Path on the backend service |
prefix |
If true, match path as prefix (e.g., /activitypub/**) |
# Build
go build -o gateway ./cmd/main.go
# Run
./gateway
# Or with custom config
CONFIG_PATH=./configs/config.toml ./gateway# Build
docker build -t dyson-gateway .
# Run
docker run -p 6000:6000 dyson-gateway
# Run with custom config
docker run -p 6000:6000 -v ./config.toml:/app/configs/config.toml dyson-gateway| Endpoint | Description |
|---|---|
GET /health |
Gateway health status |
/<service>/** |
Proxied to backend service (e.g., /ring/** → ring:5000/api/**) |
/ws |
Native WebSocket gateway (configurable via websocket.path) |
/.well-known/* |
.well-known endpoints (configurable via routes) |
/activitypub/** |
ActivityPub (configurable via routes) |
/swagger/<service>/** |
Swagger docs → service |
gRPC DyServiceDiscoveryService |
Register, renew, remove, and resolve service instances |
Current implementation follows DysonTokenAuthHandler behavior:
- Token extraction order:
tkquery,Authorizationheader (Bearer,AtField,AkField),AuthTokencookie - Token validation: remote gRPC call to
DyAuthService/Authenticateusing the configuredwebsocket.authServicetarget - Request IP is forwarded to auth service as
ip_address
Client Request
↓
Rate Limiter (120 req/min/IP)
↓
Readiness Middleware (503 if core services unhealthy)
↓
Reverse Proxy (routes based on path)
↓
Backend Service