package main import ( "bufio" "errors" "flag" "fmt" "io" "log" "net/http" "os" "runtime" "strings" "time" ) 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() != 1 { printUsage() } luaExe := flag.Arg(0) mustExist(luaExe) httpClient := &http.Client{} // queue for http messages for workers to handle msgs := make(chan interface{}, 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 { fn := l.PopToRef() route := l.ToString(-1) l.Pop(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 } // define luna.http module httpModule := make(map[string]any) httpModule["request"] = func (l *Lua) int { body := l.ToString(-1) url := l.ToString(-3) method := l.ToString(-4) l.Pop(1) headers := make(map[string]string) l.PushNil() for l.Next() { if !l.IsString(-2) || !l.IsString(-2) { l.Pop(1) continue } v := l.ToString(-1) l.Pop(1) // We must not pop the item key from the stack // because otherwise C.lua_next won't work // properly. k := l.ToString(-1) headers[k] = v } l.Pop(2) 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 } module := make(map[string]any) module["router"] = routeModule module["http"] = httpModule // start workers for i := 0; i < *wrksNum; i++ { wrk := NewWorker() wrks = append(wrks, wrk) go func () { err := wrk.Start(luaExe, module) if err != nil { log.Fatal(err) } wrks = append(wrks, wrk) log.Printf("worker %d started\n", i) wrk.Listen(msgs) }() defer wrk.Stop() } // 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 } } } }() log.Printf("luna is running at %s...", *lAddr) log.Fatal(http.ListenAndServe(*lAddr, mux)) } 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) } }