Files
hastebin-ansi/internal/server/server.go
2025-03-17 14:19:17 +03:00

114 lines
2.9 KiB
Go

package server
import (
"context"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/armbian/ansi-hastebin/config"
"github.com/armbian/ansi-hastebin/handler"
"github.com/armbian/ansi-hastebin/internal/keygenerator"
"github.com/armbian/ansi-hastebin/internal/storage"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/httprate"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log"
)
type Server struct {
config *config.Config
storage storage.Storage
keyGenerator keygenerator.KeyGenerator
server *http.Server
mux *chi.Mux
}
func NewServer(config *config.Config, storage storage.Storage, keyGenerator keygenerator.KeyGenerator) *Server {
mux := chi.NewRouter()
httpServer := &http.Server{
Addr: config.Host + ":" + strconv.Itoa(config.Port),
Handler: mux,
}
return &Server{
config: config,
storage: storage,
keyGenerator: keyGenerator,
server: httpServer,
mux: mux,
}
}
func (s *Server) RegisterRoutes() {
// Register middlewares
s.mux.Use(middleware.Logger)
s.mux.Use(middleware.Recoverer)
// Rate limiter
if s.config.RateLimiting.Enable {
s.mux.Use(httprate.LimitByRealIP(s.config.RateLimiting.Limit, time.Duration(s.config.RateLimiting.Window)*time.Second))
}
// Register promhttp middleware
s.mux.Get("/metrics", promhttp.Handler().ServeHTTP)
// Register document handler
documentHandler := handler.NewDocumentHandler(s.config.KeyLength, s.config.MaxLength, s.storage, s.keyGenerator)
documentHandler.RegisterRoutes(s.mux)
// Register health check
s.mux.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
// Register static files
static := os.DirFS("static")
fileServer := http.FileServer(http.FS(static))
s.mux.Get("/*", func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/")
if _, err := static.Open(path); err == nil {
fileServer.ServeHTTP(w, r)
return
}
// If file does not exist, serve index.html
index, err := static.Open("index.html")
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
defer index.Close()
if _, err := io.Copy(w, index); err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
})
}
func (s *Server) Start() {
log.Info().Str("host", s.config.Host).Int("port", s.config.Port).Msg("Starting server")
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("Failed to start server")
}
}
func (s *Server) Shutdown(ctx context.Context) {
log.Info().Msg("Gracefully shutting down server")
if err := s.storage.Close(); err != nil {
log.Error().Err(err).Msg("Failed to close storage")
}
if err := s.server.Shutdown(ctx); err != nil {
log.Error().Err(err).Msg("Failed to shutdown server")
}
}