Refactor and restructure core components

- Removed obsolete files: `hanle_multi.go`, `logger.go`, `handle_com.go`, `handle_list.go`, `server.go`, and `utils.go` from the `general_server` and `sv1` packages.
- Introduced new implementations for the `GeneralServer` and `HandlerV1` in the `core` package, enhancing the overall architecture.
- Updated the `go.mod` and `go.sum` files to include new dependencies.
- Added a new configuration structure in `config.yaml` and `config.go` for better management of application settings.
- Implemented a Lua command handling mechanism in `handle_com.go` and `handle_list.go` for improved command execution.
- Enhanced logging capabilities with a new logger setup in `logger.go`.
- Added a new Lua script `http.lua` for handling HTTP requests.
This commit is contained in:
alex
2025-06-26 16:50:35 +03:00
parent 9f7f046a07
commit 96fb13e3c7
15 changed files with 214 additions and 145 deletions

View File

@@ -1,6 +1,9 @@
APP_NAME := node
BIN_DIR := bin
GOPATH := $(shell go env GOPATH)
export CONFIG_PATH := ./config.yaml
CGO_CFLAGS := -I/usr/local/include
CGO_LDFLAGS := -L/usr/local/lib -llua5.1 -lm -ldl
.PHONY: all build run runq test fmt vet lint check clean
all: build
@@ -20,12 +23,17 @@ setup: lint-setup goimports-setup golicenses-setup
@echo "Setup complete. Run 'make build' to compile the application."
build:
@go build -o $(BIN_DIR)/$(APP_NAME) ./cmd/$(APP_NAME)
@echo "Building..."
@echo "CGO_CFLAGS is: '$(CGO_CFLAGS)'"
@echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o $(BIN_DIR)/$(APP_NAME) ./cmd/$(APP_NAME)
run: build
@echo "Running!"
./$(BIN_DIR)/$(APP_NAME)
runq: build
@echo "Running!"
./$(BIN_DIR)/$(APP_NAME) | jq
test:

View File

