package main import ( "bufio" "database/sql" "errors" "flag" "fmt" "io" "log" "net/http" "os" "runtime" "runtime/cgo" "strings" "sync" "time" _ "github.com/mattn/go-sqlite3" ) func main() { lAddr := flag.String("l", "127.0.0.1:3000", "Address HTTP-server will listen to") wrksNum := flag.Int("n", runtime.NumCPU(), "Number of HTTP-workers to start") flag.Parse() if flag.NArg() == 0 { printUsage() } luaArgv := flag.Args() mustExist(luaArgv[0]) httpClient := &http.Client{} // queue for http messages for workers to handle msgs := make(chan any, 4096) mux := http.NewServeMux() wrks := []*Worker{} // track routes for mux to avoid registering the same route twice routes := make(map[string]bool) // define luna.router module routeModule := make(map[string]any) routeModule["route"] = func (l *Lua) int { var route string var fn LuaRef err := l.Scan(&route, &fn) if err != nil { fmt.Println("error:", err) // FIXME: handle return 0 } // 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 } // define luna.http module httpModule := make(map[string]any) httpModule["request"] = func (l *Lua) int { var method, url, body string headers := make(map[string]string) err := l.Scan(&method, &url, &headers, &body) if err != nil { // FIXME: handle fmt.Println("error:", err) return 0 } req, err := http.NewRequest(method, url, strings.NewReader(body)) if err != nil { // FIXME: return error return 0 } for k, v := range headers { req.Header.Add(k, v) } resp, err := httpClient.Do(req) if err != nil { // FIXME: return error return 0 } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { // FIXME: return error return 0 } respHeaders := make(map[string]any) for k := range resp.Header { respHeaders[k] = resp.Header.Get(k) } l.PushNumber(resp.StatusCode) l.PushObject(respHeaders) l.PushString(string(respBody)) return 3 } // define luna.db module dbModule := make(map[string]any) dbModule["open"] = func (l *Lua) int { var file string err := l.Scan(&file) if err != nil { // FIXME: handle. return 0 } r, err := sql.Open("sqlite3", file) if err != nil { // FIXME: handle. return 0 } h := cgo.NewHandle(r) l.PushNumber(int(h)) return 1 } dbModule["begin"] = func (l *Lua) int { var handle cgo.Handle err := l.Scan(&handle) if err != nil { // FIXME: handle. return 0 } db := handle.Value().(*sql.DB) tx, err := db.Begin() if err != nil { // FIXME: handle. return 0 } txh := cgo.NewHandle(tx) l.PushNumber(int(txh)) return 1 } dbModule["commit"] = func (l *Lua) int { var handle cgo.Handle err := l.Scan(&handle) if err != nil { // FIXME: handle. return 0 } tx := handle.Value().(*sql.Tx) err = tx.Commit() if err != nil { // FIXME: handle. return 0 } handle.Delete() return 0 } dbModule["rollback"] = func (l *Lua) int { var handle cgo.Handle err := l.Scan(&handle) if err != nil { // FIXME: handle. return 0 } tx := handle.Value().(*sql.Tx) err = tx.Rollback() if err != nil { // FIXME: handle. return 0 } handle.Delete() return 0 } dbModule["exec-tx"] = func (l *Lua) int { var handle cgo.Handle var query string var params []any err := l.Scan(&handle, &query, ¶ms) if err != nil { fmt.Println(err) // FIXME: handle. return 0 } tx := handle.Value().(*sql.Tx) _, err = tx.Exec(query, params...) if err != nil { fmt.Println(err) // FIXME: handle. return 0 } return 0 } dbModule["exec"] = func (l *Lua) int { var handle cgo.Handle var query string var params []any err := l.Scan(&handle, &query, ¶ms) if err != nil { fmt.Println(err) // FIXME: handle. return 0 } db := handle.Value().(*sql.DB) _, err = db.Exec(query, params...) if err != nil { fmt.Println(err) // FIXME: handle. return 0 } return 0 } dbModule["query"] = func (l *Lua) int { var handle cgo.Handle var query string var params []any err := l.Scan(&handle, &query, ¶ms) if err != nil { fmt.Println(err) // FIXME: handle. return 0 } db := handle.Value().(*sql.DB) rows, err := db.Query(query, params...) if err != nil { // FIXME: handle. return 0 } var res [][]any cols, _ := rows.Columns() for rows.Next() { scans := make([]any, len(cols)) for i, _ := range scans { scans[i] = &scans[i] } var row []any rows.Scan(scans...) for _, v := range scans { row = append(row, v) } res = append(res, row) } var ares []any for _, v := range res { ares = append(ares, v) } err = l.PushArray(ares) if err != nil { fmt.Println(err) // FIXME: handle return 0 } return 1 } dbModule["query*"] = func (l *Lua) int { var handle cgo.Handle var query string var params []any err := l.Scan(&handle, &query, ¶ms) if err != nil { // FIXME: handle. return 0 } db := handle.Value().(*sql.DB) rows, err := db.Query(query, params...) if err != nil { // FIXME: handle. return 0 } var res []map[string]any cols, _ := rows.Columns() for rows.Next() { scans := make([]any, len(cols)) for i, _ := range scans { scans[i] = &scans[i] } row := make(map[string]any) rows.Scan(scans...) for i, v := range scans { row[cols[i]] = v } res = append(res, row) } var ares []any for _, v := range res { ares = append(ares, v) } err = l.PushArray(ares) if err != nil { // FIXME: handle return 0 } return 1 } dbModule["close"] = func (l *Lua) int { var handle cgo.Handle err := l.Scan(&handle) if err != nil { // FIXME: handle. return 0 } db := handle.Value().(*sql.DB) err = db.Close() if err != nil { // FIXME: handle. return 0 } handle.Delete() return 0 } module := make(map[string]any) module["router"] = routeModule module["http"] = httpModule module["db"] = dbModule wg := sync.WaitGroup{} wg.Add(*wrksNum) // start workers for i := 0; i < *wrksNum; i++ { wrk := NewWorker() wrks = append(wrks, wrk) go func () { err := wrk.Start(luaArgv, module) if err != nil { log.Fatal(err) } wrks = append(wrks, wrk) log.Printf("worker %d started\n", i) wg.Add(-1) wrk.Listen(msgs) }() defer wrk.Stop() } wg.Wait() // listen for evals from stdio go func () { reader := bufio.NewReader(os.Stdin) for { code, _ := reader.ReadString('\n') if len(code) == 0 { continue } for _, wrk := range wrks { if err := wrk.Eval(code); err != nil { log.Printf("error: %s\n", err) break } } } }() if len(routes) > 0 { log.Printf("luna is running at %s...", *lAddr) log.Fatal(http.ListenAndServe(*lAddr, mux)) } log.Printf("no HTTP-routes were registered, exiting...") } func printUsage() { fmt.Printf("usage: %s [luafile]\n", os.Args[0]) flag.PrintDefaults() os.Exit(2) } func mustExist(file string) { if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) { fmt.Printf("file \"%s\" does not exist\n", file) os.Exit(1) } }