Add GeneralServer implementation for API request routing based on versioning

This commit is contained in:
alex
2025-07-05 15:04:39 +03:00
parent 7093183140
commit b70819e976

View File

@@ -1,3 +1,16 @@
// 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 package general_server
import ( import (
@@ -12,52 +25,68 @@ import (
"github.com/go-chi/chi/v5" "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 type serversApiVer string
// GeneralServerApiContract defines the interface for servers that can be registered
type GeneralServerApiContract interface { type GeneralServerApiContract interface {
// GetVersion returns the API version of the server.
GetVersion() string GetVersion() string
// Handle and HandleList methods are used to forward requests.
Handle(w http.ResponseWriter, r *http.Request) Handle(w http.ResponseWriter, r *http.Request)
HandleList(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 { type GeneralServerContarct interface {
GeneralServerApiContract GeneralServerApiContract
// AppendToArray adds a new server to the GeneralServer's internal map.
AppendToArray(GeneralServerApiContract) error AppendToArray(GeneralServerApiContract) error
} }
// GeneralServer implements the GeneralServerApiContract and serves as a router for different API versions.
type GeneralServer struct { type GeneralServer struct {
w http.ResponseWriter w http.ResponseWriter
r *http.Request 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 servers map[serversApiVer]GeneralServerApiContract
log slog.Logger log slog.Logger
cfg *config.ConfigConf cfg *config.ConfigConf
} }
// structure only for initialization // GeneralServerInit structure only for initialization general server.
type GeneralServerInit struct { type GeneralServerInit struct {
Log slog.Logger Log slog.Logger
Config *config.ConfigConf Config *config.ConfigConf
} }
// InitGeneral initializes a new GeneralServer with the provided configuration and registered servers.
func InitGeneral(o *GeneralServerInit, servers ...GeneralServerApiContract) *GeneralServer { func InitGeneral(o *GeneralServerInit, servers ...GeneralServerApiContract) *GeneralServer {
general := &GeneralServer{ general := &GeneralServer{
servers: make(map[serversApiVer]GeneralServerApiContract), servers: make(map[serversApiVer]GeneralServerApiContract),
cfg: o.Config, cfg: o.Config,
log: o.Log, log: o.Log,
} }
// register the provided servers
// s is each server implementing GeneralServerApiContract, this is not a general server
for _, s := range servers { for _, s := range servers {
general.servers[serversApiVer(s.GetVersion())] = s general.servers[serversApiVer(s.GetVersion())] = s
} }
return general return general
} }
// GetVersion returns the API version of the GeneralServer, which is "general".
func (s *GeneralServer) GetVersion() string { func (s *GeneralServer) GetVersion() string {
return "general" return "general"
} }
// AppendToArray adds a new server to the GeneralServer's internal map.
func (s *GeneralServer) AppendToArray(server GeneralServerApiContract) error { func (s *GeneralServer) AppendToArray(server GeneralServerApiContract) error {
if _, exist := s.servers[serversApiVer(server.GetVersion())]; !exist { if _, exist := s.servers[serversApiVer(server.GetVersion())]; !exist {
s.servers[serversApiVer(server.GetVersion())] = server s.servers[serversApiVer(server.GetVersion())] = server
@@ -66,6 +95,8 @@ func (s *GeneralServer) AppendToArray(server GeneralServerApiContract) error {
return errors.New("server with this version is already exist") 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) { func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
s.w = w s.w = w
s.r = r s.r = r
@@ -83,17 +114,25 @@ func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
s.log.Debug("Received request") s.log.Debug("Received request")
// transfer control to the server
if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok { if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok {
srv.Handle(w, r) srv.Handle(w, r)
return 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.Layers, serverReqApiVer) { if slices.Contains(s.cfg.Layers, serverReqApiVer) {
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok { if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
s.log.Debug("Using latest version under custom layer", s.log.Debug("Using latest version under custom layer",
slog.String("layer", serverReqApiVer), slog.String("layer", serverReqApiVer),
slog.String("fallback-version", s.cfg.LatestVer), slog.String("fallback-version", s.cfg.LatestVer),
) )
// transfer control to the latest version server under the custom layer
srv.Handle(w, r) srv.Handle(w, r)
return return
} }
@@ -104,6 +143,7 @@ func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
s.writeJSONError(http.StatusBadRequest, "unsupported API version") s.writeJSONError(http.StatusBadRequest, "unsupported API version")
} }
// 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) { func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
s.w = w s.w = w
s.r = r s.r = r
@@ -122,6 +162,7 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
log.Debug("Received request") log.Debug("Received request")
// transfer control to the server
if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok { if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok {
srv.HandleList(w, r) srv.HandleList(w, r)
return return
@@ -133,6 +174,7 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
slog.String("layer", serverReqApiVer), slog.String("layer", serverReqApiVer),
slog.String("fallback-version", s.cfg.LatestVer), slog.String("fallback-version", s.cfg.LatestVer),
) )
// transfer control to the latest version server under the custom layer
srv.HandleList(w, r) srv.HandleList(w, r)
return return
} }
@@ -143,15 +185,8 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
s.writeJSONError(http.StatusBadRequest, "unsupported API version") s.writeJSONError(http.StatusBadRequest, "unsupported API version")
} }
// func (s *GeneralServer) _errNotFound() { // writeJSONError writes a JSON error response to the HTTP response writer.
// s.writeJSONError(http.StatusBadRequest, "invalid request") // It sets the Content-Type to application/json, writes the specified HTTP status code,
// s.log.Error("HTTP request error",
// slog.String("remote", s.r.RemoteAddr),
// slog.String("method", s.r.Method),
// slog.String("url", s.r.URL.String()),
// slog.Int("status", http.StatusBadRequest))
// }
func (s *GeneralServer) writeJSONError(status int, msg string) { func (s *GeneralServer) writeJSONError(status int, msg string) {
s.w.Header().Set("Content-Type", "application/json") s.w.Header().Set("Content-Type", "application/json")
s.w.WriteHeader(status) s.w.WriteHeader(status)