github.com/mikespook/gleam@v0.0.0-20170807230223-29a9392b522c/lua.go (about) 1 package gleam 2 3 import ( 4 "context" 5 "crypto/tls" 6 "log" 7 "net/http" 8 "os" 9 "strings" 10 "sync" 11 12 // lua 13 "github.com/cjoudrey/gluahttp" 14 "github.com/cjoudrey/gluaurl" 15 "github.com/yuin/gluare" 16 lua "github.com/yuin/gopher-lua" 17 "layeh.com/gopher-json" 18 "layeh.com/gopher-lfs" 19 "layeh.com/gopher-luar" 20 21 // mqtt 22 mqtt "github.com/eclipse/paho.mqtt.golang" 23 24 "github.com/mikespook/schego" 25 ) 26 27 const ( 28 MessageFunc = "onDefaultMessage" 29 ErrorFunc = "onError" 30 InitFunc = "init" 31 AfterInitFunc = "afterInit" 32 BeforeFinalizeFunc = "beforeFinalize" 33 FinalizeFunc = "finalize" 34 35 LogFunc = "log" 36 LogfFunc = "logf" 37 38 ConfigVar = "config" 39 40 LuaCallStackSize = 1024 41 LuaIncludeGoStackTrace = true 42 LuaRegistrySize = 1024 * 64 43 LuaSkipOpenLibs = false 44 45 BootstrapFile = "bootstrap.lua" 46 ) 47 48 type luaEnv struct { 49 sync.RWMutex 50 workdir string 51 l *lua.LState 52 } 53 54 func newLuaEnv(workdir string) *luaEnv { 55 return &luaEnv{ 56 workdir: workdir, 57 } 58 } 59 60 func (e *luaEnv) Init(config *Config) error { 61 if err := os.Chdir(e.workdir); err != nil { 62 return err 63 } 64 e.Lock() 65 defer e.Unlock() 66 opt := lua.Options{ 67 CallStackSize: LuaCallStackSize, 68 IncludeGoStackTrace: LuaIncludeGoStackTrace, 69 RegistrySize: LuaRegistrySize, 70 SkipOpenLibs: LuaSkipOpenLibs, 71 } 72 e.l = lua.NewState(opt) 73 // Preload module 74 json.Preload(e.l) 75 lfs.Preload(e.l) 76 e.l.PreloadModule("re", gluare.Loader) 77 e.l.PreloadModule("url", gluaurl.Loader) 78 client := &http.Client{} 79 e.l.PreloadModule("http", gluahttp.NewHttpModule(client).Loader) 80 // Buildin var & func 81 e.setLog() 82 e.setLogf() 83 e.l.SetGlobal(ConfigVar, luar.New(e.l, config)) 84 r := e.l.DoFile(BootstrapFile) 85 if config.NotVerifyTLS { // TODO apply config to PreloadModules 86 client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} 87 } 88 return r 89 } 90 91 func (e *luaEnv) setLog() { 92 e.l.SetGlobal(LogFunc, e.l.NewFunction(func(L *lua.LState) int { 93 argc := L.GetTop() 94 argv := make([]interface{}, argc) 95 for i := 1; i <= argc; i++ { 96 argv[i-1] = L.Get(i) 97 } 98 log.Print(argv...) 99 return 0 100 })) 101 } 102 103 func (e *luaEnv) setLogf() { 104 e.l.SetGlobal(LogfFunc, e.l.NewFunction(func(L *lua.LState) int { 105 argc := L.GetTop() 106 format := L.Get(1).String() 107 argv := make([]interface{}, argc-1) 108 for i := 2; i <= argc; i++ { 109 argv[i-2] = L.Get(i) 110 } 111 log.Printf(format, argv...) 112 return 0 113 })) 114 } 115 116 func (e *luaEnv) Final() { 117 e.l.Close() 118 } 119 120 func (e *luaEnv) getFuncByName(obj lua.LValue, nest []string) lua.LValue { 121 if obj.Type() == lua.LTFunction { 122 return obj 123 } 124 if obj.Type() == lua.LTTable { 125 if len(nest) == 0 { 126 return lua.LNil 127 } 128 obj = obj.(*lua.LTable).RawGetString(nest[0]) 129 return e.getFuncByName(obj, nest[1:]) 130 } 131 return lua.LNil 132 } 133 134 func (e *luaEnv) GetFuncByName(name string) lua.LValue { 135 nest := strings.Split(name, ".") 136 obj := e.l.GetGlobal(nest[0]) 137 return e.getFuncByName(obj, nest[1:]) 138 } 139 140 func (e *luaEnv) newOnMessage(name string) mqtt.MessageHandler { 141 if name == "" { 142 return nil 143 } 144 e.RLock() 145 p := lua.P{ 146 Fn: e.GetFuncByName(name), 147 Protect: true, 148 } 149 e.RUnlock() 150 if p.Fn.Type() == lua.LTNil { // Final is not defined, return nil to run the default one 151 return nil 152 } 153 return func(client mqtt.Client, msg mqtt.Message) { 154 e.Lock() 155 defer e.Unlock() 156 L, cancel := e.l.NewThread() 157 defer func() { 158 if cancel != nil { 159 cancel() 160 } 161 L.Close() 162 }() 163 clientL := luar.New(L, client) 164 msgL := messageToLua(L, msg) 165 if err := L.CallByParam(p, clientL, msgL); err != nil { 166 ctx := context.Background() 167 ctx = context.WithValue(ctx, "name", name) 168 ctx = context.WithValue(ctx, "message", msg) 169 e.onError(ctx, err) 170 } 171 } 172 } 173 174 func (e *luaEnv) newOnSchedule(name string, client *mqtt.Client) schego.ExecFunc { 175 e.RLock() 176 p := lua.P{ 177 Fn: e.GetFuncByName(name), 178 Protect: true, 179 } 180 e.RUnlock() 181 if p.Fn.Type() == lua.LTNil { // Specific schego func is not defined, return nil to skip it 182 return nil 183 } 184 return func(ctx context.Context) error { 185 e.Lock() 186 defer e.Unlock() 187 L, cancel := e.l.NewThread() 188 defer func() { 189 if cancel != nil { 190 cancel() 191 } 192 L.Close() 193 }() 194 ctxL := contextToLua(L, ctx) 195 clientL := luar.New(L, *client) 196 return L.CallByParam(p, clientL, ctxL) 197 } 198 } 199 200 func (e *luaEnv) onError(ctx context.Context, err error) { 201 e.RLock() 202 p := lua.P{ 203 Fn: e.l.GetGlobal(ErrorFunc), 204 NRet: 0, 205 Protect: true, 206 } 207 e.RUnlock() 208 if p.Fn.Type() == lua.LTNil { 209 return 210 } 211 212 ctxL := contextToLua(e.l, ctx) 213 errL := luar.New(e.l, err.Error()) 214 215 if err := e.l.CallByParam(p, ctxL, errL); err != nil { 216 log.Printf("Error: %s", err) 217 } 218 } 219 220 func (e *luaEnv) onSeat(name string) error { 221 e.RLock() 222 p := lua.P{ 223 Fn: e.l.GetGlobal(name), 224 NRet: 0, 225 Protect: true, 226 } 227 e.RUnlock() 228 if p.Fn.Type() == lua.LTNil { 229 return nil 230 } 231 return e.l.CallByParam(p) 232 } 233 234 func (e *luaEnv) onEvent(name string, client mqtt.Client) error { 235 e.RLock() 236 p := lua.P{ 237 Fn: e.l.GetGlobal(name), 238 NRet: 0, 239 Protect: true, 240 } 241 e.RUnlock() 242 if p.Fn.Type() == lua.LTNil { 243 return nil 244 } 245 clientL := luar.New(e.l, client) 246 return e.l.CallByParam(p, clientL) 247 } 248 249 func contextToLua(L *lua.LState, ctx context.Context) lua.LValue { 250 name := ctx.Value("name") 251 if name != nil { 252 strname, ok := name.(string) 253 if !ok { 254 return lua.LNil 255 } 256 msgL := luar.New(L, ctx.Value("message")) 257 ctxL := L.CreateTable(0, 2) 258 ctxL.RawSetString("Id", lua.LString(strname)) 259 ctxL.RawSetString("Message", msgL) 260 return ctxL 261 } 262 event := ctx.Value("event") 263 if event != nil { 264 return luar.New(L, event) 265 } 266 return lua.LNil 267 } 268 269 func messageToLua(L *lua.LState, msg mqtt.Message) *lua.LTable { 270 msgL := L.CreateTable(0, 6) 271 msgL.RawSetString("Duplicate", lua.LBool(msg.Duplicate())) 272 msgL.RawSetString("MessageID", lua.LNumber(msg.MessageID())) 273 msgL.RawSetString("Payload", lua.LString(msg.Payload())) 274 msgL.RawSetString("Qos", lua.LNumber(msg.Qos())) 275 msgL.RawSetString("Retained", lua.LBool(msg.Retained())) 276 msgL.RawSetString("Topic", lua.LString(msg.Topic())) 277 return msgL 278 }