mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-06 06:32:25 +00:00
Refactor server structure: migrate v1 functionality to sv1 module, remove deprecated files, and update command handling
This commit is contained in:
92
sv1/handle_com.go
Normal file
92
sv1/handle_com.go
Normal file
@@ -0,0 +1,92 @@
|
||||
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()
|
||||
h.log.Info("Received request", slog.String("version", "v1"), slog.String("connection-uuid", uuid16), slog.String("remote", h.r.RemoteAddr), slog.String("method", h.r.Method), slog.String("url", h.r.URL.String()))
|
||||
|
||||
cmd := chi.URLParam(h.r, "cmd")
|
||||
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) {
|
||||
h.writeJSONError(http.StatusBadRequest, "invalid command")
|
||||
h.log.Error("HTTP request error", slog.String("connection-uuid", uuid16), slog.String("error", "invalid command"), slog.String("cmd", cmd), slog.Int("status", http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
if !h.listAllowedCmd.MatchString(cmd) {
|
||||
h.writeJSONError(http.StatusBadRequest, "invalid command")
|
||||
h.log.Error("HTTP request error", slog.String("connection-uuid", uuid16), slog.String("error", "invalid command"), slog.String("cmd", cmd), slog.Int("status", http.StatusBadRequest))
|
||||
return
|
||||
}
|
||||
scriptPath := filepath.Join(h.cfg.ComDir, cmd+".lua")
|
||||
if _, err := os.Stat(scriptPath); err != nil {
|
||||
h.writeJSONError(http.StatusNotFound, "command not found")
|
||||
h.log.Error("HTTP request error", slog.String("connection-uuid", uuid16), slog.String("error", "command not found"), slog.String("cmd", cmd), slog.Int("status", http.StatusNotFound))
|
||||
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())
|
||||
|
||||
L.DoString(`
|
||||
print = function() end
|
||||
io.write = function(...) end
|
||||
io.stdout = function() return nil end
|
||||
io.stderr = function() return nil end
|
||||
io.read = function(...) return nil end
|
||||
`)
|
||||
|
||||
if err := L.DoFile(scriptPath); err != nil {
|
||||
h.writeJSONError(http.StatusInternalServerError, "lua error: "+err.Error())
|
||||
h.log.Error("Failed to execute lua script", slog.String("connection-uuid", uuid16), slog.String("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":
|
||||
h.log.Info("Command executed with error", slog.String("connection-uuid", uuid16), slog.String("cmd", cmd), slog.Any("result", out))
|
||||
case "ok":
|
||||
h.log.Info("Command executed successfully", slog.String("connection-uuid", uuid16), slog.String("cmd", cmd), slog.Any("result", out))
|
||||
default:
|
||||
h.log.Info("Command executed and returned an unknown status", slog.String("connection-uuid", uuid16), slog.String("cmd", cmd), slog.Any("result", out))
|
||||
}
|
||||
h.log.Info("Session completed", slog.String("connection-uuid", uuid16), slog.String("remote", h.r.RemoteAddr), slog.String("method", h.r.Method), slog.String("url", h.r.URL.String()))
|
||||
}
|
||||
55
sv1/handle_list.go
Normal file
55
sv1/handle_list.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) _handleList() {
|
||||
uuid16 := h.newUUID()
|
||||
h.log.Info("Received request", slog.String("version", "v1"), slog.String("connection-uuid", uuid16), slog.String("remote", h.r.RemoteAddr), slog.String("method", h.r.Method), slog.String("url", h.r.URL.String()))
|
||||
type ComMeta struct {
|
||||
Description string
|
||||
}
|
||||
var (
|
||||
files []os.DirEntry
|
||||
err error
|
||||
com ComMeta
|
||||
commands = make(map[string]ComMeta)
|
||||
)
|
||||
|
||||
if files, err = os.ReadDir(h.cfg.ComDir); err != nil {
|
||||
h.writeJSONError(http.StatusInternalServerError, "failed to read commands directory: "+err.Error())
|
||||
h.log.Error("Failed to read commands directory", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.IsDir() || filepath.Ext(file.Name()) != ".lua" {
|
||||
continue
|
||||
}
|
||||
cmdName := file.Name()[:len(file.Name())-4] // remove .lua extension
|
||||
if !h.allowedCmd.MatchString(string([]rune(cmdName)[0])) {
|
||||
continue
|
||||
}
|
||||
if !h.listAllowedCmd.MatchString(cmdName) {
|
||||
continue
|
||||
}
|
||||
if com.Description, err = h.extractDescriptionStatic(filepath.Join(h.cfg.ComDir, file.Name())); err != nil {
|
||||
h.writeJSONError(http.StatusInternalServerError, "failed to read command: "+err.Error())
|
||||
h.log.Error("Failed to read command", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
if com.Description == "" {
|
||||
com.Description = "description missing"
|
||||
}
|
||||
commands[cmdName] = ComMeta{Description: com.Description}
|
||||
}
|
||||
json.NewEncoder(h.w).Encode(commands)
|
||||
h.log.Info("Command executed successfully", slog.String("connection-uuid", uuid16))
|
||||
h.log.Info("Session completed", slog.String("connection-uuid", uuid16), slog.String("remote", h.r.RemoteAddr), slog.String("method", h.r.Method), slog.String("url", h.r.URL.String()))
|
||||
}
|
||||
69
sv1/server.go
Normal file
69
sv1/server.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/config"
|
||||
)
|
||||
|
||||
type ServerV1UtilsContract interface {
|
||||
extractDescriptionStatic(path string) (string, error)
|
||||
writeJSONError(status int, msg string)
|
||||
newUUID() string
|
||||
|
||||
_errNotFound()
|
||||
ErrNotFound(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type ServerV1Contract interface {
|
||||
ServerV1UtilsContract
|
||||
|
||||
Handle(w http.ResponseWriter, r *http.Request)
|
||||
HandleList(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
_handle()
|
||||
_handleList()
|
||||
}
|
||||
|
||||
// structure only for initialization
|
||||
type HandlerV1InitStruct struct {
|
||||
Log slog.Logger
|
||||
Config *config.ConfigConf
|
||||
AllowedCmd *regexp.Regexp
|
||||
ListAllowedCmd *regexp.Regexp
|
||||
}
|
||||
|
||||
type HandlerV1 struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
|
||||
log slog.Logger
|
||||
|
||||
cfg *config.ConfigConf
|
||||
|
||||
allowedCmd *regexp.Regexp
|
||||
listAllowedCmd *regexp.Regexp
|
||||
}
|
||||
|
||||
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||
return &HandlerV1{
|
||||
log: o.Log,
|
||||
cfg: o.Config,
|
||||
allowedCmd: o.AllowedCmd,
|
||||
listAllowedCmd: o.ListAllowedCmd,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
57
sv1/utils.go
Normal file
57
sv1/utils.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
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, 16)
|
||||
_, 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")
|
||||
h.log.Error("HTTP request error", slog.String("remote", h.r.RemoteAddr), slog.String("method", h.r.Method), slog.String("url", h.r.URL.String()), 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,
|
||||
}
|
||||
json.NewEncoder(h.w).Encode(resp)
|
||||
}
|
||||
|
||||
func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`---\s*#description\s*=\s*"([^"]+)"`)
|
||||
m := re.FindStringSubmatch(string(data))
|
||||
if len(m) <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
return m[1], nil
|
||||
}
|
||||
Reference in New Issue
Block a user