mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 21:12: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{
|
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||||
Log: *x.SLog,
|
Log: *x.SLog,
|
||||||
Config: x.Config.Conf,
|
Config: x.Config.Conf,
|
||||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+$`),
|
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
|
||||||
ListAllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
|
|
||||||
Ver: "v1",
|
Ver: "v1",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -264,7 +263,24 @@ var runCmd = &cobra.Command{
|
|||||||
Level: logs.GlobalLevel,
|
Level: logs.GlobalLevel,
|
||||||
}, "", 0),
|
}, "", 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() {
|
go func() {
|
||||||
|
defer utils.CatchPanicWithCancel(cancelMain)
|
||||||
if x.Config.Conf.TLS.TlsEnabled {
|
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))
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -296,6 +312,7 @@ var runCmd = &cobra.Command{
|
|||||||
|
|
||||||
if x.Config.Conf.Updates.UpdatesEnabled {
|
if x.Config.Conf.Updates.UpdatesEnabled {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer utils.CatchPanicWithCancel(cancelMain)
|
||||||
x.Updated = update.NewUpdater(ctxMain, x.Log, x.Config.Conf, x.Config.Env)
|
x.Updated = update.NewUpdater(ctxMain, x.Log, x.Config.Conf, x.Config.Env)
|
||||||
x.Updated.Shutdownfunc(cancelMain)
|
x.Updated.Shutdownfunc(cancelMain)
|
||||||
for {
|
for {
|
||||||
@@ -316,19 +333,7 @@ var runCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-ctxMain.Done()
|
<-ctxMain.Done()
|
||||||
if err := srv.Shutdown(ctxMain); err != nil {
|
nodeApp.CallFallback(ctx)
|
||||||
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!")
|
|
||||||
|
|
||||||
return nil
|
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"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
@@ -16,14 +17,20 @@ import (
|
|||||||
type AppContract interface {
|
type AppContract interface {
|
||||||
InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX))
|
InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX))
|
||||||
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
|
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 {
|
type App struct {
|
||||||
initHooks []func(cs *corestate.CoreState, x *AppX)
|
initHooks []func(cs *corestate.CoreState, x *AppX)
|
||||||
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
|
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
|
||||||
|
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||||
|
|
||||||
Corestate *corestate.CoreState
|
Corestate *corestate.CoreState
|
||||||
AppX *AppX
|
AppX *AppX
|
||||||
|
|
||||||
|
fallbackOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppX struct {
|
type AppX struct {
|
||||||
@@ -46,6 +53,10 @@ func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) {
|
|||||||
a.initHooks = append(a.initHooks, fn...)
|
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) {
|
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
|
||||||
a.runHook = fn
|
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)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
if a.runHook != nil {
|
defer func() {
|
||||||
if err := a.runHook(ctx, a.Corestate, a.AppX); err != nil {
|
if r := recover(); r != nil {
|
||||||
log.Fatalf("fatal in Run: %v", err)
|
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"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"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 {
|
if req.JSONRPC != rpc.JSONRPCVersion {
|
||||||
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
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)
|
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)
|
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := server.Handle(r, req)
|
resp = server.Handle(r, req)
|
||||||
// checks if request is notification
|
// checks if request is notification
|
||||||
if req.ID == nil {
|
if req.ID == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -18,4 +18,10 @@ const (
|
|||||||
|
|
||||||
ErrContextVersion = -32010
|
ErrContextVersion = -32010
|
||||||
ErrContextVersionS = "Invalid context version"
|
ErrContextVersionS = "Invalid context version"
|
||||||
|
|
||||||
|
ErrInvalidMethodFormat = -32020
|
||||||
|
ErrInvalidMethodFormatS = "Invalid method format"
|
||||||
|
|
||||||
|
ErrMethodIsMissing = -32020
|
||||||
|
ErrMethodIsMissingS = "Method is missing"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,342 +1,97 @@
|
|||||||
package sv1
|
package sv1
|
||||||
|
|
||||||
import (
|
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"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandlerV1 is the main handler for version 1 of the API.
|
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
// The function processes the HTTP request and runs Lua scripts,
|
if req.Method == "" {
|
||||||
// preparing the environment and subsequently transmitting the execution result
|
h.log.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, req.ID)
|
||||||
uuid16, err := utils.NewUUID(int(config.UUIDLength))
|
}
|
||||||
|
|
||||||
|
method, err := h.resolveMethodPath(req.Method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.log.Error("Failed to generate UUID",
|
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
||||||
slog.String("error", err.Error()))
|
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)
|
||||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error()); err != nil {
|
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
||||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
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)
|
return h.HandleLUA(method, req)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (h *HandlerV1) HandleLUA(path string, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
L := lua.NewState()
|
L := lua.NewState()
|
||||||
defer L.Close()
|
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()
|
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.SetField(inTable, "Params", paramsTable)
|
||||||
L.SetGlobal("In", inTable)
|
L.SetGlobal("In", inTable)
|
||||||
|
|
||||||
// Создаем таблицу Out с Result
|
|
||||||
resultTable := L.NewTable()
|
|
||||||
outTable := L.NewTable()
|
outTable := L.NewTable()
|
||||||
|
resultTable := L.NewTable()
|
||||||
L.SetField(outTable, "Result", resultTable)
|
L.SetField(outTable, "Result", resultTable)
|
||||||
L.SetGlobal("Out", outTable)
|
L.SetGlobal("Out", outTable)
|
||||||
|
|
||||||
prepareLuaEnv := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
prep := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
||||||
if _, err := os.Stat(prepareLuaEnv); err == nil {
|
if _, err := os.Stat(prep); err == nil {
|
||||||
if err := L.DoFile(prepareLuaEnv); err != nil {
|
if err := L.DoFile(prep); err != nil {
|
||||||
log.Error("Failed to prepare lua environment",
|
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
} else {
|
if err := L.DoFile(path); err != nil {
|
||||||
log.Warn("No environment preparation script found, skipping preparation")
|
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lv := L.GetGlobal("Out")
|
lv := L.GetGlobal("Out")
|
||||||
tbl, ok := lv.(*lua.LTable)
|
outTbl, ok := lv.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error("Lua global 'Out' is not a table")
|
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
resultTbl, ok := resultVal.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error("Lua global 'Result' is not a table")
|
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make(map[string]any)
|
out := make(map[string]any)
|
||||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||||
})
|
})
|
||||||
uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
|
return rpc.NewResponse(out, req.ID)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
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 and listAllowedCmd are regular expressions used to validate command names.
|
||||||
allowedCmd *regexp.Regexp
|
allowedCmd *regexp.Regexp
|
||||||
listAllowedCmd *regexp.Regexp
|
|
||||||
|
|
||||||
ver string
|
ver string
|
||||||
}
|
}
|
||||||
@@ -39,7 +38,6 @@ func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
|||||||
log: &o.Log,
|
log: &o.Log,
|
||||||
cfg: o.Config,
|
cfg: o.Config,
|
||||||
allowedCmd: o.AllowedCmd,
|
allowedCmd: o.AllowedCmd,
|
||||||
listAllowedCmd: o.ListAllowedCmd,
|
|
||||||
ver: o.Ver,
|
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