add real-time infrastructure (#137)
* setup infra for pusher service * setup client sdk for pusher service * fix: ping parse failure * fix: send pong back to client avoid disconnections every 2.5 minutes * increase replicas * feat: show presence and compose indicator
This commit was merged in pull request #137.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/flowy-live/llink/internal"
|
||||
"github.com/flowy-live/llink/internal/auth"
|
||||
"github.com/flowy-live/llink/internal/db"
|
||||
"github.com/flowy-live/llink/internal/network"
|
||||
"github.com/flowy-live/llink/internal/pusher"
|
||||
"github.com/flowy-live/llink/internal/utils"
|
||||
|
||||
pbpusher "github.com/flowy-live/llink/genproto/llink/pusher"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
redisDBAuth = 4 // shared with orion for session validation
|
||||
redisDBPusher = 5 // dedicated to pusher state (presence, pub/sub)
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := utils.MustGetEnv("PORT")
|
||||
grpcPort := utils.MustGetEnv("GRPC_PORT")
|
||||
|
||||
// Initialize database (for network membership checks)
|
||||
db.Init()
|
||||
defer db.Cleanup()
|
||||
|
||||
// Redis for auth session validation (same DB as orion)
|
||||
authRedis := internal.ConnectAndTestRedis(redisDBAuth)
|
||||
|
||||
// Redis for pusher state (presence hashes, pub/sub)
|
||||
pusherRedis := internal.ConnectAndTestRedis(redisDBPusher)
|
||||
|
||||
// Services
|
||||
authSvc := auth.NewAuthService(authRedis, nil) // nil aeroSvc — pusher only calls GetSession
|
||||
networkSvc := network.NewService(db.Pool())
|
||||
|
||||
// Pod identity (use hostname in k8s, which is the pod name)
|
||||
podID, err := os.Hostname()
|
||||
if err != nil {
|
||||
podID = fmt.Sprintf("pod-%d", os.Getpid())
|
||||
}
|
||||
|
||||
// Pusher core
|
||||
bridge := pusher.NewRedisBridge(pusherRedis, podID)
|
||||
authorizer := pusher.NewAuthorizer(networkSvc)
|
||||
hub := pusher.NewHub(bridge, authorizer)
|
||||
bridge.SetHub(hub)
|
||||
server := pusher.NewServer(hub, bridge, authSvc)
|
||||
|
||||
// Context for graceful shutdown
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Start hub event loop
|
||||
go hub.Run(ctx)
|
||||
|
||||
// Start Redis Pub/Sub listener
|
||||
go bridge.Listen(ctx)
|
||||
|
||||
// Start pod heartbeat + stale pod cleanup
|
||||
go bridge.Heartbeat(ctx)
|
||||
|
||||
// --- gRPC server (internal, for presence queries) ---
|
||||
grpcListener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", grpcPort))
|
||||
if err != nil {
|
||||
slog.Error("failed to listen for gRPC", "port", grpcPort, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
grpcServer := grpc.NewServer()
|
||||
pbpusher.RegisterPusherServiceServer(grpcServer, server)
|
||||
go func() {
|
||||
slog.Info("gRPC server listening", "port", grpcPort)
|
||||
if err := grpcServer.Serve(grpcListener); err != nil {
|
||||
slog.Error("gRPC server failed", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// --- HTTP server (WebSocket + health) ---
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
mux.HandleFunc("GET /ws", server.HandleWebSocket)
|
||||
|
||||
httpAddr := fmt.Sprintf("0.0.0.0:%s", port)
|
||||
httpServer := &http.Server{Addr: httpAddr, Handler: mux}
|
||||
|
||||
go func() {
|
||||
slog.Info("HTTP server listening", "addr", httpAddr)
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("HTTP server failed", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// --- Graceful shutdown ---
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
|
||||
<-sigCh
|
||||
|
||||
slog.Info("shutting down...")
|
||||
cancel() // stops hub, bridge listener, heartbeat
|
||||
|
||||
grpcServer.GracefulStop()
|
||||
httpServer.Shutdown(context.Background())
|
||||
slog.Info("shutdown complete")
|
||||
}
|
||||
Reference in New Issue
Block a user