@@ -5,10 +5,10 @@ import (
"net/http"
"regexp"
"github.com/akyaiy/GoSally-mvp/config"
gs "github.com/akyaiy/GoSally-mvp/general_server"
"github.com/akyaiy/GoSally-mvp/logs"
"github.com/akyaiy/GoSally-mvp/sv1"
"github.com/akyaiy/GoSally-mvp/core/config"
gs "github.com/akyaiy/GoSally-mvp/core/general_server"
"github.com/akyaiy/GoSally-mvp/core/logs"
"github.com/akyaiy/GoSally-mvp/core/sv1"
"github.com/go-chi/chi/v5"
)
@@ -38,6 +38,7 @@ func main() {
Log: *logs.SetupLogger(cfg.Mode),
Config: cfg,
}, serverv1)
r := chi.NewRouter()
r.Route("/api/{ver}/com", func(r chi.Router) {
r.Get("/", s.HandleList)

View File

@@ -1,4 +1,5 @@
package.path = "./com/?.lua;" .. package.path
package.path = package.path .. ";/usr/lib64/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua" .. ";./com/?.lua;"
package.cpath = package.cpath .. ";/usr/lib64/lua/5.1/?.so;/usr/local/lib/lua/5.1/?.so"
print = function() end
io.write = function(...) end

15
com/http.lua Normal file
View File

@@ -0,0 +1,15 @@
package.path = package.path .. ";/usr/lib64/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua;" .. ";./com/?.lua;"
package.cpath = package.cpath .. ";/usr/lib64/lua/5.1/?.so;/usr/local/lib/lua/5.1/?.so;"
local https = require("ssl.https")
local ltn12 = require("ltn12")
local response = {}
local res, code, headers = https.request{
url = "https://localhost:8080/api/v1/echo?msg=sigma",
sink = ltn12.sink.table(response)
}
Result.msg = table.concat(response)
Result.status = "ok"

View File

@@ -7,7 +7,7 @@ import (
"net/http"
"slices"
"github.com/akyaiy/GoSally-mvp/config"
"github.com/akyaiy/GoSally-mvp/core/config"
"github.com/go-chi/chi/v5"
)
@@ -81,7 +81,7 @@ func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
),
)
s.log.Info("Received request")
s.log.Debug("Received request")
if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok {
srv.Handle(w, r)
@@ -90,7 +90,7 @@ func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
if slices.Contains(s.cfg.Layers, serverReqApiVer) {
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
s.log.Info("Using latest version under custom layer",
s.log.Debug("Using latest version under custom layer",
slog.String("layer", serverReqApiVer),
slog.String("fallback-version", s.cfg.LatestVer),
)
@@ -120,7 +120,7 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
),
)
log.Info("Received request")
log.Debug("Received request")
if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok {
srv.HandleList(w, r)
@@ -129,7 +129,7 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
if slices.Contains(s.cfg.Layers, serverReqApiVer) {
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
log.Info("Using latest version under custom layer",
log.Debug("Using latest version under custom layer",
slog.String("layer", serverReqApiVer),
slog.String("fallback-version", s.cfg.LatestVer),
)

174
core/sv1/handle_com.go Normal file
View File

@@ -0,0 +1,174 @@
package sv1
import (
"encoding/json"
"log/slog"
"net/http"
"os"
"path/filepath"
lua "github.com/aarzilli/golua/lua"
"github.com/go-chi/chi/v5"
)
func (h *HandlerV1) _handle() {
uuid16 := h.newUUID()
log := h.log.With(
slog.Group("request",
slog.String("version", h.GetVersion()),
slog.String("url", h.r.URL.String()),
slog.String("method", h.r.Method),
),
slog.Group("connection",
slog.String("connection-uuid", uuid16),
slog.String("remote", h.r.RemoteAddr),
),
)
log.Info("Received request")
cmd := chi.URLParam(h.r, "cmd")
var scriptPath string
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) {
log.Error("HTTP request error",
slog.String("error", "invalid command"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusBadRequest))
h.writeJSONError(http.StatusBadRequest, "invalid command")
return
}
if !h.listAllowedCmd.MatchString(cmd) {
log.Error("HTTP request error",
slog.String("error", "invalid command"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusBadRequest))
h.writeJSONError(http.StatusBadRequest, "invalid command")
return
}
if scriptPath = h.comMatch(chi.URLParam(h.r, "ver"), cmd); scriptPath == "" {
log.Error("HTTP request error",
slog.String("error", "command not found"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound))
h.writeJSONError(http.StatusNotFound, "command not found")
return
}
scriptPath = filepath.Join(h.cfg.ComDir, scriptPath)
if _, err := os.Stat(scriptPath); err != nil {
log.Error("HTTP request error",
slog.String("error", "command not found"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound))
h.writeJSONError(http.StatusNotFound, "command not found")
return
}
L := lua.NewState()
defer L.Close()
L.OpenLibs()
// Создаем таблицу Params
L.NewTable()
paramsTableIndex := L.GetTop() // Индекс таблицы в стеке
// Заполняем таблицу из query параметров
qt := h.r.URL.Query()
for k, v := range qt {
if len(v) > 0 {
L.PushString(v[0]) // Значение
L.SetField(paramsTableIndex, k) // paramsTable[k] = v[0]
}
}
// Помещаем Params в глобальные переменные
L.SetGlobal("Params")
// Создаем пустую таблицу Result
L.NewTable()
L.SetGlobal("Result")
// Загружаем и выполняем скрипт подготовки окружения, если есть
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())
return
}
} else {
log.Error("No environment preparation script found, skipping preparation",
slog.String("error", err.Error()))
}
// Выполняем основной Lua скрипт
if err := L.DoFile(scriptPath); err != nil {
log.Error("Failed to execute lua script",
slog.Group("lua-status",
slog.String("error", err.Error()),
slog.String("lua-version", lua.LUA_VERSION)))
h.writeJSONError(http.StatusInternalServerError, "lua error: "+err.Error())
return
}
// Получаем глобальную переменную Result (таблица)
L.GetGlobal("Result")
if L.IsTable(-1) {
out := make(map[string]interface{})
L.PushNil() // Первый ключ
for {
if L.Next(-2) == 0 {
break
}
// На стеке: -1 = value, -2 = key
key := L.ToString(-2)
var val interface{}
switch L.Type(-1) {
case lua.LUA_TSTRING:
val = L.ToString(-1)
case lua.LUA_TNUMBER:
val = L.ToNumber(-1)
case lua.LUA_TBOOLEAN:
val = L.ToBoolean(-1)
default:
// fallback
val = L.ToString(-1)
}
out[key] = val
L.Pop(1) // Удаляем value, key остаётся для следующего L.Next
}
L.Pop(1) // Удаляем таблицу Result со стека
h.w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(h.w).Encode(out); err != nil {
log.Error("Failed to encode JSON response",
slog.String("error", err.Error()))
}
switch out["status"] {
case "error":
log.Info("Command executed with error",
slog.String("cmd", cmd),
slog.Any("result", out))
case "ok":
log.Info("Command executed successfully",
slog.String("cmd", cmd), slog.Any("result", out))
default:
log.Info("Command executed and returned an unknown status",
slog.String("cmd", cmd),
slog.Any("result", out))
}
} else {
L.Pop(1) // убираем не таблицу из стека
log.Error("Lua global 'Result' is not a table")
h.writeJSONError(http.StatusInternalServerError, "'Result' is not a table")
return
}
log.Info("Session completed",
slog.Group("lua-status",
slog.String("lua-version", lua.LUA_VERSION)))
}

View File

