Files
GoSally/core/general_server/handle_multi.go
2025-07-25 11:31:53 +03:00

251 lines
8.0 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 (
"encoding/json"
"errors"
"io"
"log/slog"
"net/http"
"github.com/akyaiy/GoSally-mvp/core/config"
)
// 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)
}
*/
type GeneralServerApiContract interface {
GetVersion() string
Handle(w http.ResponseWriter, r *http.Request)
}
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")
}
func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
WriteRouterError(w, &RouterError{
Status: "error",
StatusCode: http.StatusBadRequest,
Payload: map[string]any{
"Message": IssueMethod,
},
})
s.log.Info("invalid request received", slog.String("issue", IssueMethod), slog.String("requested-method", r.Method))
return
}
var payload RawPettiEnvelope
body, err := io.ReadAll(r.Body)
if err != nil {
WriteRouterError(w, &RouterError{
Status: "error",
StatusCode: http.StatusBadRequest,
Payload: map[string]any{
"Message": IssueToReadBody,
},
})
s.log.Info("invalid request received", slog.String("issue", IssueToReadBody))
return
}
if err := json.Unmarshal(body, &payload); err != nil {
WriteRouterError(w, &RouterError{
Status: "error",
StatusCode: http.StatusBadRequest,
Payload: map[string]any{
"Message": InvalidProtocol,
},
})
s.log.Info("invalid request received", slog.String("issue", InvalidProtocol))
return
}
server, ok := s.servers[serversApiVer(payload.PettiVer)]
if !ok {
WriteRouterError(w, &RouterError{
Status: "error",
StatusCode: http.StatusBadRequest,
Payload: map[string]any{
"Message": InvalidProtovolVersion,
},
})
s.log.Info("invalid request received", slog.String("issue", InvalidProtovolVersion), slog.String("requested-version", payload.PettiVer))
return
}
server.Handle(w, r)
}
/*
// 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()))
}
}
*/