summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunwox <me@unwox.com>2024-07-23 23:19:46 +0600
committerunwox <me@unwox.com>2024-07-23 23:24:14 +0600
commitcba8ff3896a59598e9f0aa946fd547002ac23aba (patch)
tree33fa5edd8f9640bb00e28f75e6bb73910e80c529
parent701d73e4a47964ea40100ab2f329ef1dcc0a6f83 (diff)
use luna module instead of returning table for lua->go interaction
-rw-r--r--init.fnl2
-rw-r--r--lua.go57
-rw-r--r--main.go110
-rw-r--r--worker.go183
4 files changed, 193 insertions, 159 deletions
diff --git a/init.fnl b/init.fnl
index bce021a..0f89ad3 100644
--- a/init.fnl
+++ b/init.fnl
@@ -14,4 +14,4 @@
(let [headers { :content-type "text/html" }]
(values 200 headers (html.render template))))
-{ :routes { :/ root-handler } }
+(luna.router.route "/" root-handler)
diff --git a/lua.go b/lua.go
index 199f13a..e7eee54 100644
--- a/lua.go
+++ b/lua.go
@@ -21,7 +21,7 @@ static inline void luna_push_function(lua_State *l, uintptr_t f) {
import "C"
import (
"errors"
- "log"
+ "fmt"
"runtime/cgo"
"unsafe"
)
@@ -33,7 +33,6 @@ type Lua struct {
//export luna_run_go_func
func luna_run_go_func(f C.uintptr_t) C.int {
- log.Println(f)
fn := cgo.Handle(f).Value().(func() int)
return C.int(fn())
}
@@ -45,36 +44,36 @@ func (l *Lua) Start() {
func (l *Lua) Close() {
C.lua_close(l.l)
- C.free(unsafe.Pointer(l.l))
}
-func (l *Lua) Require(file string) (LuaRef, error) {
+func (l *Lua) Require(file string) error {
if !l.LoadFile(file) {
errMsg := l.ToString(-1)
l.Pop(1)
- return 0, errors.New("could not open the file:\n" + errMsg)
+ return errors.New("could not open the file:\n" + errMsg)
}
- if !l.PCall(0, C.LUA_MULTRET) {
- errMsg := l.ToString(-1)
- l.Pop(1)
- return 0, errors.New("could not execute the file:\n" + errMsg)
- }
- if !l.IsTable(-1) {
- return 0, errors.New("module did not return a table")
+ err := l.PCall(0, C.LUA_MULTRET)
+ if err != nil {
+ return errors.New("could not execute the file:\n" + err.Error())
}
- return l.PopToRef(), nil
+ return nil
}
-func (l *Lua) PCall(nargs int, nresults int) bool {
+func (l *Lua) PCall(nargs int, nresults int) error {
res := C.lua_pcallk(l.l, C.int(nargs), C.int(nresults), 0, 0, nil)
- return res == C.LUA_OK
+ if res != C.LUA_OK {
+ errMsg := l.ToString(-1)
+ l.Pop(1)
+ return errors.New(errMsg)
+ }
+ return nil
}
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)
+ cstr := C.CString(file)
+ defer C.free(unsafe.Pointer(cstr))
+ res := C.luaL_loadfilex(l.l, cstr, nil)
return res == C.LUA_OK
}
@@ -102,6 +101,10 @@ func (l *Lua) PushNil() {
C.lua_pushnil(l.l)
}
+func (l *Lua) PushNumber(num int) {
+ C.lua_pushnumber(l.l, C.double(num))
+}
+
func (l *Lua) PushString(str string) {
cstr := C.CString(str)
defer C.free(unsafe.Pointer(cstr))
@@ -121,11 +124,13 @@ func (l *Lua) PushGoFunction(f func (l *Lua) int) cgo.Handle {
func (l *Lua) CreateTable(len int) {
C.lua_createtable(l.l, 0, C.int(len))
}
+
func (l *Lua) SetTableItem(key string) {
cstr := C.CString(key)
defer C.free(unsafe.Pointer(cstr))
C.lua_setfield(l.l, -2, C.CString(key))
}
+
func (l *Lua) PushStringTable(table map[string]string) {
l.CreateTable(len(table))
for k, v := range table {
@@ -173,10 +178,9 @@ func (l *Lua) LoadString(code string) error {
l.Pop(1)
return errors.New(errMsg)
}
- if !l.PCall(0, 0) {
- errMsg := l.ToString(-1)
- l.Pop(1)
- return errors.New(errMsg)
+ err := l.PCall(0, 0)
+ if err != nil {
+ return err
}
l.Pop(l.StackLen())
return nil
@@ -190,11 +194,12 @@ func (l *Lua) RestoreStackFunc() func () {
if diff == 0 {
return
} else if diff < 0 {
- log.Fatalf(
+ msg := fmt.Sprintf(
"too many stack pops: len before: %d, after: %d\n",
before,
after,
)
+ panic(msg)
}
C.lua_settop(l.l, C.int(before))
}
@@ -205,3 +210,9 @@ func (l *Lua) SetGlobal(name string) {
defer C.free(unsafe.Pointer(cstr))
C.lua_setglobal(l.l, cstr)
}
+
+func (l *Lua) GetGlobal(name string) {
+ cstr := C.CString(name)
+ defer C.free(unsafe.Pointer(cstr))
+ C.lua_getglobal(l.l, cstr)
+}
diff --git a/main.go b/main.go
index 793928d..8fb3608 100644
--- a/main.go
+++ b/main.go
@@ -2,7 +2,6 @@ package main
import (
"bufio"
- "flag"
"fmt"
"log"
"net/http"
@@ -11,64 +10,89 @@ import (
)
func main() {
- debug := flag.Bool("debug", false, "Debug mode!")
- flag.Parse()
-
- readCh := make(chan interface{}, 4096)
+ msgs := make(chan interface{}, 4096)
mux := http.NewServeMux()
wrks := []*Worker{}
- for i := 0; i < 8; i++ {
- log.Printf("worker %d started\n", i)
- wrk := NewWorker(readCh)
- err := wrk.Start("init.lua")
- if err != nil {
- log.Fatal(err)
- }
- if i == 0 {
- for _, route := range wrk.ListRoutes() {
- // we need to copy route otherwise it's going
- // to change with every next iteration
- route := route
- mux.HandleFunc(
- route,
- func (w http.ResponseWriter, r *http.Request) {
- start := time.Now()
- resCh := wrk.Request(route, r, *debug)
- res := <- resCh
- for k, h := range res.Headers {
- w.Header().Set(k, h)
- }
- w.WriteHeader(int(res.Code))
- fmt.Fprint(w, string(res.Body))
- log.Printf(
- "%s %s (%s) -> %d\n",
- r.Method,
- r.URL.Path,
- time.Now().Sub(start),
- res.Code,
- )
- },
- )
+ routes := make(map[string]bool)
+
+ // define luna.router module
+ routeModule := make(map[string]any)
+ routeModule["route"] = func (l *Lua) int {
+ fn := l.PopToRef()
+ route := l.ToString(-1)
+ // find corresponding worker for the lua context
+ var wrk *Worker
+ for _, wrk = range wrks {
+ if wrk.HasSameLua(l) {
+ wrk.SetRoute(route, fn)
+ break
}
}
+ if wrk == nil {
+ return 0
+ }
+ // check if route is already registered. otherwise
+ // mux.HandleFunc will panic
+ _, ok := routes[route]
+ if ok {
+ return 0
+ }
+ routes[route] = true
+ mux.HandleFunc(
+ route,
+ func (w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ resCh := HandleHTTPRequest(msgs, route, r)
+ res := <- resCh
+ for k, h := range res.Headers {
+ w.Header().Set(k, h)
+ }
+ w.WriteHeader(int(res.Code))
+ fmt.Fprint(w, string(res.Body))
+ log.Printf(
+ "%s %s (%s) -> %d\n",
+ r.Method,
+ r.URL.Path,
+ time.Now().Sub(start),
+ res.Code,
+ )
+ },
+ )
+ return 0
+ }
+ module := make(map[string]any)
+ module["router"] = routeModule
+
+ // start workers
+ for i := 0; i < 8; i++ {
+ wrk := NewWorker()
wrks = append(wrks, wrk)
- go wrk.Listen()
+ go func () {
+ err := wrk.Start("init.lua", module)
+ if err != nil {
+ log.Fatal(err)
+ }
+ wrks = append(wrks, wrk)
+ wrk.Listen(msgs)
+ }()
defer wrk.Stop()
+ log.Printf("worker %d started\n", i)
}
+ // listen for evals from stdio
go func () {
reader := bufio.NewReader(os.Stdin)
for {
- text, _ := reader.ReadString('\n')
- for _, w := range wrks {
- err := <- w.Eval(text)
- if err != nil {
+ code, _ := reader.ReadString('\n')
+ for _, wrk := range wrks {
+ if err := wrk.Run(code); err != nil {
log.Printf("error: %s\n", err)
break
}
}
}
}()
- fmt.Println("luna is running...")
+
+ fmt.Println("luna is running at 127.0.0.1:3002...")
log.Fatal(http.ListenAndServe("127.0.0.1:3002", mux))
}
diff --git a/worker.go b/worker.go
index 646efea..b79f7e8 100644
--- a/worker.go
+++ b/worker.go
@@ -5,6 +5,7 @@ import (
"io"
"log"
"net/http"
+ "sync"
)
type HTTPRequest struct {
@@ -14,11 +15,6 @@ type HTTPRequest struct {
result chan *HTTPResponse
}
-type EvalRequest struct {
- code string
- result chan error
-}
-
type HTTPResponse struct {
Code int
Headers map[string]string
@@ -26,58 +22,70 @@ type HTTPResponse struct {
}
type Worker struct {
- read chan interface{}
lua *Lua
- api LuaRef
routes map[string]LuaRef
started bool
+ mu sync.Mutex
+}
+
+func HandleHTTPRequest(
+ queue chan any,
+ route string,
+ req *http.Request,
+) chan *HTTPResponse {
+ res := make(chan *HTTPResponse)
+ queue <- &HTTPRequest{
+ request: req,
+ route: route,
+ result: res,
+ }
+ return res
}
-func NewWorker(read chan interface{}) *Worker {
+func NewWorker() *Worker {
return &Worker {
- read: read,
routes: make(map[string]LuaRef),
lua: &Lua{},
}
}
-func (w *Worker) Start(filename string) error {
+func (w *Worker) Start(filename string, module map[string]any) error {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+
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
- err = w.initRoutes()
+ defer w.lua.RestoreStackFunc()()
+ w.initLunaModule(module)
+ err := w.lua.Require(filename)
if err != nil {
return err
}
-
+ w.started = true
return nil
}
-func (w *Worker) Listen() {
- for {
- resStack := w.lua.RestoreStackFunc()
- r := <- w.read
+func (w *Worker) Listen(queue chan any) {
+ handle := func() {
+ defer w.lua.RestoreStackFunc()()
+ r := <- queue
switch r.(type) {
case *HTTPRequest:
r := r.(*HTTPRequest)
- // If in debug mode always use handlers from api table
- // instead of cached references. Makes it much easier
- // to hot-replace route handlers.
- if r.debug {
- w.lua.PushFromRef(w.api)
- w.lua.PushTableItem("routes")
- w.lua.PushTableItem(r.route)
- } else {
- w.lua.PushFromRef(w.routes[r.route])
+ if _, ok := w.routes[r.route]; !ok {
+ r.result <- &HTTPResponse {
+ Code: 404,
+ Headers: make(map[string]string),
+ Body: "not found",
+ }
+ log.Println("no corresponding route")
+ return
}
+
+ w.lua.PushFromRef(w.routes[r.route])
w.lua.PushString(r.request.Method)
w.lua.PushString(r.request.URL.Path)
@@ -95,12 +103,22 @@ func (w *Worker) Listen() {
Body: "server error",
}
log.Println("could not read a request body")
- resStack()
- continue
+ return
}
w.lua.PushString(string(body))
- w.lua.PCall(4, 3)
+ err = w.lua.PCall(4, 3)
+
+ if err != nil {
+ r.result <- &HTTPResponse {
+ Code: 500,
+ Headers: make(map[string]string),
+ Body: "server error",
+ }
+ log.Println("could not read a request body")
+ return
+ }
+
code := w.lua.ToInt(-3)
rbody := w.lua.ToString(-1)
@@ -127,81 +145,62 @@ func (w *Worker) Listen() {
Body: rbody,
}
- case *EvalRequest:
- r := r.(*EvalRequest)
- err := w.lua.LoadString(r.code)
- r.result <- err
-
default:
log.Fatal("unknown request")
}
-
- resStack()
}
-}
-func (w *Worker) Request(
- route string,
- r *http.Request,
- debug bool,
-) chan *HTTPResponse {
- res := make(chan *HTTPResponse)
- w.read <- &HTTPRequest{
- request: r,
- route: route,
- debug: debug,
- result: res,
+ for {
+ handle()
}
- return res
}
-func (w *Worker) Eval(code string) chan error {
- res := make(chan error)
- w.read <- &EvalRequest{code: code, result: res}
- return res
+func (w *Worker) Run(code string) error {
+ w.mu.Lock()
+ defer w.mu.Unlock()
+ defer w.lua.RestoreStackFunc()()
+ return w.lua.LoadString(code)
}
-func (w *Worker) ListRoutes() []string {
- res := []string{}
- for route, _ := range w.routes {
- res = append(res, route)
- }
- return res
+func (w *Worker) SetRoute(route string, handler LuaRef) {
+ w.routes[route] = handler
}
func (w *Worker) Stop() {
+ w.mu.Lock()
+ defer w.mu.Unlock()
w.lua.Close()
}
-func (w *Worker) initLunaModule() {
- w.lua.CreateTable(1)
- w.lua.PushGoFunction(func (l *Lua) int {
- l.PushString("Hello, " + l.ToString(-1))
- return 1
- })
- w.lua.SetTableItem("helloFromGo")
+func (w *Worker) initLunaModule(module map[string]any) {
+ var pushTable func(t map[string]any)
+ pushTable = func (t map[string]any) {
+ w.lua.CreateTable(len(t))
+ for k, v := range t {
+ switch v.(type) {
+ case string:
+ v, _ := v.(string)
+ w.lua.PushString(v)
+ case func (l *Lua) int:
+ v, _ := v.(func (l *Lua) int)
+ w.lua.PushGoFunction(v)
+ case int:
+ v, _ := v.(int)
+ w.lua.PushNumber(v)
+ case map[string]any:
+ v, _ := v.(map[string]any)
+ pushTable(v)
+ default:
+ // FIXME: more details
+ log.Fatal("unsupported module value type")
+ }
+ w.lua.SetTableItem(k)
+ }
+ }
+ pushTable(module)
w.lua.SetGlobal("luna")
}
-func (w *Worker) initRoutes() error {
- defer w.lua.RestoreStackFunc()()
- w.lua.PushFromRef(w.api)
- w.lua.PushTableItem("routes")
- if !w.lua.IsTable(-1) {
- return errors.New("\"routes\" must be a table")
- }
- 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
- }
- w.lua.Pop(2)
- return nil
+func (w *Worker) HasSameLua(l *Lua) bool {
+ return w.lua == l
}