146 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"gralias/config"
 | |
| 	"gralias/crons"
 | |
| 	"gralias/handlers"
 | |
| 	"gralias/repos"
 | |
| 	"gralias/telemetry"
 | |
| 	"log/slog"
 | |
| 	"net/http"
 | |
| 	_ "net/http/pprof"
 | |
| 	"os"
 | |
| 	"os/signal"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| var cfg *config.Config
 | |
| 
 | |
| func init() {
 | |
| 	cfg = config.LoadConfigOrDefault("")
 | |
| }
 | |
| 
 | |
| // GzipFileServer serves pre-compressed .gz files if available
 | |
| func GzipFileServer(root http.FileSystem) http.Handler {
 | |
| 	fs := http.FileServer(root)
 | |
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		// Check if client accepts gzip
 | |
| 		if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
 | |
| 			// Check for .gz version of the file
 | |
| 			gzPath := r.URL.Path + ".gz"
 | |
| 			if file, err := root.Open(gzPath); err == nil {
 | |
| 				file.Close()
 | |
| 				// Set headers for gzip
 | |
| 				w.Header().Set("Content-Encoding", "gzip")
 | |
| 				w.Header().Set("Content-Type", getContentType(r.URL.Path))
 | |
| 				r.URL.Path = gzPath
 | |
| 			}
 | |
| 		}
 | |
| 		fs.ServeHTTP(w, r)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Helper to set correct Content-Type
 | |
| func getContentType(path string) string {
 | |
| 	switch filepath.Ext(path) {
 | |
| 	case ".css":
 | |
| 		return "text/css"
 | |
| 	case ".js":
 | |
| 		return "application/javascript"
 | |
| 	default:
 | |
| 		return "" // http.FileServer will detect it
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func ListenToRequests(port string) *http.Server {
 | |
| 	mux := http.NewServeMux()
 | |
| 	var handler http.Handler = mux
 | |
| 	handler = handlers.LogRequests(handlers.GetSession(handler))
 | |
| 	handler = telemetry.OtelMiddleware(handler)
 | |
| 	server := &http.Server{
 | |
| 		Handler: handler,
 | |
| 		Addr:    ":" + port,
 | |
| 		// ReadTimeout:  time.Second * 5, // does this timeout conflict with sse connection?
 | |
| 		WriteTimeout: 0, // sse streaming
 | |
| 	}
 | |
| 	// fs := http.FileServer(http.Dir("assets/"))
 | |
| 	fs := http.Dir("assets/")
 | |
| 	mux.Handle("GET /assets/", http.StripPrefix("/assets/", GzipFileServer(fs)))
 | |
| 	//
 | |
| 	mux.HandleFunc("GET /ping", handlers.HandlePing)
 | |
| 	mux.HandleFunc("GET /", handlers.HandleHome)
 | |
| 	mux.HandleFunc("GET /stats", handlers.HandleStats)
 | |
| 	mux.HandleFunc("POST /login", handlers.HandleFrontLogin)
 | |
| 	mux.HandleFunc("GET /signout", handlers.HandleSignout)
 | |
| 	mux.HandleFunc("POST /join-team", handlers.HandleJoinTeam)
 | |
| 	mux.HandleFunc("GET /end-turn", handlers.HandleEndTurn)
 | |
| 	mux.HandleFunc("POST /room-create", handlers.HandleCreateRoom)
 | |
| 	mux.HandleFunc("GET /start-game", handlers.HandleStartGame)
 | |
| 	mux.HandleFunc("GET /room-join", handlers.HandleJoinRoom)
 | |
| 	mux.HandleFunc("POST /give-clue", handlers.HandleGiveClue)
 | |
| 	mux.HandleFunc("GET /room/exit", handlers.HandleExit)
 | |
| 	//elements
 | |
| 	mux.HandleFunc("GET /actionhistory", handlers.HandleActionHistory)
 | |
| 	mux.HandleFunc("GET /room/createform", handlers.HandleShowCreateForm)
 | |
| 	mux.HandleFunc("GET /room/hideform", handlers.HandleHideCreateForm)
 | |
| 	mux.HandleFunc("GET /word/show-color", handlers.HandleShowColor)
 | |
| 	mux.HandleFunc("POST /check/name", handlers.HandleNameCheck)
 | |
| 	mux.HandleFunc("GET /add-bot", handlers.HandleAddBot)
 | |
| 	mux.HandleFunc("GET /remove-bot", handlers.HandleRemoveBot)
 | |
| 	mux.HandleFunc("GET /mark-card", handlers.HandleMarkCard)
 | |
| 	mux.HandleFunc("GET /room", handlers.HandleGetRoom)
 | |
| 	// special
 | |
| 	mux.HandleFunc("GET /renotify-bot", handlers.HandleRenotifyBot)
 | |
| 	// sse
 | |
| 	mux.Handle("GET /sub/sse", handlers.Notifier)
 | |
| 	slog.Info("Listening", "addr", port)
 | |
| 	return server
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	shutdown := telemetry.InitTracer()
 | |
| 	defer shutdown()
 | |
| 	// Setup graceful shutdown
 | |
| 	stop := make(chan os.Signal, 1)
 | |
| 	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
 | |
| 	// repo := repos.NewRepoProvider(cfg.DBPath)
 | |
| 	repo := repos.RP
 | |
| 	defer repo.Close()
 | |
| 	cm := crons.NewCronManager(repo, slog.Default())
 | |
| 	cm.Start()
 | |
| 	server := ListenToRequests(cfg.ServerConfig.Port)
 | |
| 	pprofPort := "6060"
 | |
| 	pprofServer := &http.Server{
 | |
| 		Addr: ":" + pprofPort,
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		slog.Info("Pprof server listening", "addr", pprofPort)
 | |
| 		if err := pprofServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
 | |
| 			slog.Error("Pprof server failed", "error", err)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	go func() {
 | |
| 		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	<-stop
 | |
| 	slog.Info("Shutting down servers...")
 | |
| 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 | |
| 	defer cancel()
 | |
| 
 | |
| 	if err := server.Shutdown(ctx); err != nil {
 | |
| 		slog.Error("Main server shutdown failed", "error", err)
 | |
| 	}
 | |
| 	if err := pprofServer.Shutdown(ctx); err != nil {
 | |
| 		slog.Error("Pprof server shutdown failed", "error", err)
 | |
| 	}
 | |
| }
 | 
