mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 20:52:25 +00:00
Compare commits
12 Commits
bf5e136dc9
...
92c89996f5
| Author | SHA1 | Date | |
|---|---|---|---|
| 92c89996f5 | |||
| 1c73d3f87a | |||
| e35972b8ad | |||
| 0344d58ad4 | |||
| cf7bd1ceec | |||
| c3540bfbe1 | |||
| bd54628b5c | |||
| b103736a9d | |||
| 7eeedf0b31 | |||
| 1675001f24 | |||
| e01ecdf1db | |||
| febee7cac5 |
35
cmd/run.go
35
cmd/run.go
@@ -231,8 +231,7 @@ var runCmd = &cobra.Command{
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
Log: *x.SLog,
|
||||
Config: x.Config.Conf,
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+$`),
|
||||
ListAllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
|
||||
Ver: "v1",
|
||||
})
|
||||
|
||||
@@ -264,7 +263,24 @@ var runCmd = &cobra.Command{
|
||||
Level: logs.GlobalLevel,
|
||||
}, "", 0),
|
||||
}
|
||||
|
||||
nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if err := srv.Shutdown(ctxMain); err != nil {
|
||||
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
|
||||
} else {
|
||||
x.Log.Printf("Server stopped gracefully")
|
||||
}
|
||||
|
||||
x.Log.Println("Cleaning up...")
|
||||
|
||||
if err := run_manager.Clean(); err != nil {
|
||||
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
|
||||
}
|
||||
x.Log.Println("bye!")
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer utils.CatchPanicWithCancel(cancelMain)
|
||||
if x.Config.Conf.TLS.TlsEnabled {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
||||
if err != nil {
|
||||
@@ -296,6 +312,7 @@ var runCmd = &cobra.Command{
|
||||
|
||||
if x.Config.Conf.Updates.UpdatesEnabled {
|
||||
go func() {
|
||||
defer utils.CatchPanicWithCancel(cancelMain)
|
||||
x.Updated = update.NewUpdater(ctxMain, x.Log, x.Config.Conf, x.Config.Env)
|
||||
x.Updated.Shutdownfunc(cancelMain)
|
||||
for {
|
||||
@@ -316,19 +333,7 @@ var runCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
<-ctxMain.Done()
|
||||
if err := srv.Shutdown(ctxMain); err != nil {
|
||||
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
|
||||
} else {
|
||||
x.Log.Printf("Server stopped gracefully")
|
||||
}
|
||||
|
||||
x.Log.Println("Cleaning up...")
|
||||
|
||||
if err := run_manager.Clean(); err != nil {
|
||||
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
|
||||
}
|
||||
x.Log.Println("bye!")
|
||||
|
||||
nodeApp.CallFallback(ctx)
|
||||
return nil
|
||||
})
|
||||
},
|
||||
|
||||
8
com/Utils/Echo.lua
Normal file
8
com/Utils/Echo.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
if not In.Params.msg or In.Params.msg == "" then
|
||||
Out.Error = {
|
||||
message = "there must be a msg parameter"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
Out.Result.answer = In.Params.msg
|
||||
1
com/Utils/Ping.lua
Normal file
1
com/Utils/Ping.lua
Normal file
@@ -0,0 +1 @@
|
||||
Out.Result.answer = "pong"
|
||||
13
com/echo.lua
13
com/echo.lua
@@ -1,13 +0,0 @@
|
||||
--- #description = "Echoes back the message."
|
||||
--- #args
|
||||
--- msg = the message
|
||||
|
||||
if not In.Params.msg or In.Params.msg == "" then
|
||||
Out.Result.status = Status.error
|
||||
Out.Result.error = "Missing parameter: msg"
|
||||
return
|
||||
end
|
||||
|
||||
Out.Result.status = Status.ok
|
||||
Out.Result.answer = In.Params.msg
|
||||
return
|
||||
60
internal/core/utils/panic.go
Normal file
60
internal/core/utils/panic.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// temportary solution, pls dont judge
|
||||
func trimStackPaths(stack []byte, folderName string) []byte {
|
||||
lines := strings.Split(string(stack), "\n")
|
||||
for i, line := range lines {
|
||||
idx := strings.Index(line, folderName)
|
||||
if idx != -1 {
|
||||
indentEnd := strings.LastIndex(line[:idx], "\t")
|
||||
if indentEnd == -1 {
|
||||
indentEnd = 0
|
||||
} else {
|
||||
indentEnd++
|
||||
}
|
||||
start := idx + len(folderName) + 1
|
||||
if start > len(line) {
|
||||
start = len(line)
|
||||
}
|
||||
lines[i] = line[:indentEnd] + line[start:]
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func CatchPanic() {
|
||||
if err := recover(); err != nil {
|
||||
stack := make([]byte, 8096)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
stack = trimStackPaths(stack, "GoSally-mvp")
|
||||
log.Printf("recovered panic:\n%s", stack)
|
||||
}
|
||||
}
|
||||
|
||||
func CatchPanicWithCancel(cancel context.CancelFunc) {
|
||||
if err := recover(); err != nil {
|
||||
stack := make([]byte, 8096)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
stack = trimStackPaths(stack, "GoSally-mvp")
|
||||
log.Printf("recovered panic:\n%s", stack)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func CatchPanicWithFallback(onPanic func(any)) {
|
||||
if err := recover(); err != nil {
|
||||
stack := make([]byte, 8096)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
stack = trimStackPaths(stack, "GoSally-mvp")
|
||||
log.Printf("recovered panic:\n%s", stack)
|
||||
onPanic(err)
|
||||
}
|
||||
}
|
||||
9
internal/core/utils/safe_fetch.go
Normal file
9
internal/core/utils/safe_fetch.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
// SafeFetch safely fetches data. If v = nil, a fallback value is returned.
|
||||
func SafeFetch[T any](v *T, fallback T) T {
|
||||
if v == nil {
|
||||
return fallback
|
||||
}
|
||||
return *v
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
@@ -16,14 +17,20 @@ import (
|
||||
type AppContract interface {
|
||||
InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX))
|
||||
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
|
||||
Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX))
|
||||
|
||||
CallFallback(ctx context.Context)
|
||||
}
|
||||
|
||||
type App struct {
|
||||
initHooks []func(cs *corestate.CoreState, x *AppX)
|
||||
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
|
||||
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||
|
||||
Corestate *corestate.CoreState
|
||||
AppX *AppX
|
||||
|
||||
fallbackOnce sync.Once
|
||||
}
|
||||
|
||||
type AppX struct {
|
||||
@@ -46,6 +53,10 @@ func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) {
|
||||
a.initHooks = append(a.initHooks, fn...)
|
||||
}
|
||||
|
||||
func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
|
||||
a.fallback = fn
|
||||
}
|
||||
|
||||
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
|
||||
a.runHook = fn
|
||||
|
||||
@@ -56,9 +67,30 @@ func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
defer stop()
|
||||
|
||||
if a.runHook != nil {
|
||||
if err := a.runHook(ctx, a.Corestate, a.AppX); err != nil {
|
||||
log.Fatalf("fatal in Run: %v", err)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.AppX.Log.Printf("PANIC recovered: %v", r)
|
||||
if a.fallback != nil {
|
||||
a.fallback(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
var runErr error
|
||||
if a.runHook != nil {
|
||||
runErr = a.runHook(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
|
||||
if runErr != nil {
|
||||
a.AppX.Log.Fatalf("fatal in Run: %v", runErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) CallFallback(ctx context.Context) {
|
||||
a.fallbackOnce.Do(func() {
|
||||
if a.fallback != nil {
|
||||
a.fallback(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
)
|
||||
|
||||
@@ -75,7 +76,11 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
|
||||
defer utils.CatchPanicWithFallback(func(rec any) {
|
||||
gs.log.Error("panic caught in handler", slog.Any("error", rec))
|
||||
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", req.ID)
|
||||
})
|
||||
if req.JSONRPC != rpc.JSONRPCVersion {
|
||||
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
||||
return rpc.NewError(rpc.ErrInvalidRequest, rpc.ErrInvalidRequestS, req.ID)
|
||||
@@ -87,7 +92,7 @@ func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) *rpc.RPCRes
|
||||
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, req.ID)
|
||||
}
|
||||
|
||||
resp := server.Handle(r, req)
|
||||
resp = server.Handle(r, req)
|
||||
// checks if request is notification
|
||||
if req.ID == nil {
|
||||
return nil
|
||||
|
||||
@@ -18,4 +18,10 @@ const (
|
||||
|
||||
ErrContextVersion = -32010
|
||||
ErrContextVersionS = "Invalid context version"
|
||||
|
||||
ErrInvalidMethodFormat = -32020
|
||||
ErrInvalidMethodFormatS = "Invalid method format"
|
||||
|
||||
ErrMethodIsMissing = -32020
|
||||
ErrMethodIsMissingS = "Method is missing"
|
||||
)
|
||||
|
||||
@@ -1,342 +1,97 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
return rpc.NewResponse("Hi", req.ID) // test answer to make sure everything works
|
||||
}
|
||||
|
||||
// func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
// var req PettiRequest
|
||||
// // 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
|
||||
// // }
|
||||
// if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "невалидный JSON: "+err.Error())
|
||||
// return
|
||||
// }
|
||||
|
||||
// if req.PettiVer == "" {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PettiVer")
|
||||
// return
|
||||
// }
|
||||
// if req.PettiVer != h.GetVersion() {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "неподдерживаемая версия PettiVer")
|
||||
// return
|
||||
// }
|
||||
// if req.PackageType.Request == "" {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PackageType.Request")
|
||||
// return
|
||||
// }
|
||||
// if req.Payload == nil {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует Payload")
|
||||
// return
|
||||
// }
|
||||
// cmdRaw, ok := req.Payload["Exec"].(string)
|
||||
// if !ok || cmdRaw == "" {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "Payload.Exec отсутствует или некорректен")
|
||||
// return
|
||||
// }
|
||||
// cmd := cmdRaw
|
||||
|
||||
// if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "команда запрещена")
|
||||
// return
|
||||
// }
|
||||
|
||||
// // ===== Проверка скрипта
|
||||
// scriptPath := h.comMatch(h.GetVersion(), cmd)
|
||||
// if scriptPath == "" {
|
||||
// utils.WriteJSONError(w, http.StatusNotFound, "команда не найдена")
|
||||
// return
|
||||
// }
|
||||
// fullPath := filepath.Join(h.cfg.ComDir, scriptPath)
|
||||
// if _, err := os.Stat(fullPath); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusNotFound, "файл команды не найден")
|
||||
// return
|
||||
// }
|
||||
|
||||
// // ===== Запуск Lua
|
||||
// L := lua.NewState()
|
||||
// defer L.Close()
|
||||
|
||||
// inTable := L.NewTable()
|
||||
// paramsTable := L.NewTable()
|
||||
// if params, ok := req.Payload["PassedParameters"].(map[string]interface{}); ok {
|
||||
// for k, v := range params {
|
||||
// L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
|
||||
// }
|
||||
// }
|
||||
// L.SetField(inTable, "Params", paramsTable)
|
||||
// L.SetGlobal("In", inTable)
|
||||
|
||||
// resultTable := L.NewTable()
|
||||
// outTable := L.NewTable()
|
||||
// L.SetField(outTable, "Result", resultTable)
|
||||
// L.SetGlobal("Out", outTable)
|
||||
|
||||
// prepareLua := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
||||
// if _, err := os.Stat(prepareLua); err == nil {
|
||||
// if err := L.DoFile(prepareLua); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "lua _prepare ошибка: "+err.Error())
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// if err := L.DoFile(fullPath); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "lua exec ошибка: "+err.Error())
|
||||
// return
|
||||
// }
|
||||
|
||||
// lv := L.GetGlobal("Out")
|
||||
// tbl, ok := lv.(*lua.LTable)
|
||||
// if !ok {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' не таблица")
|
||||
// return
|
||||
// }
|
||||
// resultVal := tbl.RawGetString("Result")
|
||||
// resultTbl, ok := resultVal.(*lua.LTable)
|
||||
// if !ok {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' не таблица")
|
||||
// return
|
||||
// }
|
||||
|
||||
// out := make(map[string]any)
|
||||
// resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
// out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||
// })
|
||||
|
||||
// uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
|
||||
|
||||
// resp := PettiResponse{
|
||||
// PettiVer: req.PettiVer,
|
||||
// ResponsibleAgentUUID: uuid32,
|
||||
// PackageType: struct {
|
||||
// AnswerOf string `json:"AnswerOf"`
|
||||
// }{AnswerOf: req.PackageType.Request},
|
||||
// Payload: map[string]any{
|
||||
// "RequestedCommand": cmd,
|
||||
// "Response": out,
|
||||
// },
|
||||
// }
|
||||
|
||||
// // ===== Финальная проверка на сериализацию (валидность сборки)
|
||||
// respData, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "внутренняя ошибка: пакет невалиден")
|
||||
// return
|
||||
// }
|
||||
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// if _, err := w.Write(respData); err != nil {
|
||||
// h.log.Error("Ошибка при отправке JSON", slog.String("err", err.Error()))
|
||||
// }
|
||||
|
||||
// // ===== Логгирование статуса
|
||||
// status, _ := out["status"].(string)
|
||||
// switch status {
|
||||
// case "ok":
|
||||
// h.log.Info("Успешно", slog.String("cmd", cmd), slog.Any("out", out))
|
||||
// case "error":
|
||||
// h.log.Warn("Ошибка в команде", slog.String("cmd", cmd), slog.Any("out", out))
|
||||
// default:
|
||||
// h.log.Info("Неизвестный статус", slog.String("cmd", cmd), slog.Any("out", out))
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// 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(int(config.UUIDLength))
|
||||
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
if req.Method == "" {
|
||||
h.log.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, req.ID)
|
||||
}
|
||||
|
||||
method, err := h.resolveMethodPath(req.Method)
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
slog.String("error", err.Error()))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error()); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
||||
h.log.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
|
||||
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, req.ID)
|
||||
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
||||
h.log.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||
return rpc.NewError(rpc.ErrMethodNotFound, rpc.ErrMethodNotFoundS, req.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
log := h.log.With(
|
||||
slog.Group("request",
|
||||
slog.String("version", h.GetVersion()),
|
||||
slog.String("url", r.URL.String()),
|
||||
slog.String("method", r.Method),
|
||||
),
|
||||
slog.Group("connection",
|
||||
slog.String("connection-uuid", uuid16),
|
||||
slog.String("remote", r.RemoteAddr),
|
||||
),
|
||||
)
|
||||
log.Info("Received request")
|
||||
|
||||
cmd := chi.URLParam(r, "cmd")
|
||||
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "invalid command"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusBadRequest, "invalid command"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
scriptPath := h.comMatch(chi.URLParam(r, "ver"), cmd)
|
||||
if scriptPath == "" {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusNotFound, "command not found"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
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))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusNotFound, "command not found"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
return h.HandleLUA(method, req)
|
||||
}
|
||||
|
||||
func (h *HandlerV1) HandleLUA(path string, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
paramsTable := L.NewTable()
|
||||
qt := r.URL.Query()
|
||||
for k, v := range qt {
|
||||
if len(v) > 0 {
|
||||
L.SetField(paramsTable, k, lua.LString(v[0]))
|
||||
}
|
||||
}
|
||||
inTable := L.NewTable()
|
||||
paramsTable := L.NewTable()
|
||||
if fetchedParams, ok := req.Params.(map[string]interface{}); ok {
|
||||
for k, v := range fetchedParams {
|
||||
L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
|
||||
}
|
||||
}
|
||||
L.SetField(inTable, "Params", paramsTable)
|
||||
L.SetGlobal("In", inTable)
|
||||
|
||||
// Создаем таблицу Out с Result
|
||||
resultTable := L.NewTable()
|
||||
outTable := L.NewTable()
|
||||
resultTable := L.NewTable()
|
||||
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()))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error()); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
prep := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
||||
if _, err := os.Stat(prep); err == nil {
|
||||
if err := L.DoFile(prep); err != nil {
|
||||
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Warn("No environment preparation script found, skipping preparation")
|
||||
}
|
||||
|
||||
if err := L.DoFile(scriptPath); err != nil {
|
||||
log.Error("Failed to execute lua script",
|
||||
slog.String("error", err.Error()))
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error()); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
if err := L.DoFile(path); err != nil {
|
||||
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
||||
}
|
||||
|
||||
lv := L.GetGlobal("Out")
|
||||
tbl, ok := lv.(*lua.LTable)
|
||||
outTbl, ok := lv.(*lua.LTable)
|
||||
if !ok {
|
||||
log.Error("Lua global 'Out' is not a table")
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' is not a table"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID)
|
||||
}
|
||||
|
||||
resultVal := tbl.RawGetString("Result")
|
||||
// Check if Out.Error exists
|
||||
if errVal := outTbl.RawGetString("Error"); errVal != lua.LNil {
|
||||
if errTbl, ok := errVal.(*lua.LTable); ok {
|
||||
code := rpc.ErrInternalError
|
||||
message := "Internal script error"
|
||||
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
||||
code = int(c.(lua.LNumber))
|
||||
}
|
||||
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
||||
message = msg.String()
|
||||
}
|
||||
return rpc.NewError(code, message, req.ID)
|
||||
}
|
||||
return rpc.NewError(rpc.ErrInternalError, "Out.Error is not a table", req.ID)
|
||||
}
|
||||
|
||||
// Otherwise, parse Out.Result
|
||||
resultVal := outTbl.RawGetString("Result")
|
||||
resultTbl, ok := resultVal.(*lua.LTable)
|
||||
if !ok {
|
||||
log.Error("Lua global 'Result' is not a table")
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' is not a table"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID)
|
||||
}
|
||||
|
||||
out := make(map[string]any)
|
||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||
})
|
||||
uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
|
||||
response := ResponseFormat{
|
||||
ResponsibleAgentUUID: uuid32,
|
||||
RequestedCommand: cmd,
|
||||
Response: out,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
log.Error("Failed to encode JSON response",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
status, _ := out["status"].(string)
|
||||
switch 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")
|
||||
return rpc.NewResponse(out, req.ID)
|
||||
}
|
||||
*/
|
||||
|
||||
26
internal/server/sv1/path.go
Normal file
26
internal/server/sv1/path.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) resolveMethodPath(method string) (string, error) {
|
||||
if !h.allowedCmd.MatchString(method) {
|
||||
return "", errors.New(rpc.ErrInvalidMethodFormatS)
|
||||
}
|
||||
|
||||
parts := strings.Split(method, ">")
|
||||
relPath := filepath.Join(parts...) + ".lua"
|
||||
fullPath := filepath.Join(h.cfg.ComDir, relPath)
|
||||
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
return "", errors.New(rpc.ErrMethodNotFoundS)
|
||||
}
|
||||
|
||||
return fullPath, nil
|
||||
}
|
||||
@@ -26,7 +26,6 @@ type HandlerV1 struct {
|
||||
|
||||
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
|
||||
allowedCmd *regexp.Regexp
|
||||
listAllowedCmd *regexp.Regexp
|
||||
|
||||
ver string
|
||||
}
|
||||
@@ -39,7 +38,6 @@ func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||
log: &o.Log,
|
||||
cfg: o.Config,
|
||||
allowedCmd: o.AllowedCmd,
|
||||
listAllowedCmd: o.ListAllowedCmd,
|
||||
ver: o.Ver,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) comMatch(ver string, comName string) string {
|
||||
files, err := os.ReadDir(h.cfg.ComDir)
|
||||
if err != nil {
|
||||
h.log.Error("Failed to read com dir",
|
||||
slog.String("error", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
baseName := comName + ".lua"
|
||||
verName := comName + "?" + ver + ".lua"
|
||||
|
||||
var baseFileFound string
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
fname := f.Name()
|
||||
|
||||
if fname == verName {
|
||||
return fname
|
||||
}
|
||||
|
||||
if fname == baseName {
|
||||
baseFileFound = fname
|
||||
}
|
||||
}
|
||||
|
||||
return baseFileFound
|
||||
}
|
||||
Reference in New Issue
Block a user