mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 17:32:25 +00:00
Add GeneralServer implementation for API request routing based on versioning
This commit is contained in:
@@ -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)
|
||||||
Reference in New Issue
Block a user