package main import ( "errors" "fmt" "io" "log" "net/http" ) /* #cgo LDFLAGS: -llua #include #include #include #include */ import "C" import "unsafe" type LuaRef C.longlong type Lua struct { l *C.lua_State } func (l *Lua) Start () { l.l = C.luaL_newstate() C.luaL_openlibs(l.l) } func (l *Lua) Close () { C.lua_close(l.l) C.free(unsafe.Pointer(l.l)) } func (l *Lua) Require (file string) (LuaRef, error) { if !l.LoadFile(file) { return 0, errors.New("could not open the file") } if !l.PCall(0, C.LUA_MULTRET) { return 0, errors.New("could not execute the file") } if !l.IsTable(-1) { return 0, errors.New("module did not return a table") } return l.PopToRef(), nil } func (l *Lua) PCall (nargs int, nresults int) bool { res := C.lua_pcallk(l.l, C.int(nargs), C.int(nresults), 0, 0, nil) return res == C.LUA_OK } func (l *Lua) LoadFile (file string) bool { cfile := C.CString(file) defer C.free(unsafe.Pointer(cfile)) res := C.luaL_loadfilex(l.l, cfile, nil) return res == C.LUA_OK } func (l *Lua) StackLen () int { return int(C.lua_gettop(l.l)) } func (l *Lua) ToString (index int) string { return C.GoString(C.lua_tolstring(l.l, C.int(index), nil)) } func (l *Lua) ToInt (index int) int { return int(C.lua_tonumberx(l.l, C.int(index), nil)) } func (l *Lua) PopToRef () LuaRef { return LuaRef(C.luaL_ref(l.l, C.LUA_REGISTRYINDEX)) } func (l *Lua) Pop (n int) { C.lua_settop(l.l, C.int(-n - 1)) } func (l *Lua) PushNil () { C.lua_pushnil(l.l) } func (l *Lua) PushString (str string) { cstr := C.CString(str) defer C.free(unsafe.Pointer(cstr)) C.lua_pushstring(l.l, cstr) } func (l *Lua) PushTable (table map[string]string) { C.lua_createtable(l.l, 0, C.int(len(table))) for k, v := range table { l.PushString(v) C.lua_setfield(l.l, -2, C.CString(k)) } } func (l *Lua) PushFromRef (ref LuaRef) { C.lua_rawgeti(l.l, C.LUA_REGISTRYINDEX, C.longlong(ref)); } func (l *Lua) IsString (index int) bool { return C.lua_isstring(l.l, C.int(index)) == 1 } func (l *Lua) IsNumber (index int) bool { return C.lua_isnumber(l.l, C.int(index)) == 1 } func (l *Lua) IsFunction (index int) bool { return C.lua_type(l.l, C.int(index)) == C.LUA_TFUNCTION } func (l *Lua) IsTable (index int) bool { return C.lua_type(l.l, C.int(index)) == C.LUA_TTABLE } func (l *Lua) Next () bool { return C.lua_next(l.l, -2) != 0 } func (l *Lua) PushTableItem (key string) { ckey := C.CString(key) defer C.free(unsafe.Pointer(ckey)) C.lua_pushstring(l.l, ckey); C.lua_gettable(l.l, -2); } type WorkerRequest struct { Request *http.Request Route string result chan *WorkerResponse } type WorkerResponse struct { Code int Headers map[string]string Body string } type Worker struct { read chan *WorkerRequest lua *Lua api LuaRef routes map[string]LuaRef started bool } func NewWorker(read chan *WorkerRequest) *Worker { return &Worker { read: read, routes: make(map[string]LuaRef), lua: &Lua{}, } } func (w *Worker) Start (filename string) error { if w.started { return errors.New("already started") } w.lua.Start() api, err := w.lua.Require(filename) if err != nil { return err } w.api = api w.initRoutes() return nil } func (w *Worker) Listen () { for { r := <- w.read handlerRef := w.routes[r.Route] w.lua.PushFromRef(handlerRef) w.lua.PushString(r.Request.Method) w.lua.PushString(r.Request.URL.Path) fh := make(map[string]string) for k := range r.Request.Header { fh[k] = r.Request.Header.Get(k) } w.lua.PushTable(fh) body, err := io.ReadAll(r.Request.Body) if err != nil { w.lua.Pop(4); // TODO: 500 return } w.lua.PushString(string(body)) w.lua.PCall(4, 3) code := w.lua.ToInt(-3) rbody := w.lua.ToString(-1) // parse headers headers := make(map[string]string) w.lua.Pop(1) w.lua.PushNil() for w.lua.Next() { if !w.lua.IsString(-2) || !w.lua.IsString(-2) { w.lua.Pop(1) continue } v := w.lua.ToString(-1) w.lua.Pop(1) // we must not pop the item key from the stack because // otherwise C.lua_next won't work properly k := w.lua.ToString(-1) headers[k] = v } w.lua.Pop(2) r.result <- &WorkerResponse { Code: int(code), Headers: headers, Body: rbody, } } } func (w *Worker) Request (route string, r *http.Request,) chan *WorkerResponse { res := make(chan *WorkerResponse) w.read <- &WorkerRequest{ Request: r, Route: route, result: res, } return res } func (w *Worker) ListRoutes() []string { res := []string{} for route, _ := range w.routes { res = append(res, route) } return res } func (w *Worker) Stop () { w.lua.Close() } func (w *Worker) initRoutes() { w.lua.PushFromRef(w.api) w.lua.PushTableItem("routes") // FIXME: istable? w.lua.PushNil() for w.lua.Next() { if !w.lua.IsString(-2) || !w.lua.IsFunction(-1) { w.lua.Pop(1) continue } handlerRef := w.lua.PopToRef() // we must not pop the item key from the stack because // otherwise C.lua_next won't work properly route := w.lua.ToString(-1) w.routes[route] = handlerRef } // FIXME: pop? } func main() { rch := make(chan *WorkerRequest, 4096) for i := 0; i < 8; i++ { log.Printf("worker %d started\n", i) w := NewWorker(rch) err := w.Start("init.lua") if err != nil { log.Fatal(err) } if i == 0 { for _, route := range w.ListRoutes() { http.HandleFunc( route, func (wr http.ResponseWriter, r *http.Request) { resCh := w.Request(route, r) res := <- resCh for k, h := range res.Headers { wr.Header().Set(k, h) } wr.WriteHeader(int(res.Code)) fmt.Fprint(wr, string(res.Body)) }, ) } } go w.Listen() defer w.Stop() } fmt.Println("running") log.Fatal(http.ListenAndServe("127.0.0.1:3002", nil)) }