@@ -5,7 +5,7 @@ import (
"net/http"
"regexp"
"github.com/akyaiy/GoSally-mvp/config"
"github.com/akyaiy/GoSally-mvp/core/config"
)
type ServerV1UtilsContract interface {

1
go.mod
View File

@@ -10,6 +10,7 @@ require (
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/aarzilli/golua v0.0.0-20250217091409-248753f411c4 // indirect
github.com/joho/godotenv v1.5.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect

2
go.sum
View File

@@ -1,6 +1,8 @@
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/aarzilli/golua v0.0.0-20250217091409-248753f411c4 h1:gW5i3FQAMcbkNgo/A87gCKAbBMalAO8BlPIMo9Gk2Ow=
github.com/aarzilli/golua v0.0.0-20250217091409-248753f411c4/go.mod h1:hMjfaJVSqVnxenMlsxrq3Ni+vrm9Hs64tU4M7dhUoO4=
github.com/akyaiy/GoSally-mvp/config v0.0.0-20250622141207-5326dd45b694 h1:SJfxaud4HMVg9roTMMJaTQ+Odoz1LIw60TiS97EtWCE=
github.com/akyaiy/GoSally-mvp/config v0.0.0-20250622141207-5326dd45b694/go.mod h1:2eaoBiPQmvZoC9DAzn11zHzWmscBI4dMTi4HeGO96XQ=
github.com/akyaiy/GoSally-mvp/logs v0.0.0-20250622141207-5326dd45b694 h1:qYZIzX3NczqozwCnlLQ5M1vTLoCqIIF1qxxweub4zQo=

View File

@@ -1,133 +0,0 @@
package sv1
import (
"encoding/json"
"log/slog"
"net/http"
"os"
"path/filepath"
"github.com/go-chi/chi/v5"
lua "github.com/yuin/gopher-lua"
)
func (h *HandlerV1) _handle() {
uuid16 := h.newUUID()
log := h.log.With(
slog.Group("request",
slog.String("version", h.GetVersion()),
slog.String("url", h.r.URL.String()),
slog.String("method", h.r.Method),
),
slog.Group("connection",
slog.String("connection-uuid", uuid16),
slog.String("remote", h.r.RemoteAddr),
),
)
log.Info("Received request")
cmd := chi.URLParam(h.r, "cmd")
var scriptPath string
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) {
log.Error("HTTP request error",
slog.String("error", "invalid command"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusBadRequest))
h.writeJSONError(http.StatusBadRequest, "invalid command")
return
}
if !h.listAllowedCmd.MatchString(cmd) {
log.Error("HTTP request error",
slog.String("error", "invalid command"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusBadRequest))
h.writeJSONError(http.StatusBadRequest, "invalid command")
return
}
if scriptPath = h.comMatch(chi.URLParam(h.r, "ver"), cmd); scriptPath == "" {
log.Error("HTTP request error",
slog.String("error", "command not found"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound))
h.writeJSONError(http.StatusNotFound, "command not found")
return
}
scriptPath = filepath.Join(h.cfg.ComDir, scriptPath)
if _, err := os.Stat(scriptPath); err != nil {
log.Error("HTTP request error",
slog.String("error", "command not found"),
slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound))
h.writeJSONError(http.StatusNotFound, "command not found")
return
}
L := lua.NewState()
defer L.Close()
L.OpenLibs() // loads base, io, os, string, math, table, debug, package, coroutine, channel… :contentReference[oaicite:0]{index=0}
qt := h.r.URL.Query()
tbl := L.NewTable()
for k, v := range qt {
if len(v) > 0 {
L.SetField(tbl, k, lua.LString(v[0]))
}
}
L.SetGlobal("Params", tbl)
L.SetGlobal("Result", L.NewTable())
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())
return
}
} else {
log.Error("No environment preparation script found, skipping preparation",
slog.String("error", err.Error()))
}
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())
return
}
out := make(map[string]any)
if rt := L.GetGlobal("Result"); rt.Type() == lua.LTTable {
rt.(*lua.LTable).ForEach(func(k, v lua.LValue) {
switch v.Type() {
case lua.LTString:
out[k.String()] = v.String()
case lua.LTNumber:
out[k.String()] = float64(v.(lua.LNumber))
case lua.LTBool:
out[k.String()] = bool(v.(lua.LBool))
default:
out[k.String()] = v.String()
}
})
}
h.w.Header().Set("Content-Type", "application/json")
json.NewEncoder(h.w).Encode(out)
switch out["status"] {
case "error":
log.Info("Command executed with error",
slog.String("cmd", cmd),
slog.Any("result", out))
case "ok":
log.Info("Command executed successfully",
slog.String("cmd", cmd), slog.Any("result", out))
default:
log.Info("Command executed and returned an unknown status",
slog.String("cmd", cmd),
slog.Any("result", out))
}
log.Info("Session completed")
}