Files
GoSally/core/general_server/handle_multi.go
2025-07-10 18:22:53 +03:00

191 lines
6.7 KiB
Go

// Package general_server provides an API request router based on versioning and custom layers.
//
// The GeneralServer distributes incoming HTTP requests to specific registered servers
// depending on the API version or defined logical layer. To operate properly, additional
// servers must be registered using the InitGeneral function or AppendToArray method.
//
// All registered servers must implement the GeneralServerApiContract interface to ensure
// correct interaction. The GeneralServer itself implements this interface and can be
// passed as an HTTP handler.
//
// If the requested version is not explicitly registered but matches a configured logical
// layer, the server will fallback to the latest registered version for that layer.
// Otherwise, an HTTP 400 error is returned.
package general_server
import (
"errors"
"log/slog"
"net/http"
"slices"
"github.com/akyaiy/GoSally-mvp/core/config"
"github.com/akyaiy/GoSally-mvp/core/utils"
"github.com/go-chi/chi/v5"
)
// serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer.
type serversApiVer string
// GeneralServerApiContract defines the interface for servers that can be registered
type GeneralServerApiContract interface {
// GetVersion returns the API version of the server.
GetVersion() string
// Handle and HandleList methods are used to forward requests.
Handle(w http.ResponseWriter, r *http.Request)
HandleList(w http.ResponseWriter, r *http.Request)
}
// GeneralServerContarct extends the GeneralServerApiContract with a method to append new servers.
// This interface is only for general server initialization and does not need to be implemented by individual servers.
type GeneralServerContarct interface {
GeneralServerApiContract
// AppendToArray adds a new server to the GeneralServer's internal map.
AppendToArray(GeneralServerApiContract) error
}
// GeneralServer implements the GeneralServerApiContract and serves as a router for different API versions.
type GeneralServer struct {
w http.ResponseWriter
r *http.Request
// servers holds the registered servers by their API version.
// The key is the version string, and the value is the server implementing GeneralServerApi
servers map[serversApiVer]GeneralServerApiContract
log slog.Logger
cfg *config.Conf
}
// GeneralServerInit structure only for initialization general server.
type GeneralServerInit struct {
Log slog.Logger
Config *config.Conf
}
// InitGeneral initializes a new GeneralServer with the provided configuration and registered servers.
func InitGeneral(o *GeneralServerInit, servers ...GeneralServerApiContract) *GeneralServer {
general := &GeneralServer{
servers: make(map[serversApiVer]GeneralServerApiContract),
cfg: o.Config,
log: o.Log,
}
// register the provided servers
// s is each server implementing GeneralServerApiContract, this is not a general server
for _, s := range servers {
general.servers[serversApiVer(s.GetVersion())] = s
}
return general
}
// GetVersion returns the API version of the GeneralServer, which is "general".
func (s *GeneralServer) GetVersion() string {
return "general"
}
// AppendToArray adds a new server to the GeneralServer's internal map.
func (s *GeneralServer) AppendToArray(server GeneralServerApiContract) error {
if _, exist := s.servers[serversApiVer(server.GetVersion())]; !exist {
s.servers[serversApiVer(server.GetVersion())] = server
return nil
}
return errors.New("server with this version is already exist")
}
// Handle processes incoming HTTP requests, routing them to the appropriate server based on the API version.
// It checks if the requested version is registered and handles the request accordingly.
func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
s.w = w
s.r = r
serverReqApiVer := chi.URLParam(r, "ver")
log := s.log.With(
slog.Group("request",
slog.String("version", serverReqApiVer),
slog.String("url", s.r.URL.String()),
slog.String("method", s.r.Method),
),
slog.Group("connection",
slog.String("remote", s.r.RemoteAddr),
),
)
s.log.Debug("Received request")
// transfer control to the server
if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok {
srv.Handle(w, r)
return
}
// if the requested version is not registered, check if it matches a logical layer
// and use the latest version for that layer if available
// this allows for custom layers to be defined in the configuration
// and used as a fallback for unsupported versions
// this is useful for cases where the API version is not explicitly registered
// but the logical layer is defined in the configuration
if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) {
if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok {
s.log.Debug("Using latest version under custom layer",
slog.String("layer", serverReqApiVer),
slog.String("fallback-version", s.cfg.HTTPServer.HTTPServer_Api.LatestVer),
)
// transfer control to the latest version server under the custom layer
srv.Handle(w, r)
return
}
}
log.Error("HTTP request error: unsupported API version",
slog.Int("status", http.StatusBadRequest))
if err := utils.WriteJSONError(s.w, http.StatusBadRequest, "unsupported API version"); err != nil {
s.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
}
// HandleList processes incoming HTTP requests for listing commands, routing them to the appropriate server based on the API version.
func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
s.w = w
s.r = r
serverReqApiVer := chi.URLParam(r, "ver")
log := s.log.With(
slog.Group("request",
slog.String("version", serverReqApiVer),
slog.String("url", s.r.URL.String()),
slog.String("method", s.r.Method),
),
slog.Group("connection",
slog.String("remote", s.r.RemoteAddr),
),
)
log.Debug("Received request")
// transfer control to the server
if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok {
srv.HandleList(w, r)
return
}
if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) {
if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok {
log.Debug("Using latest version under custom layer",
slog.String("layer", serverReqApiVer),
slog.String("fallback-version", s.cfg.HTTPServer.HTTPServer_Api.LatestVer),
)
// transfer control to the latest version server under the custom layer
srv.HandleList(w, r)
return
}
}
log.Error("HTTP request error: unsupported API version",
slog.Int("status", http.StatusBadRequest))
if err := utils.WriteJSONError(s.w, http.StatusBadRequest, "unsupported API version"); err != nil {
s.log.Error("Failed to write JSON", slog.String("err", err.Error()))
}
}