package main import ( "errors" "fmt" "io" "log" "net/http" ) /* #cgo LDFLAGS: -llua #include #include #include int luna_load_file(lua_State *L, char *file) { if (luaL_dofile(L, file) != LUA_OK) { return -1; } if (!lua_istable(L, -1)) { return -1; } return luaL_ref(L, LUA_REGISTRYINDEX); } int luna_isfunction(lua_State *L, int n) { return lua_isfunction(L, n); } void luna_push_table_item(lua_State *L, int ref, char *key) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref); lua_pushstring(L, key); lua_gettable(L, -2); } void luna_pop(lua_State *L, int n) { lua_pop(L, n); } const char * luna_tostring(lua_State *L, int n) { return lua_tostring(L, n); } int luna_tonumber(lua_State *L, int n) { return lua_tonumber(L, n); } void luna_push_ref(lua_State *L, int ref) { lua_rawgeti(L, LUA_REGISTRYINDEX, ref); } int luna_pop_ref(lua_State *L) { return luaL_ref(L, LUA_REGISTRYINDEX); } const char * luna_pcall(lua_State *L, int nargs, int nresults) { if (lua_pcall(L, nargs, nresults, 0) != LUA_OK) { // FIXME: check for errors return NULL; } } */ import "C" 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 L *C.lua_State routes map[string]C.int api C.int started bool } func NewWorker(read chan *WorkerRequest) *Worker { return &Worker { read: read, routes: make(map[string]C.int), } } func (w *Worker) Start (filename string) error { if w.started { return errors.New("already started") } w.L = C.luaL_newstate() C.luaL_openlibs(w.L) // FIXME: C.luna_load_file should return an error. w.api = C.luna_load_file(w.L, C.CString(filename)) w.initRoutes() return nil } func (w *Worker) Listen () { for { r := <- w.read handlerRef := w.routes[r.Route] C.luna_push_ref(w.L, handlerRef) C.lua_pushstring(w.L, C.CString(r.Request.Method)) C.lua_pushstring(w.L, C.CString(r.Request.URL.Path)) // table for headers C.lua_createtable(w.L, 0, C.int(len(r.Request.Header))) for k := range r.Request.Header { h := r.Request.Header.Get(k) C.lua_pushstring(w.L, C.CString(h)) C.lua_setfield(w.L, -2, C.CString(k)) } body, err := io.ReadAll(r.Request.Body) if err != nil { C.luna_pop(w.L, 4); // TODO: 500 return } C.lua_pushstring(w.L, C.CString(string(body))) C.luna_pcall(w.L, 4, 3) code := C.luna_tonumber(w.L, -3) rbody := C.GoString(C.luna_tostring(w.L, -1)) // parse headers headers := make(map[string]string) C.luna_pop(w.L, 1) C.lua_pushnil(w.L) for C.lua_next(w.L, -2) != 0 { if C.lua_isstring(w.L, -2) == 0 || C.lua_isstring(w.L, -1) == 0 { C.luna_pop(w.L, 1) continue } v := C.GoString(C.luna_tostring(w.L, -1)) C.luna_pop(w.L, 1) // we must not pop the item key from the stack because // otherwise C.lua_next won't work properly k := C.GoString(C.luna_tostring(w.L, -1)) headers[k] = v } C.luna_pop(w.L, 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 () { C.lua_close(w.L) } func (w *Worker) initRoutes() { C.luna_push_table_item(w.L, w.api, C.CString("routes")) C.lua_pushnil(w.L) for C.lua_next(w.L, -2) != 0 { if C.lua_isstring(w.L, -2) == 0 || C.luna_isfunction(w.L, -1) == 0 { C.luna_pop(w.L, 1) continue } handlerRef := C.luna_pop_ref(w.L) // we must not pop the item key from the stack because // otherwise C.lua_next won't work properly route := C.GoString(C.luna_tostring(w.L, -1)) w.routes[route] = handlerRef } } func main() { rch := make(chan *WorkerRequest, 4096) for i := 0; i < 8; i++ { log.Printf("worker %d started\n", i) w := NewWorker(rch) w.Start("init.lua") 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:3001", nil)) }