mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 08:32:24 +00:00
Refactor error handling and utility functions; remove deprecated code and improve logging
This commit is contained in:
@@ -63,7 +63,6 @@ func main() {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
r.NotFound(serverv1.ErrNotFound)
|
||||
|
||||
address := cfg.Address
|
||||
if cfg.TlsEnabled {
|
||||
|
||||
16
com/exec.lua
16
com/exec.lua
@@ -1,16 +0,0 @@
|
||||
if not Params.f then
|
||||
Result.status = "error"
|
||||
Result.error = "Missing parameter: f"
|
||||
return
|
||||
end
|
||||
|
||||
local code = os.execute("touch " .. Params.f)
|
||||
if code ~= 0 then
|
||||
Result.status = "error"
|
||||
Result.message = "Failed to execute command"
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
Result.status = "ok"
|
||||
Result.message = "Command executed successfully"
|
||||
@@ -5,12 +5,14 @@ var UUIDLength byte = 4
|
||||
|
||||
// ApiRoute setting for go-chi for main route for api requests
|
||||
var ApiRoute string = "/api/{ver}"
|
||||
|
||||
// ComDirRoute setting for go-chi for main route for commands
|
||||
var ComDirRoute string = "/com"
|
||||
|
||||
// NodeVersion is the version of the node. It can be set by the build system or manually.
|
||||
// If not set, it will return "version0.0.0-none" by default
|
||||
var NodeVersion string
|
||||
|
||||
// ActualFileName is a feature of the GoSally update system.
|
||||
// In the repository, the file specified in the variable contains the current information about updates
|
||||
var ActualFileName string = "actual.txt"
|
||||
@@ -28,7 +30,7 @@ func (_ _updateConsts) GetNodeVersion() string {
|
||||
}
|
||||
func (_ _updateConsts) GetActualFileName() string { return ActualFileName }
|
||||
|
||||
func GetInternalConsts() _internalConsts { return _internalConsts{} }
|
||||
func GetInternalConsts() _internalConsts { return _internalConsts{} }
|
||||
func (_ _internalConsts) GetUUIDLength() byte { return UUIDLength }
|
||||
|
||||
func GetServerConsts() _serverConsts { return _serverConsts{} }
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
package general_server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
)
|
||||
@@ -140,7 +140,7 @@ func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
log.Error("HTTP request error: unsupported API version",
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
s.writeJSONError(http.StatusBadRequest, "unsupported API version")
|
||||
utils.WriteJSONError(s.w, http.StatusBadRequest, "unsupported API version")
|
||||
}
|
||||
|
||||
// HandleList processes incoming HTTP requests for listing commands, routing them to the appropriate server based on the API version.
|
||||
@@ -182,23 +182,5 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
log.Error("HTTP request error: unsupported API version",
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
s.writeJSONError(http.StatusBadRequest, "unsupported API version")
|
||||
}
|
||||
|
||||
// writeJSONError writes a JSON error response to the HTTP response writer.
|
||||
// It sets the Content-Type to application/json, writes the specified HTTP status code,
|
||||
func (s *GeneralServer) writeJSONError(status int, msg string) {
|
||||
s.w.Header().Set("Content-Type", "application/json")
|
||||
s.w.WriteHeader(status)
|
||||
resp := map[string]interface{}{
|
||||
"status": "error",
|
||||
"error": msg,
|
||||
"code": status,
|
||||
}
|
||||
if err := json.NewEncoder(s.w).Encode(resp); err != nil {
|
||||
s.log.Error("Failed to write JSON error response",
|
||||
slog.String("error", err.Error()),
|
||||
slog.Int("status", status))
|
||||
return
|
||||
}
|
||||
utils.WriteJSONError(s.w, http.StatusBadRequest, "unsupported API version")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Package logs provides a logger setup function that configures the logger based on the environment.
|
||||
// It supports different logging levels for development and production environments.
|
||||
// It uses the standard library's slog package for structured logging.
|
||||
package logs
|
||||
|
||||
import (
|
||||
@@ -5,11 +8,15 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Environment constants for logger setup
|
||||
const (
|
||||
envDev = "dev"
|
||||
// envDev enables development logging with debug level
|
||||
envDev = "dev"
|
||||
// envProd enables production logging with info level
|
||||
envProd = "prod"
|
||||
)
|
||||
|
||||
// SetupLogger initializes and returns a logger based on the provided environment.
|
||||
func SetupLogger(env string) *slog.Logger {
|
||||
var log *slog.Logger
|
||||
switch env {
|
||||
|
||||
@@ -6,30 +6,20 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MockHandler is a mock implementation of slog.Handler for testing purposes.
|
||||
type MockHandler struct {
|
||||
mu sync.Mutex
|
||||
mu sync.Mutex
|
||||
// Logs stores the log records captured by the handler.
|
||||
Logs []slog.Record
|
||||
}
|
||||
|
||||
func NewMockHandler() *MockHandler {
|
||||
return &MockHandler{}
|
||||
}
|
||||
|
||||
func (h *MockHandler) Enabled(_ context.Context, _ slog.Level) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewMockHandler() *MockHandler { return &MockHandler{} }
|
||||
func (h *MockHandler) Enabled(_ context.Context, _ slog.Level) bool { return true }
|
||||
func (h *MockHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h }
|
||||
func (h *MockHandler) WithGroup(_ string) slog.Handler { return h }
|
||||
func (h *MockHandler) Handle(_ context.Context, r slog.Record) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.Logs = append(h.Logs, r.Clone())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MockHandler) WithAttrs(_ []slog.Attr) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *MockHandler) WithGroup(_ string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -7,12 +7,22 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) _handle() {
|
||||
uuid16 := h.newUUID()
|
||||
// HandlerV1 is the main handler for version 1 of the API.
|
||||
// The function processes the HTTP request and runs Lua scripts,
|
||||
// preparing the environment and subsequently transmitting the execution result
|
||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
uuid16, err := utils.NewUUID()
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
slog.String("error", err.Error()))
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error())
|
||||
return
|
||||
}
|
||||
log := h.log.With(
|
||||
slog.Group("request",
|
||||
slog.String("version", h.GetVersion()),
|
||||
@@ -32,7 +42,7 @@ func (h *HandlerV1) _handle() {
|
||||
slog.String("error", "invalid command"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
h.writeJSONError(http.StatusBadRequest, "invalid command")
|
||||
utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid command")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,7 +52,7 @@ func (h *HandlerV1) _handle() {
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
h.writeJSONError(http.StatusNotFound, "command not found")
|
||||
utils.WriteJSONError(h.w, http.StatusNotFound, "command not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,15 +62,13 @@ func (h *HandlerV1) _handle() {
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
h.writeJSONError(http.StatusNotFound, "command not found")
|
||||
utils.WriteJSONError(h.w, http.StatusNotFound, "command not found")
|
||||
return
|
||||
}
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
// Создаем таблицу Params
|
||||
// Создаем таблицу In с Params
|
||||
paramsTable := L.NewTable()
|
||||
qt := h.r.URL.Query()
|
||||
for k, v := range qt {
|
||||
@@ -78,49 +86,44 @@ func (h *HandlerV1) _handle() {
|
||||
L.SetField(outTable, "Result", resultTable)
|
||||
L.SetGlobal("Out", outTable)
|
||||
|
||||
// Скрипт подготовки окружения
|
||||
prepareLuaEnv := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
||||
if _, err := os.Stat(prepareLuaEnv); err == nil {
|
||||
if err := L.DoFile(prepareLuaEnv); err != nil {
|
||||
log.Error("Failed to prepare lua environment",
|
||||
slog.String("error", err.Error()))
|
||||
h.writeJSONError(http.StatusInternalServerError, "lua error: "+err.Error())
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "lua error: "+err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Warn("No environment preparation script found, skipping preparation")
|
||||
}
|
||||
|
||||
// Основной Lua скрипт
|
||||
if err := L.DoFile(scriptPath); err != nil {
|
||||
log.Error("Failed to execute lua script",
|
||||
slog.String("error", err.Error()))
|
||||
h.writeJSONError(http.StatusInternalServerError, "lua error: "+err.Error())
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "lua error: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем Out
|
||||
lv := L.GetGlobal("Out")
|
||||
tbl, ok := lv.(*lua.LTable)
|
||||
if !ok {
|
||||
log.Error("Lua global 'Out' is not a table")
|
||||
h.writeJSONError(http.StatusInternalServerError, "'Out' is not a table")
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "'Out' is not a table")
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем Result из Out
|
||||
resultVal := tbl.RawGetString("Result")
|
||||
resultTbl, ok := resultVal.(*lua.LTable)
|
||||
if !ok {
|
||||
log.Error("Lua global 'Result' is not a table")
|
||||
h.writeJSONError(http.StatusInternalServerError, "'Result' is not a table")
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "'Result' is not a table")
|
||||
return
|
||||
}
|
||||
|
||||
// Перебираем таблицу Result
|
||||
out := make(map[string]interface{})
|
||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
out[key.String()] = convertTypes(value)
|
||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||
})
|
||||
|
||||
h.w.Header().Set("Content-Type", "application/json")
|
||||
@@ -147,26 +150,3 @@ func (h *HandlerV1) _handle() {
|
||||
|
||||
log.Info("Session completed")
|
||||
}
|
||||
|
||||
func convertTypes(value lua.LValue) any {
|
||||
switch value.Type() {
|
||||
case lua.LTString:
|
||||
return value.String()
|
||||
case lua.LTNumber:
|
||||
return float64(value.(lua.LNumber))
|
||||
case lua.LTBool:
|
||||
return bool(value.(lua.LBool))
|
||||
case lua.LTTable:
|
||||
result := make(map[string]interface{})
|
||||
if tbl, ok := value.(*lua.LTable); ok {
|
||||
tbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
result[key.String()] = convertTypes(value)
|
||||
})
|
||||
}
|
||||
return result
|
||||
case lua.LTNil:
|
||||
return nil
|
||||
default:
|
||||
return value.String()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,19 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) _handleList() {
|
||||
uuid16 := h.newUUID()
|
||||
// The function processes the HTTP request and returns a list of available commands.
|
||||
func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
uuid16, err := utils.NewUUID()
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
slog.String("error", err.Error()))
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error())
|
||||
return
|
||||
}
|
||||
log := h.log.With(
|
||||
slog.Group("request",
|
||||
slog.String("version", h.GetVersion()),
|
||||
@@ -31,7 +39,6 @@ func (h *HandlerV1) _handleList() {
|
||||
}
|
||||
var (
|
||||
files []os.DirEntry
|
||||
err error
|
||||
commands = make(map[string]ComMeta)
|
||||
cmdsProcessed = make(map[string]bool)
|
||||
)
|
||||
@@ -39,7 +46,7 @@ func (h *HandlerV1) _handleList() {
|
||||
if files, err = os.ReadDir(h.cfg.ComDir); err != nil {
|
||||
log.Error("Failed to read commands directory",
|
||||
slog.String("error", err.Error()))
|
||||
h.writeJSONError(http.StatusInternalServerError, "failed to read commands directory: "+err.Error())
|
||||
utils.WriteJSONError(h.w, http.StatusInternalServerError, "failed to read commands directory: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Package sv1 provides the implementation of the Server V1 API handler.
|
||||
// It includes utilities for handling API requests, extracting descriptions, and managing UUIDs.
|
||||
package sv1
|
||||
|
||||
import (
|
||||
@@ -8,16 +10,7 @@ import (
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
)
|
||||
|
||||
type ServerV1UtilsContract interface {
|
||||
extractDescriptionStatic(path string) (string, error)
|
||||
writeJSONError(status int, msg string)
|
||||
newUUID() string
|
||||
|
||||
_errNotFound()
|
||||
ErrNotFound(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// structure only for initialization
|
||||
// HandlerV1InitStruct structure is only for initialization
|
||||
type HandlerV1InitStruct struct {
|
||||
Ver string
|
||||
Log slog.Logger
|
||||
@@ -26,6 +19,7 @@ type HandlerV1InitStruct struct {
|
||||
ListAllowedCmd *regexp.Regexp
|
||||
}
|
||||
|
||||
// HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests.
|
||||
type HandlerV1 struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
@@ -34,12 +28,16 @@ type HandlerV1 struct {
|
||||
|
||||
cfg *config.ConfigConf
|
||||
|
||||
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
|
||||
allowedCmd *regexp.Regexp
|
||||
listAllowedCmd *regexp.Regexp
|
||||
|
||||
ver string
|
||||
}
|
||||
|
||||
// InitV1Server initializes a new HandlerV1 with the provided configuration and returns it.
|
||||
// Should be carefull with giving to this function invalid parameters,
|
||||
// because there is no validation of parameters in this function.
|
||||
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||
return &HandlerV1{
|
||||
log: o.Log,
|
||||
@@ -50,18 +48,8 @@ func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
h.w = w
|
||||
h.r = r
|
||||
h._handle()
|
||||
}
|
||||
|
||||
func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
h.w = w
|
||||
h.r = r
|
||||
h._handleList()
|
||||
}
|
||||
|
||||
// GetVersion returns the API version of the HandlerV1, which is set during initialization.
|
||||
// This version is used to identify the API version in the request routing.
|
||||
func (h *HandlerV1) GetVersion() string {
|
||||
return h.ver
|
||||
}
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) ErrNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
h.w = w
|
||||
h.r = r
|
||||
h._errNotFound()
|
||||
}
|
||||
|
||||
func (h *HandlerV1) newUUID() string {
|
||||
bytes := make([]byte, int(config.GetInternalConsts().GetUUIDLength()/2))
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID", slog.String("error", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
func (h *HandlerV1) _errNotFound() {
|
||||
h.writeJSONError(http.StatusBadRequest, "invalid request")
|
||||
func (h *HandlerV1) errNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid request")
|
||||
h.log.Error("HTTP request error",
|
||||
slog.String("remote", h.r.RemoteAddr),
|
||||
slog.String("method", h.r.Method),
|
||||
@@ -37,22 +18,6 @@ func (h *HandlerV1) _errNotFound() {
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
}
|
||||
|
||||
func (h *HandlerV1) writeJSONError(status int, msg string) {
|
||||
h.w.Header().Set("Content-Type", "application/json")
|
||||
h.w.WriteHeader(status)
|
||||
resp := map[string]interface{}{
|
||||
"status": "error",
|
||||
"error": msg,
|
||||
"code": status,
|
||||
}
|
||||
if err := json.NewEncoder(h.w).Encode(resp); err != nil {
|
||||
h.log.Error("Failed to write JSON error response",
|
||||
slog.String("error", err.Error()),
|
||||
slog.Int("status", status))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
||||
22
core/utils/http_errors.go
Normal file
22
core/utils/http_errors.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// writeJSONError writes a JSON error response to the HTTP response writer.
|
||||
// It sets the Content-Type to application/json, writes the specified HTTP status code
|
||||
func WriteJSONError(w http.ResponseWriter, status int, msg string) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
resp := map[string]any{
|
||||
"status": "error",
|
||||
"error": msg,
|
||||
"code": status,
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
core/utils/internal_lua.go
Normal file
26
core/utils/internal_lua.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import lua "github.com/yuin/gopher-lua"
|
||||
|
||||
func ConvertLuaTypesToGolang(value lua.LValue) any {
|
||||
switch value.Type() {
|
||||
case lua.LTString:
|
||||
return value.String()
|
||||
case lua.LTNumber:
|
||||
return float64(value.(lua.LNumber))
|
||||
case lua.LTBool:
|
||||
return bool(value.(lua.LBool))
|
||||
case lua.LTTable:
|
||||
result := make(map[string]interface{})
|
||||
if tbl, ok := value.(*lua.LTable); ok {
|
||||
tbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
result[key.String()] = ConvertLuaTypesToGolang(value)
|
||||
})
|
||||
}
|
||||
return result
|
||||
case lua.LTNil:
|
||||
return nil
|
||||
default:
|
||||
return value.String()
|
||||
}
|
||||
}
|
||||
18
core/utils/uuid.go
Normal file
18
core/utils/uuid.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
)
|
||||
|
||||
func NewUUID() (string, error) {
|
||||
bytes := make([]byte, int(config.GetInternalConsts().GetUUIDLength()/2))
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to generate UUID: " + err.Error())
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
Reference in New Issue
Block a user