feat: backend service

This commit is contained in:
Ilia Denisov
2026-05-06 10:14:55 +03:00
committed by GitHub
parent 3e2622757e
commit f446c6a2ac
1486 changed files with 49720 additions and 266401 deletions
+124
View File
@@ -0,0 +1,124 @@
// Package server is documented in router.go. server.go owns the HTTP listener
// lifecycle: it binds the configured TCP listener, serves the supplied
// http.Handler, and shuts down within the configured budget.
package server
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"sync"
"time"
"galaxy/backend/internal/config"
"go.uber.org/zap"
)
// Server owns the HTTP listener exposed by the backend.
type Server struct {
cfg config.HTTPConfig
handler http.Handler
logger *zap.Logger
stateMu sync.RWMutex
server *http.Server
listener net.Listener
}
// NewServer constructs an HTTP server bound to cfg. handler is the prebuilt
// http.Handler returned by NewRouter. A nil logger is replaced with zap.NewNop.
func NewServer(cfg config.HTTPConfig, handler http.Handler, logger *zap.Logger) *Server {
if logger == nil {
logger = zap.NewNop()
}
if handler == nil {
handler = http.NotFoundHandler()
}
return &Server{
cfg: cfg,
handler: handler,
logger: logger.Named("http"),
}
}
// Run binds the listener and serves requests until Shutdown closes the server.
func (s *Server) Run(ctx context.Context) error {
if ctx == nil {
return errors.New("run backend HTTP server: nil context")
}
if err := ctx.Err(); err != nil {
return err
}
listener, err := net.Listen("tcp", s.cfg.Addr)
if err != nil {
return fmt.Errorf("run backend HTTP server: listen on %q: %w", s.cfg.Addr, err)
}
server := &http.Server{
Handler: s.handler,
ReadTimeout: s.cfg.ReadTimeout,
WriteTimeout: s.cfg.WriteTimeout,
IdleTimeout: s.cfg.ReadTimeout,
}
s.stateMu.Lock()
s.server = server
s.listener = listener
s.stateMu.Unlock()
s.logger.Info("backend HTTP server started", zap.String("addr", listener.Addr().String()))
defer func() {
s.stateMu.Lock()
s.server = nil
s.listener = nil
s.stateMu.Unlock()
}()
err = server.Serve(listener)
switch {
case err == nil:
return nil
case errors.Is(err, http.ErrServerClosed):
s.logger.Info("backend HTTP server stopped")
return nil
default:
return fmt.Errorf("run backend HTTP server: serve on %q: %w", s.cfg.Addr, err)
}
}
// Shutdown gracefully stops the HTTP server within ctx, applying the
// configured per-listener shutdown timeout when it is shorter.
func (s *Server) Shutdown(ctx context.Context) error {
if ctx == nil {
return errors.New("shutdown backend HTTP server: nil context")
}
s.stateMu.RLock()
server := s.server
s.stateMu.RUnlock()
if server == nil {
return nil
}
shutdownCtx, cancel := boundedContext(ctx, s.cfg.ShutdownTimeout)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("shutdown backend HTTP server: %w", err)
}
return nil
}
func boundedContext(parent context.Context, limit time.Duration) (context.Context, context.CancelFunc) {
if limit <= 0 {
return context.WithCancel(parent)
}
return context.WithTimeout(parent, limit)
}