use fasthttp static file serving

This commit is contained in:
Sky Johnson 2025-07-17 19:11:01 -05:00
parent e95f0f3370
commit 12ba756b95
2 changed files with 79 additions and 46 deletions

View File

@ -4,7 +4,9 @@ import (
"context"
"fmt"
"net"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
@ -18,6 +20,8 @@ var (
globalStateCreator StateCreator
globalMu sync.RWMutex
serverRunning bool
staticHandlers = make(map[string]*fasthttp.FS)
staticMu sync.RWMutex
)
func SetStateCreator(creator StateCreator) {
@ -26,11 +30,12 @@ func SetStateCreator(creator StateCreator) {
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_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,
}
}
@ -162,6 +167,32 @@ func http_has_servers(s *luajit.State) int {
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()
@ -175,6 +206,22 @@ func WaitForServers() {
}
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()
@ -230,3 +277,19 @@ func handleRequest(ctx *fasthttp.RequestCtx) {
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
}

View File

@ -523,48 +523,18 @@ function http.cors(options)
end
end
function http.static(root_path)
function http.static(root_path, url_prefix)
url_prefix = url_prefix or "/"
if not _G.__IS_WORKER then
local success, err = moonshark.http_register_static(url_prefix, root_path)
if not success then
error("Failed to register static handler: " .. (err or "unknown error"))
end
end
-- Return no-op middleware
return function(req, res, next)
if req.method ~= "GET" and req.method ~= "HEAD" then
next()
return
end
local file_path = moonshark.path_join(root_path, req.path)
file_path = moonshark.path_clean(file_path)
local abs_root = moonshark.path_abs(root_path)
local abs_file = moonshark.path_abs(file_path)
if not abs_file or not abs_file:find("^" .. abs_root:gsub("([%(%)%.%+%-%*%?%[%]%^%$%%])", "%%%1")) then
next()
return
end
if moonshark.file_exists(file_path) and not moonshark.file_is_dir(file_path) then
local content = moonshark.file_read(file_path)
if content then
local ext = moonshark.path_ext(file_path):lower()
local content_types = {
[".html"] = "text/html",
[".css"] = "text/css",
[".js"] = "application/javascript",
[".json"] = "application/json",
[".png"] = "image/png",
[".jpg"] = "image/jpeg",
[".jpeg"] = "image/jpeg",
[".gif"] = "image/gif",
[".svg"] = "image/svg+xml",
[".webp"] = "image/webp",
[".txt"] = "text/plain",
}
local content_type = content_types[ext] or "application/octet-stream"
res:type(content_type):send(content)
return
end
end
next()
end
end