296 lines
5.9 KiB
Go
296 lines
5.9 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
var (
|
|
globalServer *fasthttp.Server
|
|
globalWorkerPool *WorkerPool
|
|
globalStateCreator StateCreator
|
|
globalMu sync.RWMutex
|
|
serverRunning bool
|
|
staticHandlers = make(map[string]*fasthttp.FS)
|
|
staticMu sync.RWMutex
|
|
)
|
|
|
|
func SetStateCreator(creator StateCreator) {
|
|
globalStateCreator = creator
|
|
}
|
|
|
|
func GetFunctionList() map[string]luajit.GoFunction {
|
|
return map[string]luajit.GoFunction{
|
|
"http_create_server": http_create_server,
|
|
"http_spawn_workers": http_spawn_workers,
|
|
"http_listen": http_listen,
|
|
"http_close_server": http_close_server,
|
|
"http_has_servers": http_has_servers,
|
|
"http_register_static": http_register_static,
|
|
}
|
|
}
|
|
|
|
func http_create_server(s *luajit.State) int {
|
|
globalMu.Lock()
|
|
defer globalMu.Unlock()
|
|
|
|
if globalServer != nil {
|
|
s.PushBoolean(true) // Already created
|
|
return 1
|
|
}
|
|
|
|
globalServer = &fasthttp.Server{
|
|
Handler: handleRequest,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
s.PushBoolean(true)
|
|
return 1
|
|
}
|
|
|
|
func http_spawn_workers(s *luajit.State) int {
|
|
globalMu.Lock()
|
|
defer globalMu.Unlock()
|
|
|
|
if globalWorkerPool != nil {
|
|
s.PushBoolean(true) // Already spawned
|
|
return 1
|
|
}
|
|
|
|
if globalStateCreator == nil {
|
|
s.PushBoolean(false)
|
|
s.PushString("state creator not set")
|
|
return 2
|
|
}
|
|
|
|
workerCount := max(runtime.NumCPU(), 2)
|
|
|
|
pool, err := NewWorkerPool(workerCount, s, globalStateCreator)
|
|
if err != nil {
|
|
s.PushBoolean(false)
|
|
s.PushString(fmt.Sprintf("failed to create worker pool: %v", err))
|
|
return 2
|
|
}
|
|
globalWorkerPool = pool
|
|
|
|
s.PushBoolean(true)
|
|
return 1
|
|
}
|
|
|
|
func http_listen(s *luajit.State) int {
|
|
if err := s.CheckMinArgs(1); err != nil {
|
|
return s.PushError("http_listen: %v", err)
|
|
}
|
|
|
|
addr := s.ToString(1)
|
|
|
|
globalMu.RLock()
|
|
server := globalServer
|
|
globalMu.RUnlock()
|
|
|
|
if server == nil {
|
|
s.PushBoolean(false)
|
|
s.PushString("no server created")
|
|
return 2
|
|
}
|
|
|
|
globalMu.Lock()
|
|
if serverRunning {
|
|
globalMu.Unlock()
|
|
s.PushBoolean(true) // Already running
|
|
return 1
|
|
}
|
|
serverRunning = true
|
|
globalMu.Unlock()
|
|
|
|
go func() {
|
|
if err := server.ListenAndServe(addr); err != nil {
|
|
fmt.Printf("HTTP server error: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
conn, err := net.Dial("tcp", addr)
|
|
if err != nil {
|
|
globalMu.Lock()
|
|
serverRunning = false
|
|
globalMu.Unlock()
|
|
s.PushBoolean(false)
|
|
s.PushString(fmt.Sprintf("failed to start server: %v", err))
|
|
return 2
|
|
}
|
|
conn.Close()
|
|
|
|
s.PushBoolean(true)
|
|
return 1
|
|
}
|
|
|
|
func http_close_server(s *luajit.State) int {
|
|
globalMu.Lock()
|
|
defer globalMu.Unlock()
|
|
|
|
if globalWorkerPool != nil {
|
|
globalWorkerPool.Close()
|
|
globalWorkerPool = nil
|
|
}
|
|
|
|
if globalServer != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
globalServer.ShutdownWithContext(ctx)
|
|
globalServer = nil
|
|
}
|
|
|
|
serverRunning = false
|
|
s.PushBoolean(true)
|
|
return 1
|
|
}
|
|
|
|
func http_has_servers(s *luajit.State) int {
|
|
globalMu.RLock()
|
|
running := serverRunning
|
|
globalMu.RUnlock()
|
|
|
|
s.PushBoolean(running)
|
|
return 1
|
|
}
|
|
|
|
func http_register_static(s *luajit.State) int {
|
|
if err := s.CheckMinArgs(2); err != nil {
|
|
return s.PushError("http_register_static: %v", err)
|
|
}
|
|
|
|
urlPrefix := s.ToString(1)
|
|
rootPath := s.ToString(2)
|
|
|
|
// Ensure prefix starts with /
|
|
if !strings.HasPrefix(urlPrefix, "/") {
|
|
urlPrefix = "/" + urlPrefix
|
|
}
|
|
|
|
// Convert to absolute path
|
|
absPath, err := filepath.Abs(rootPath)
|
|
if err != nil {
|
|
s.PushBoolean(false)
|
|
s.PushString(fmt.Sprintf("invalid path: %v", err))
|
|
return 2
|
|
}
|
|
|
|
RegisterStaticHandler(urlPrefix, absPath)
|
|
s.PushBoolean(true)
|
|
return 1
|
|
}
|
|
|
|
func HasActiveServers() bool {
|
|
globalMu.RLock()
|
|
defer globalMu.RUnlock()
|
|
return serverRunning
|
|
}
|
|
|
|
func WaitForServers() {
|
|
for HasActiveServers() {
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func handleRequest(ctx *fasthttp.RequestCtx) {
|
|
path := string(ctx.Path())
|
|
|
|
// Check static handlers first
|
|
staticMu.RLock()
|
|
for prefix, fs := range staticHandlers {
|
|
if strings.HasPrefix(path, prefix) {
|
|
staticMu.RUnlock()
|
|
// Remove prefix and serve
|
|
ctx.Request.URI().SetPath(strings.TrimPrefix(path, prefix))
|
|
fs.NewRequestHandler()(ctx)
|
|
return
|
|
}
|
|
}
|
|
staticMu.RUnlock()
|
|
|
|
// Fall back to Lua handling
|
|
globalMu.RLock()
|
|
pool := globalWorkerPool
|
|
globalMu.RUnlock()
|
|
|
|
if pool == nil {
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
ctx.SetBodyString("Worker pool not initialized")
|
|
return
|
|
}
|
|
|
|
worker := pool.Get()
|
|
defer pool.Put(worker)
|
|
|
|
if worker == nil {
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
ctx.SetBodyString("No worker available")
|
|
return
|
|
}
|
|
|
|
req := GetRequest()
|
|
defer PutRequest(req)
|
|
|
|
resp := GetResponse()
|
|
defer PutResponse(resp)
|
|
|
|
// Populate request
|
|
req.Method = string(ctx.Method())
|
|
req.Path = string(ctx.Path())
|
|
req.Body = string(ctx.Request.Body())
|
|
|
|
ctx.QueryArgs().VisitAll(func(key, value []byte) {
|
|
req.Query[string(key)] = string(value)
|
|
})
|
|
|
|
ctx.Request.Header.VisitAll(func(key, value []byte) {
|
|
req.Headers[string(key)] = string(value)
|
|
})
|
|
|
|
// Let Lua handle everything
|
|
err := worker.HandleRequest(req, resp)
|
|
if err != nil {
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
ctx.SetBodyString(fmt.Sprintf("Internal Server Error: %v", err))
|
|
return
|
|
}
|
|
|
|
// Apply response
|
|
ctx.SetStatusCode(resp.StatusCode)
|
|
for key, value := range resp.Headers {
|
|
ctx.Response.Header.Set(key, value)
|
|
}
|
|
if resp.Body != "" {
|
|
ctx.SetBodyString(resp.Body)
|
|
}
|
|
}
|
|
|
|
// RegisterStaticHandler adds a static file handler
|
|
func RegisterStaticHandler(urlPrefix, rootPath string) {
|
|
staticMu.Lock()
|
|
defer staticMu.Unlock()
|
|
|
|
fs := &fasthttp.FS{
|
|
Root: rootPath,
|
|
IndexNames: []string{"index.html"},
|
|
GenerateIndexPages: false,
|
|
Compress: true,
|
|
AcceptByteRange: true,
|
|
}
|
|
|
|
staticHandlers[urlPrefix] = fs
|
|
}
|