diff options
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 236 |
1 files changed, 236 insertions, 0 deletions
@@ -0,0 +1,236 @@ +package main + +import ( + "errors" + "fmt" + "io" + "log" + "net/http" +) +/* +#cgo LDFLAGS: -llua +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +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)) +} |
