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  }