mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 19:32:26 +00:00
Compare commits
24 Commits
9a274250cd
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c9e5102f | ||
| a72627d87c | |||
| 4a9719cdfb | |||
| 7de5ec5248 | |||
| e5f9105364 | |||
| ce2a23f9e6 | |||
| d56b022bf5 | |||
| ca38c10ec4 | |||
| 13dbd00bb7 | |||
| e7289dc9be | |||
| 5394178abc | |||
| 981551e944 | |||
| 27446adf3f | |||
| 2f071c25b2 | |||
| d23fd32e84 | |||
| 86d35a9ede | |||
| c77d51a95c | |||
| 3cbea14e84 | |||
| 6e59af1662 | |||
| 8684d178e0 | |||
| 945ab6c9cf | |||
| 520901c331 | |||
| f3c4b9e9b1 | |||
|
|
81359c036c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@ tmp/
|
|||||||
.meta/
|
.meta/
|
||||||
db/
|
db/
|
||||||
|
|
||||||
|
com/test.lua
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
Taskfile.yml
|
Taskfile.yml
|
||||||
config.yaml
|
config.yaml
|
||||||
66
com/DB/Put.lua
Normal file
66
com/DB/Put.lua
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---@diagnostic disable: redefined-local
|
||||||
|
local db = require("internal.database-sqlite").connect("db/test.db", {log = true})
|
||||||
|
local log = require("internal.log")
|
||||||
|
local session = require("internal.session")
|
||||||
|
|
||||||
|
if not (session.request.params.name and session.request.params.email) then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32602,
|
||||||
|
message = "Name and email are required"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local existing, err = db:query("SELECT 1 FROM users WHERE email = ? LIMIT 1", {
|
||||||
|
session.request.params.email
|
||||||
|
})
|
||||||
|
if err ~= nil then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32603,
|
||||||
|
message = "Database check failed: "..tostring(err)
|
||||||
|
}
|
||||||
|
log.error("Email check failed: "..tostring(err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if existing and #existing > 0 then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32604,
|
||||||
|
message = "Email already exists"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ctx, err = db:exec(
|
||||||
|
"INSERT INTO users (name, email) VALUES (?, ?)",
|
||||||
|
{
|
||||||
|
session.request.params.name,
|
||||||
|
session.request.params.email
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if err ~= nil then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32605,
|
||||||
|
message = "Insert failed: "..tostring(err)
|
||||||
|
}
|
||||||
|
log.error("Insert failed: "..tostring(err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local res, err = ctx:wait()
|
||||||
|
if err ~= nil then
|
||||||
|
session.response.error = {
|
||||||
|
code = -32606,
|
||||||
|
message = "Insert confirmation failed: "..tostring(err)
|
||||||
|
}
|
||||||
|
log.error("Insert confirmation failed: "..tostring(err))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
session.response.result = {
|
||||||
|
success = true,
|
||||||
|
rows_affected = res,
|
||||||
|
message = "User created successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
db:close()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
-- com/List.lua
|
-- com/List.lua
|
||||||
|
|
||||||
local session = require("session")
|
local session = require("internal.session")
|
||||||
|
|
||||||
if session.request and session.request.params and session.request.params.about then
|
if session.request and session.request.params and session.request.params.about then
|
||||||
session.response.result = {
|
session.response.result = {
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
|
local session = require("internal.session")
|
||||||
|
local net = require("internal.net")
|
||||||
|
|
||||||
local reqAddr
|
local reqAddr
|
||||||
local logReq = true
|
local logReq = true
|
||||||
|
|
||||||
if In.Params and In.Params.url then
|
if session.request.params and session.request.params.url then
|
||||||
reqAddr = In.Params.url
|
reqAddr = session.request.params.url
|
||||||
else
|
else
|
||||||
Out.Error = {
|
session.response.error = {
|
||||||
code = -32602,
|
code = -32602,
|
||||||
message = "no url provided"
|
message = "no url provided"
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local resp = Net.Http.Get(logReq, reqAddr)
|
local resp = net.http.get_request(logReq, reqAddr)
|
||||||
if resp then
|
if resp then
|
||||||
Out.Result.answer = {
|
session.response.result.answer = {
|
||||||
status = resp.status,
|
status = resp.status,
|
||||||
body = resp.body
|
body = resp.body
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Out.Result.answer = {
|
session.response.error = {
|
||||||
status = resp.status
|
data = "error while requesting"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,35 @@
|
|||||||
|
local session = require("internal.session")
|
||||||
|
local net = require("internal.net")
|
||||||
|
local log = require("internal.log")
|
||||||
|
|
||||||
local reqAddr
|
local reqAddr
|
||||||
local logReq = true
|
local logReq = true
|
||||||
local payload
|
local payload
|
||||||
|
|
||||||
if not In.Params and In.Params.url or not In.Params.payload then
|
log.debug(session.request.params)
|
||||||
Out.Error = {
|
|
||||||
|
if not (session.request.params and session.request.params.url) then
|
||||||
|
session.response.error = {
|
||||||
code = -32602,
|
code = -32602,
|
||||||
message = "no url or payload provided"
|
message = "no url or payload provided"
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
reqAddr = In.Params.url
|
|
||||||
payload = In.Params.payload
|
|
||||||
|
|
||||||
local resp = Net.Http.Post(logReq, reqAddr, "application/json", payload)
|
|
||||||
|
reqAddr = session.request.params.url
|
||||||
|
payload = session.request.params.payload
|
||||||
|
|
||||||
|
local resp = net.http.post_request(logReq, reqAddr, "application/json", payload)
|
||||||
if resp then
|
if resp then
|
||||||
Out.Result.answer = {
|
session.response.result.answer = {
|
||||||
status = resp.status,
|
status = resp.status,
|
||||||
body = resp.body
|
body = resp.body
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Out.Result.answer = {
|
session.response.error = {
|
||||||
status = resp.status
|
data = "error while requesting"
|
||||||
}
|
}
|
||||||
@@ -4,44 +4,51 @@
|
|||||||
---@alias AnyTable table<string, Any>
|
---@alias AnyTable table<string, Any>
|
||||||
|
|
||||||
--- Global session module interface
|
--- Global session module interface
|
||||||
|
---@class SessionIn
|
||||||
|
---@field params AnyTable Request parameters
|
||||||
|
|
||||||
|
---@class SessionOut
|
||||||
|
---@field result Any|string? Result payload (table or primitive)
|
||||||
|
---@field error { code: integer, message: string, data: Any }? Optional error info
|
||||||
|
|
||||||
---@class SessionModule
|
---@class SessionModule
|
||||||
---@field request AnyTable Input context (read-only)
|
---@field request SessionIn Input context (read-only)
|
||||||
---@field request.params AnyTable Request parameters
|
---@field response SessionOut Output context (write results/errors)
|
||||||
---@field response AnyTable Output context (write results/errors)
|
|
||||||
---@field response.result Any|string? Result payload (table or primitive)
|
|
||||||
---@field response.error { code: integer, message: string }? Optional error info
|
|
||||||
|
|
||||||
--- Global log module interface
|
--- Global log module interface
|
||||||
---@class LogModule
|
---@class LogModule
|
||||||
---@field info fun(msg: string) Log informational message
|
---@field info fun(msg: string) Log informational message
|
||||||
---@field debug fun(msg: string) Log debug message
|
---@field debug fun(msg: string) Log debug message
|
||||||
---@field error fun(msg: string) Log error message
|
---@field error fun(msg: string) Log error message
|
||||||
---@field warn fun(msg: string) Log warning message
|
---@field warn fun(msg: string) Log warning message
|
||||||
---@field event fun(msg: string) Log event (generic)
|
---@field event fun(msg: string) Log event (generic)
|
||||||
---@field event_error fun(msg: string) Log event error
|
---@field event_error fun(msg: string) Log event error
|
||||||
---@field event_warn fun(msg: string) Log event warning
|
---@field event_warn fun(msg: string) Log event warning
|
||||||
|
|
||||||
--- Global net module interface
|
--- Global net module interface
|
||||||
---@class HttpResponse
|
---@class HttpResponse
|
||||||
---@field status integer HTTP status code
|
---@field status integer HTTP status code
|
||||||
---@field status_text string HTTP status text
|
---@field status_text string HTTP status text
|
||||||
---@field body string Response body
|
---@field body string Response body
|
||||||
---@field content_length integer Content length
|
---@field content_length integer Content length
|
||||||
---@field headers AnyTable Map of headers
|
---@field headers AnyTable Map of headers
|
||||||
|
|
||||||
---@class HttpModule
|
---@class HttpModule
|
||||||
---@field get fun(log: boolean, url: string): HttpResponse, string? Perform GET
|
---@field get fun(log: boolean, url: string): HttpResponse, string? Perform GET
|
||||||
---@field post fun(log: boolean, url: string, content_type: string, payload: string): HttpResponse, string? Perform POST
|
---@field post fun(log: boolean, url: string, content_type: string, payload: string): HttpResponse, string? Perform POST
|
||||||
|
|
||||||
---@class NetModule
|
---@class NetModule
|
||||||
---@field http HttpModule HTTP client functions
|
---@field http HttpModule HTTP client functions
|
||||||
|
|
||||||
--- Exposed globals
|
--- Global variables declaration
|
||||||
|
---@global
|
||||||
---@type SessionModule
|
---@type SessionModule
|
||||||
session = session or {}
|
_G.session = session or {}
|
||||||
|
|
||||||
|
---@global
|
||||||
---@type LogModule
|
---@type LogModule
|
||||||
log = log or {}
|
_G.log = log or {}
|
||||||
|
|
||||||
|
---@global
|
||||||
---@type NetModule
|
---@type NetModule
|
||||||
net = net or {}
|
_G.net = net or {}
|
||||||
@@ -1,21 +1,30 @@
|
|||||||
mode: "prod"
|
node:
|
||||||
|
mode: dev
|
||||||
|
name: "My gosally node"
|
||||||
|
show_config: true
|
||||||
|
com_dir: "%path%/com"
|
||||||
|
|
||||||
http_server:
|
http_server:
|
||||||
address: "0.0.0.0:8080"
|
address: "0.0.0.0"
|
||||||
api:
|
port: "8080"
|
||||||
latest-version: v1
|
session_ttl: 5s
|
||||||
layers:
|
timeout: 3s
|
||||||
- b1
|
idle_timeout: 30s
|
||||||
- s2
|
|
||||||
|
|
||||||
tls:
|
tls:
|
||||||
enabled: false
|
enabled: true
|
||||||
cert_file: "./cert/fullchain.pem"
|
cert_file: "%path%/cert/fullchain.pem"
|
||||||
key_file: "./cert/privkey.pem"
|
key_file: "%path%/cert/privkey.pem"
|
||||||
|
|
||||||
com_dir: "com/"
|
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
enabled: true
|
enabled: false
|
||||||
check-interval: 1h
|
check-interval: 1h
|
||||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||||
|
|
||||||
|
log:
|
||||||
|
json_format: false
|
||||||
|
level: "debug"
|
||||||
|
|
||||||
|
disable_warnings:
|
||||||
|
- --WNonStdTmpDir
|
||||||
|
- --WUndefLogLevel
|
||||||
@@ -193,8 +193,10 @@ func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX)
|
|||||||
replacements := map[string]any{
|
replacements := map[string]any{
|
||||||
"%tmp%": filepath.Clean(run_manager.RuntimeDir()),
|
"%tmp%": filepath.Clean(run_manager.RuntimeDir()),
|
||||||
"%path%": *x.Config.Env.NodePath,
|
"%path%": *x.Config.Env.NodePath,
|
||||||
"%stdout%": os.Stdout,
|
"%stdout%": "_1STDout",
|
||||||
"%stderr%": os.Stderr,
|
"%stderr%": "_2STDerr",
|
||||||
|
"%1%": "_1STDout",
|
||||||
|
"%2%": "_2STDerr",
|
||||||
}
|
}
|
||||||
|
|
||||||
processConfig(&x.Config.Conf, replacements)
|
processConfig(&x.Config.Conf, replacements)
|
||||||
@@ -301,10 +303,32 @@ func processConfig(conf any, replacements map[string]any) error {
|
|||||||
|
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
if !val.IsNil() {
|
if !val.IsNil() {
|
||||||
return processConfig(val.Interface(), replacements)
|
elem := val.Elem()
|
||||||
|
if elem.Kind() == reflect.String {
|
||||||
|
str := elem.String()
|
||||||
|
if replacement, exists := replacements[str]; exists {
|
||||||
|
strVal, err := toString(replacement)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot convert replacement to string: %v", err)
|
||||||
|
}
|
||||||
|
elem.SetString(strVal)
|
||||||
|
} else {
|
||||||
|
for placeholder, replacement := range replacements {
|
||||||
|
if strings.Contains(str, placeholder) {
|
||||||
|
replacementStr, err := toString(replacement)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid replacement for %q: %v", placeholder, err)
|
||||||
|
}
|
||||||
|
newStr := strings.ReplaceAll(str, placeholder, replacementStr)
|
||||||
|
elem.SetString(newStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return processConfig(elem.Addr().Interface(), replacements)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(cors.Handler(cors.Options{
|
r.Use(cors.Handler(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
AllowedMethods: []string{"POST"},
|
||||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Session-UUID"},
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
MaxAge: 300,
|
MaxAge: 300,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -58,8 +58,9 @@ func (c *Compositor) LoadConf(path string) error {
|
|||||||
v.SetDefault("updates.enabled", false)
|
v.SetDefault("updates.enabled", false)
|
||||||
v.SetDefault("updates.check_interval", "2h")
|
v.SetDefault("updates.check_interval", "2h")
|
||||||
v.SetDefault("updates.wanted_version", "latest-stable")
|
v.SetDefault("updates.wanted_version", "latest-stable")
|
||||||
|
v.SetDefault("log.json_format", "false")
|
||||||
v.SetDefault("log.level", "info")
|
v.SetDefault("log.level", "info")
|
||||||
v.SetDefault("log.output", "%stdout%")
|
v.SetDefault("log.output", "%2%")
|
||||||
|
|
||||||
if err := v.ReadInConfig(); err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
return fmt.Errorf("error reading config: %w", err)
|
return fmt.Errorf("error reading config: %w", err)
|
||||||
|
|||||||
@@ -55,8 +55,9 @@ type Updates struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
|
JSON *bool `mapstructure:"json_format"`
|
||||||
Level *string `mapstructure:"level"`
|
Level *string `mapstructure:"level"`
|
||||||
OutPath any `mapstructure:"output"`
|
OutPath *string `mapstructure:"output"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigEnv structure for environment variables
|
// ConfigEnv structure for environment variables
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package logs
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
@@ -58,30 +57,14 @@ func SetupLogger(o *config.Log) (*slog.Logger, error) {
|
|||||||
handlerOpts.Level = slog.LevelInfo
|
handlerOpts.Level = slog.LevelInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
switch o.OutPath {
|
switch *o.OutPath {
|
||||||
case 1:
|
case "_1STDout":
|
||||||
writer = os.Stdout
|
writer = os.Stdout
|
||||||
case 2:
|
case "_2STDerr":
|
||||||
writer = os.Stderr
|
|
||||||
case os.Stdout:
|
|
||||||
writer = os.Stdout
|
|
||||||
case os.Stderr:
|
|
||||||
writer = os.Stderr
|
writer = os.Stderr
|
||||||
default:
|
default:
|
||||||
var path string
|
|
||||||
switch v := o.OutPath.(type) {
|
|
||||||
case string:
|
|
||||||
path = v
|
|
||||||
case int, int64, float64:
|
|
||||||
path = fmt.Sprint(v)
|
|
||||||
case fmt.Stringer:
|
|
||||||
path = v.String()
|
|
||||||
default:
|
|
||||||
path = fmt.Sprint(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
logFile := &lumberjack.Logger{
|
logFile := &lumberjack.Logger{
|
||||||
Filename: filepath.Join(path, "event.log"),
|
Filename: filepath.Join(*o.OutPath, "event.log"),
|
||||||
MaxSize: 10,
|
MaxSize: 10,
|
||||||
MaxBackups: 5,
|
MaxBackups: 5,
|
||||||
MaxAge: 28,
|
MaxAge: 28,
|
||||||
@@ -90,6 +73,13 @@ func SetupLogger(o *config.Log) (*slog.Logger, error) {
|
|||||||
writer = logFile
|
writer = logFile
|
||||||
}
|
}
|
||||||
|
|
||||||
log := slog.New(slog.NewJSONHandler(writer, &handlerOpts))
|
var handler slog.Handler
|
||||||
|
|
||||||
|
if *o.JSON {
|
||||||
|
handler = slog.NewJSONHandler(writer, &handlerOpts)
|
||||||
|
} else {
|
||||||
|
handler = slog.NewTextHandler(writer, &handlerOpts)
|
||||||
|
}
|
||||||
|
log := slog.New(handler)
|
||||||
return log, nil
|
return log, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
if sessionUUID == "" {
|
if sessionUUID == "" {
|
||||||
sessionUUID = uuid.New().String()
|
sessionUUID = uuid.New().String()
|
||||||
}
|
}
|
||||||
|
gs.x.SLog.Debug("new request", slog.String("session-uuid", sessionUUID), slog.Group("connection", slog.String("ip", r.RemoteAddr)))
|
||||||
|
|
||||||
w.Header().Set("X-Session-UUID", sessionUUID)
|
w.Header().Set("X-Session-UUID", sessionUUID)
|
||||||
if !gs.sm.Add(sessionUUID) {
|
if !gs.sm.Add(sessionUUID) {
|
||||||
|
gs.x.SLog.Debug("session is busy", slog.String("session-uuid", sessionUUID))
|
||||||
rpc.WriteError(w, &rpc.RPCResponse{
|
rpc.WriteError(w, &rpc.RPCResponse{
|
||||||
Error: map[string]any{
|
Error: map[string]any{
|
||||||
"code": rpc.ErrSessionIsBusy,
|
"code": rpc.ErrSessionIsBusy,
|
||||||
@@ -36,6 +38,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
gs.x.SLog.Debug("failed to read body", slog.String("err", err.Error()))
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
rpc.WriteError(w, &rpc.RPCResponse{
|
rpc.WriteError(w, &rpc.RPCResponse{
|
||||||
JSONRPC: rpc.JSONRPCVersion,
|
JSONRPC: rpc.JSONRPCVersion,
|
||||||
@@ -55,6 +58,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
var single rpc.RPCRequest
|
var single rpc.RPCRequest
|
||||||
if batch == nil {
|
if batch == nil {
|
||||||
if err := json.Unmarshal(body, &single); err != nil {
|
if err := json.Unmarshal(body, &single); err != nil {
|
||||||
|
gs.x.SLog.Debug("failed to parse json", slog.String("err", err.Error()))
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
rpc.WriteError(w, &rpc.RPCResponse{
|
rpc.WriteError(w, &rpc.RPCResponse{
|
||||||
JSONRPC: rpc.JSONRPCVersion,
|
JSONRPC: rpc.JSONRPCVersion,
|
||||||
@@ -106,17 +110,17 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (gs *GatewayServer) Route(ctx context.Context, sid string, r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
|
func (gs *GatewayServer) Route(ctx context.Context, sid string, r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
|
||||||
defer utils.CatchPanicWithFallback(func(rec any) {
|
defer utils.CatchPanicWithFallback(func(rec any) {
|
||||||
gs.x.SLog.Error("panic caught in handler", slog.Any("error", rec))
|
gs.x.SLog.Error("panic caught in handler", slog.Any("error", rec))
|
||||||
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", req.ID)
|
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", nil, req.ID)
|
||||||
})
|
})
|
||||||
if req.JSONRPC != rpc.JSONRPCVersion {
|
if req.JSONRPC != rpc.JSONRPCVersion {
|
||||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
gs.x.SLog.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, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
server, ok := gs.servers[serversApiVer(req.ContextVersion)]
|
server, ok := gs.servers[serversApiVer(req.ContextVersion)]
|
||||||
if !ok {
|
if !ok {
|
||||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrContextVersionS), slog.String("requested-version", req.ContextVersion))
|
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrContextVersionS), slog.String("requested-version", req.ContextVersion))
|
||||||
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, req.ID)
|
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if request is notification
|
// checks if request is notification
|
||||||
|
|||||||
@@ -2,7 +2,18 @@ package rpc
|
|||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
func NewError(code int, message string, id *json.RawMessage) *RPCResponse {
|
func NewError(code int, message string, data any, id *json.RawMessage) *RPCResponse {
|
||||||
|
if data != nil {
|
||||||
|
return &RPCResponse{
|
||||||
|
JSONRPC: JSONRPCVersion,
|
||||||
|
ID: id,
|
||||||
|
Error: map[string]any{
|
||||||
|
"code": code,
|
||||||
|
"message": message,
|
||||||
|
"data": data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
return &RPCResponse{
|
return &RPCResponse{
|
||||||
JSONRPC: JSONRPCVersion,
|
JSONRPC: JSONRPCVersion,
|
||||||
ID: id,
|
ID: id,
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import (
|
|||||||
func (h *HandlerV1) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
func (h *HandlerV1) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
if req.Method == "" {
|
if req.Method == "" {
|
||||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||||
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, req.ID)
|
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
method, err := h.resolveMethodPath(req.Method)
|
method, err := h.resolveMethodPath(req.Method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
||||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
|
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
|
||||||
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, req.ID)
|
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, nil, req.ID)
|
||||||
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
||||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
h.x.SLog.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 rpc.NewError(rpc.ErrMethodNotFound, rpc.ErrMethodNotFoundS, nil, req.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sv1
|
|||||||
// TODO: make a lua state pool using sync.Pool
|
// TODO: make a lua state pool using sync.Pool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -12,12 +13,322 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DBConnection struct {
|
||||||
|
dbPath string
|
||||||
|
log bool
|
||||||
|
logger *slog.Logger
|
||||||
|
writeChan chan *dbWriteRequest
|
||||||
|
closeChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbWriteRequest struct {
|
||||||
|
query string
|
||||||
|
args []interface{}
|
||||||
|
resCh chan *dbWriteResult
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbWriteResult struct {
|
||||||
|
rowsAffected int64
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbMutexMap = make(map[string]*sync.RWMutex)
|
||||||
|
var dbGlobalMutex sync.Mutex
|
||||||
|
|
||||||
|
func getDBMutex(dbPath string) *sync.RWMutex {
|
||||||
|
dbGlobalMutex.Lock()
|
||||||
|
defer dbGlobalMutex.Unlock()
|
||||||
|
|
||||||
|
if mtx, ok := dbMutexMap[dbPath]; ok {
|
||||||
|
return mtx
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx := &sync.RWMutex{}
|
||||||
|
dbMutexMap[dbPath] = mtx
|
||||||
|
return mtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDBMod(llog *slog.Logger) func(*lua.LState) int {
|
||||||
|
llog.Debug("import module db-sqlite")
|
||||||
|
return func(L *lua.LState) int {
|
||||||
|
dbMod := L.NewTable()
|
||||||
|
|
||||||
|
L.SetField(dbMod, "connect", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
dbPath := L.CheckString(1)
|
||||||
|
|
||||||
|
logQueries := false
|
||||||
|
if L.GetTop() >= 2 {
|
||||||
|
opts := L.CheckTable(2)
|
||||||
|
if val := opts.RawGetString("log"); val != lua.LNil {
|
||||||
|
logQueries = lua.LVAsBool(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &DBConnection{
|
||||||
|
dbPath: dbPath,
|
||||||
|
log: logQueries,
|
||||||
|
logger: llog,
|
||||||
|
writeChan: make(chan *dbWriteRequest, 100),
|
||||||
|
closeChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go conn.processWrites()
|
||||||
|
|
||||||
|
ud := L.NewUserData()
|
||||||
|
ud.Value = conn
|
||||||
|
L.SetMetatable(ud, L.GetTypeMetatable("gosally_db"))
|
||||||
|
|
||||||
|
L.Push(ud)
|
||||||
|
return 1
|
||||||
|
}))
|
||||||
|
|
||||||
|
mt := L.NewTypeMetatable("gosally_db")
|
||||||
|
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
||||||
|
"exec": dbExec,
|
||||||
|
"query": dbQuery,
|
||||||
|
"close": dbClose,
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(dbMod, "__gosally_internal", lua.LString("0"))
|
||||||
|
L.Push(dbMod)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *DBConnection) processWrites() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case req := <-conn.writeChan:
|
||||||
|
mtx := getDBMutex(conn.dbPath)
|
||||||
|
mtx.Lock()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||||
|
if err == nil {
|
||||||
|
_, err = db.Exec("PRAGMA journal_mode=WAL;")
|
||||||
|
if err == nil {
|
||||||
|
res, execErr := db.Exec(req.query, req.args...)
|
||||||
|
if execErr == nil {
|
||||||
|
rows, _ := res.RowsAffected()
|
||||||
|
req.resCh <- &dbWriteResult{rowsAffected: rows}
|
||||||
|
} else {
|
||||||
|
req.resCh <- &dbWriteResult{err: execErr}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
req.resCh <- &dbWriteResult{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx.Unlock()
|
||||||
|
case <-conn.closeChan:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbExec(L *lua.LState) int {
|
||||||
|
ud := L.CheckUserData(1)
|
||||||
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
|
if !ok {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString("invalid database connection"))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
query := L.CheckString(2)
|
||||||
|
|
||||||
|
var args []any
|
||||||
|
if L.GetTop() >= 3 {
|
||||||
|
params := L.CheckTable(3)
|
||||||
|
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||||
|
args = append(args, ConvertLuaTypesToGolang(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.log {
|
||||||
|
conn.logger.Info("DB Exec",
|
||||||
|
slog.String("query", query),
|
||||||
|
slog.Any("params", args))
|
||||||
|
}
|
||||||
|
|
||||||
|
resCh := make(chan *dbWriteResult, 1)
|
||||||
|
conn.writeChan <- &dbWriteRequest{
|
||||||
|
query: query,
|
||||||
|
args: args,
|
||||||
|
resCh: resCh,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := L.NewTable()
|
||||||
|
L.SetField(ctx, "done", lua.LBool(false))
|
||||||
|
|
||||||
|
var result lua.LValue = lua.LNil
|
||||||
|
var errorMsg lua.LValue = lua.LNil
|
||||||
|
|
||||||
|
L.SetField(ctx, "wait", L.NewFunction(func(lL *lua.LState) int {
|
||||||
|
res := <-resCh
|
||||||
|
L.SetField(ctx, "done", lua.LBool(true))
|
||||||
|
|
||||||
|
if res.err != nil {
|
||||||
|
errorMsg = lua.LString(res.err.Error())
|
||||||
|
result = lua.LNil
|
||||||
|
} else {
|
||||||
|
result = lua.LNumber(res.rowsAffected)
|
||||||
|
errorMsg = lua.LNil
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.err != nil {
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(res.err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
lL.Push(lua.LNumber(res.rowsAffected))
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
return 2
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(ctx, "check", L.NewFunction(func(lL *lua.LState) int {
|
||||||
|
select {
|
||||||
|
case res := <-resCh:
|
||||||
|
lL.SetField(ctx, "done", lua.LBool(true))
|
||||||
|
if res.err != nil {
|
||||||
|
errorMsg = lua.LString(res.err.Error())
|
||||||
|
result = lua.LNil
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
lL.Push(lua.LString(res.err.Error()))
|
||||||
|
return 2
|
||||||
|
} else {
|
||||||
|
result = lua.LNumber(res.rowsAffected)
|
||||||
|
errorMsg = lua.LNil
|
||||||
|
lL.Push(lua.LNumber(res.rowsAffected))
|
||||||
|
lL.Push(lua.LNil)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
lL.Push(result)
|
||||||
|
lL.Push(errorMsg)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.Push(ctx)
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbQuery(L *lua.LState) int {
|
||||||
|
ud := L.CheckUserData(1)
|
||||||
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
|
if !ok {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString("invalid database connection"))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
query := L.CheckString(2)
|
||||||
|
|
||||||
|
var args []any
|
||||||
|
if L.GetTop() >= 3 {
|
||||||
|
params := L.CheckTable(3)
|
||||||
|
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||||
|
args = append(args, ConvertLuaTypesToGolang(v))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.log {
|
||||||
|
conn.logger.Info("DB Query",
|
||||||
|
slog.String("query", query),
|
||||||
|
slog.Any("params", args))
|
||||||
|
}
|
||||||
|
|
||||||
|
mtx := getDBMutex(conn.dbPath)
|
||||||
|
mtx.RLock()
|
||||||
|
defer mtx.RUnlock()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("query failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("get columns failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
result := L.NewTable()
|
||||||
|
colCount := len(columns)
|
||||||
|
values := make([]any, colCount)
|
||||||
|
valuePtrs := make([]any, colCount)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
for i := range columns {
|
||||||
|
valuePtrs[i] = &values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(valuePtrs...); err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
rowTable := L.NewTable()
|
||||||
|
for i, col := range columns {
|
||||||
|
val := values[i]
|
||||||
|
if val == nil {
|
||||||
|
L.SetField(rowTable, col, lua.LNil)
|
||||||
|
} else {
|
||||||
|
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.Append(rowTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(fmt.Sprintf("rows iteration failed: %v", err)))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
L.Push(result)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbClose(L *lua.LState) int {
|
||||||
|
ud := L.CheckUserData(1)
|
||||||
|
conn, ok := ud.Value.(*DBConnection)
|
||||||
|
if !ok {
|
||||||
|
L.Push(lua.LFalse)
|
||||||
|
L.Push(lua.LString("invalid database connection"))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
close(conn.closeChan)
|
||||||
|
L.Push(lua.LTrue)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
func addInitiatorHeaders(sid string, req *http.Request, headers http.Header) {
|
func addInitiatorHeaders(sid string, req *http.Request, headers http.Header) {
|
||||||
clientIP := req.RemoteAddr
|
clientIP := req.RemoteAddr
|
||||||
if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||||
@@ -38,13 +349,15 @@ func addInitiatorHeaders(sid string, req *http.Request, headers http.Header) {
|
|||||||
// I will be only glad.
|
// I will be only glad.
|
||||||
// TODO: make this huge function more harmonious by dividing responsibilities
|
// TODO: make this huge function more harmonious by dividing responsibilities
|
||||||
func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest, path string) *rpc.RPCResponse {
|
func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest, path string) *rpc.RPCResponse {
|
||||||
|
llog := h.x.SLog.With(slog.String("session-id", sid))
|
||||||
|
llog.Debug("handling LUA")
|
||||||
L := lua.NewState()
|
L := lua.NewState()
|
||||||
defer L.Close()
|
defer L.Close()
|
||||||
|
|
||||||
seed := rand.Int()
|
seed := rand.Int()
|
||||||
|
|
||||||
loadSessionMod := func(lL *lua.LState) int {
|
loadSessionMod := func(lL *lua.LState) int {
|
||||||
h.x.SLog.Debug("import module session", slog.String("script", path))
|
llog.Debug("import module session", slog.String("script", path))
|
||||||
sessionMod := lL.NewTable()
|
sessionMod := lL.NewTable()
|
||||||
inTable := lL.NewTable()
|
inTable := lL.NewTable()
|
||||||
paramsTable := lL.NewTable()
|
paramsTable := lL.NewTable()
|
||||||
@@ -59,59 +372,66 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
|||||||
resultTable := lL.NewTable()
|
resultTable := lL.NewTable()
|
||||||
lL.SetField(outTable, "result", resultTable)
|
lL.SetField(outTable, "result", resultTable)
|
||||||
|
|
||||||
|
lL.SetField(inTable, "address", lua.LString(r.RemoteAddr))
|
||||||
lL.SetField(sessionMod, "request", inTable)
|
lL.SetField(sessionMod, "request", inTable)
|
||||||
lL.SetField(sessionMod, "response", outTable)
|
lL.SetField(sessionMod, "response", outTable)
|
||||||
|
|
||||||
lL.Push(sessionMod)
|
lL.SetField(sessionMod, "id", lua.LString(sid))
|
||||||
|
|
||||||
lL.SetField(sessionMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
lL.SetField(sessionMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||||
|
lL.Push(sessionMod)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLogMod := func(lL *lua.LState) int {
|
loadLogMod := func(lL *lua.LState) int {
|
||||||
h.x.SLog.Debug("import module log", slog.String("script", path))
|
llog.Debug("import module log", slog.String("script", path))
|
||||||
logMod := lL.NewTable()
|
logMod := lL.NewTable()
|
||||||
|
|
||||||
logFuncs := map[string]func(string, ...any){
|
logFuncs := map[string]func(string, ...any){
|
||||||
"info": h.x.SLog.Info,
|
"info": llog.Info,
|
||||||
"debug": h.x.SLog.Debug,
|
"debug": llog.Debug,
|
||||||
"error": h.x.SLog.Error,
|
"error": llog.Error,
|
||||||
"warn": h.x.SLog.Warn,
|
"warn": llog.Warn,
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, logFunc := range logFuncs {
|
for name, logFunc := range logFuncs {
|
||||||
fun := logFunc
|
fun := logFunc
|
||||||
lL.SetField(logMod, name, lL.NewFunction(func(lL *lua.LState) int {
|
lL.SetField(logMod, name, lL.NewFunction(func(lL *lua.LState) int {
|
||||||
msg := lL.ToString(1)
|
msg := lL.Get(1)
|
||||||
fun(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
converted := ConvertLuaTypesToGolang(msg)
|
||||||
|
fun(fmt.Sprintf("the script says: %s", converted), slog.String("script", path))
|
||||||
return 0
|
return 0
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
lL.SetField(logMod, "event", lL.NewFunction(func(lL *lua.LState) int {
|
for _, fn := range []struct {
|
||||||
msg := lL.ToString(1)
|
field string
|
||||||
h.x.Log.Printf("%s: %s", path, msg)
|
pfunc func(string, ...any)
|
||||||
return 0
|
color func() string
|
||||||
}))
|
}{
|
||||||
|
{"event", h.x.Log.Printf, nil},
|
||||||
|
{"event_error", h.x.Log.Printf, colors.PrintError},
|
||||||
|
{"event_warn", h.x.Log.Printf, colors.PrintWarn},
|
||||||
|
} {
|
||||||
|
lL.SetField(logMod, fn.field, lL.NewFunction(func(lL *lua.LState) int {
|
||||||
|
msg := lL.Get(1)
|
||||||
|
converted := ConvertLuaTypesToGolang(msg)
|
||||||
|
if fn.color != nil {
|
||||||
|
h.x.Log.Printf("%s: %s: %s", fn.color(), path, converted)
|
||||||
|
} else {
|
||||||
|
h.x.Log.Printf("%s: %s", path, converted)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
lL.SetField(logMod, "event_error", lL.NewFunction(func(lL *lua.LState) int {
|
|
||||||
msg := lL.ToString(1)
|
|
||||||
h.x.Log.Printf("%s: %s: %s", colors.PrintError(), path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
lL.SetField(logMod, "event_warn", lL.NewFunction(func(lL *lua.LState) int {
|
|
||||||
msg := lL.ToString(1)
|
|
||||||
h.x.Log.Printf("%s: %s: %s", colors.PrintWarn(), path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
lL.Push(logMod)
|
|
||||||
lL.SetField(logMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
lL.SetField(logMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||||
|
lL.Push(logMod)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNetMod := func(lL *lua.LState) int {
|
loadNetMod := func(lL *lua.LState) int {
|
||||||
h.x.SLog.Debug("import module net", slog.String("script", path))
|
llog.Debug("import module net", slog.String("script", path))
|
||||||
netMod := lL.NewTable()
|
netMod := lL.NewTable()
|
||||||
netModhttp := lL.NewTable()
|
netModhttp := lL.NewTable()
|
||||||
|
|
||||||
@@ -145,7 +465,7 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if logRequest {
|
if logRequest {
|
||||||
h.x.SLog.Info("HTTP GET request",
|
llog.Info("HTTP GET request",
|
||||||
slog.String("script", path),
|
slog.String("script", path),
|
||||||
slog.String("url", url),
|
slog.String("url", url),
|
||||||
slog.Int("status", resp.StatusCode),
|
slog.Int("status", resp.StatusCode),
|
||||||
@@ -206,7 +526,7 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if logRequest {
|
if logRequest {
|
||||||
h.x.SLog.Info("HTTP POST request",
|
llog.Info("HTTP POST request",
|
||||||
slog.String("script", path),
|
slog.String("script", path),
|
||||||
slog.String("url", url),
|
slog.String("url", url),
|
||||||
slog.String("content_type", contentType),
|
slog.String("content_type", contentType),
|
||||||
@@ -234,42 +554,45 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
|||||||
|
|
||||||
lL.SetField(netMod, "http", netModhttp)
|
lL.SetField(netMod, "http", netModhttp)
|
||||||
|
|
||||||
lL.Push(netMod)
|
|
||||||
lL.SetField(netMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
lL.SetField(netMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||||
|
lL.Push(netMod)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
L.PreloadModule("session", loadSessionMod)
|
L.PreloadModule("internal.session", loadSessionMod)
|
||||||
L.PreloadModule("log", loadLogMod)
|
L.PreloadModule("internal.log", loadLogMod)
|
||||||
L.PreloadModule("net", loadNetMod)
|
L.PreloadModule("internal.net", loadNetMod)
|
||||||
|
L.PreloadModule("internal.database-sqlite", loadDBMod(llog))
|
||||||
|
|
||||||
|
llog.Debug("preparing environment")
|
||||||
prep := filepath.Join(*h.x.Config.Conf.Node.ComDir, "_prepare.lua")
|
prep := filepath.Join(*h.x.Config.Conf.Node.ComDir, "_prepare.lua")
|
||||||
if _, err := os.Stat(prep); err == nil {
|
if _, err := os.Stat(prep); err == nil {
|
||||||
if err := L.DoFile(prep); err != nil {
|
if err := L.DoFile(prep); err != nil {
|
||||||
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
llog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
||||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
llog.Debug("executing script", slog.String("script", path))
|
||||||
if err := L.DoFile(path); err != nil {
|
if err := L.DoFile(path); err != nil {
|
||||||
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
llog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
||||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg := L.GetGlobal("package")
|
pkg := L.GetGlobal("package")
|
||||||
pkgTbl, ok := pkg.(*lua.LTable)
|
pkgTbl, ok := pkg.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", "package not found"))
|
llog.Error("script error", slog.String("script", path), slog.String("error", "package not found"))
|
||||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded := pkgTbl.RawGetString("loaded")
|
loaded := pkgTbl.RawGetString("loaded")
|
||||||
loadedTbl, ok := loaded.(*lua.LTable)
|
loadedTbl, ok := loaded.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", "package.loaded not found"))
|
llog.Error("script error", slog.String("script", path), slog.String("error", "package.loaded not found"))
|
||||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionVal := loadedTbl.RawGetString("session")
|
sessionVal := loadedTbl.RawGetString("internal.session")
|
||||||
sessionTbl, ok := sessionVal.(*lua.LTable)
|
sessionTbl, ok := sessionVal.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return rpc.NewResponse(map[string]any{
|
return rpc.NewResponse(map[string]any{
|
||||||
@@ -279,6 +602,7 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
|||||||
|
|
||||||
tag := sessionTbl.RawGetString("__gosally_internal")
|
tag := sessionTbl.RawGetString("__gosally_internal")
|
||||||
if tag.Type() != lua.LTString || tag.String() != fmt.Sprint(seed) {
|
if tag.Type() != lua.LTString || tag.String() != fmt.Sprint(seed) {
|
||||||
|
llog.Debug("stock session module is not imported: wrong seed", slog.String("script", path))
|
||||||
return rpc.NewResponse(map[string]any{
|
return rpc.NewResponse(map[string]any{
|
||||||
"responsible-node": h.cs.UUID32,
|
"responsible-node": h.cs.UUID32,
|
||||||
}, req.ID)
|
}, req.ID)
|
||||||
@@ -287,24 +611,34 @@ func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest,
|
|||||||
outVal := sessionTbl.RawGetString("response")
|
outVal := sessionTbl.RawGetString("response")
|
||||||
outTbl, ok := outVal.(*lua.LTable)
|
outTbl, ok := outVal.(*lua.LTable)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.x.SLog.Error("script error", slog.String("script", path), slog.String("error", "response is not a table"))
|
llog.Error("script error", slog.String("script", path), slog.String("error", "response is not a table"))
|
||||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errVal := outTbl.RawGetString("error"); errVal != lua.LNil {
|
if errVal := outTbl.RawGetString("error"); errVal != lua.LNil {
|
||||||
|
llog.Debug("catch error table", slog.String("script", path))
|
||||||
if errTbl, ok := errVal.(*lua.LTable); ok {
|
if errTbl, ok := errVal.(*lua.LTable); ok {
|
||||||
code := rpc.ErrInternalError
|
code := rpc.ErrInternalError
|
||||||
message := rpc.ErrInternalErrorS
|
message := rpc.ErrInternalErrorS
|
||||||
|
data := make(map[string]any)
|
||||||
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
||||||
code = int(c.(lua.LNumber))
|
code = int(c.(lua.LNumber))
|
||||||
}
|
}
|
||||||
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
||||||
message = msg.String()
|
message = msg.String()
|
||||||
}
|
}
|
||||||
h.x.SLog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
rawData := errTbl.RawGetString("data")
|
||||||
return rpc.NewError(code, message, req.ID)
|
|
||||||
|
if tbl, ok := rawData.(*lua.LTable); ok {
|
||||||
|
tbl.ForEach(func(k, v lua.LValue) { data[k.String()] = ConvertLuaTypesToGolang(v) })
|
||||||
|
} else {
|
||||||
|
llog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||||
|
return rpc.NewError(code, message, ConvertLuaTypesToGolang(rawData), req.ID)
|
||||||
|
}
|
||||||
|
llog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||||
|
return rpc.NewError(code, message, data, req.ID)
|
||||||
}
|
}
|
||||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, req.ID)
|
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultVal := outTbl.RawGetString("result")
|
resultVal := outTbl.RawGetString("result")
|
||||||
|
|||||||
Reference in New Issue
Block a user