181 lines
4.6 KiB
Go
181 lines
4.6 KiB
Go
// server.go - Simplified HTTP server
|
|
package http
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"Moonshark/config"
|
|
"Moonshark/logger"
|
|
"Moonshark/metadata"
|
|
"Moonshark/runner"
|
|
"Moonshark/sessions"
|
|
"Moonshark/utils"
|
|
|
|
"git.sharkk.net/Go/Color"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
var emptyMap = make(map[string]any)
|
|
|
|
func NewHttpServer(cfg *config.Config, handler fasthttp.RequestHandler, dbg bool) *fasthttp.Server {
|
|
return &fasthttp.Server{
|
|
Handler: handler,
|
|
Name: "Moonshark/" + metadata.Version,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
MaxRequestBodySize: 16 << 20,
|
|
TCPKeepalive: true,
|
|
ReduceMemoryUsage: true,
|
|
StreamRequestBody: true,
|
|
NoDefaultServerHeader: true,
|
|
}
|
|
}
|
|
|
|
func NewPublicHandler(pubDir, prefix string) fasthttp.RequestHandler {
|
|
if !strings.HasPrefix(prefix, "/") {
|
|
prefix = "/" + prefix
|
|
}
|
|
if !strings.HasSuffix(prefix, "/") {
|
|
prefix += "/"
|
|
}
|
|
|
|
fs := &fasthttp.FS{
|
|
Root: pubDir,
|
|
IndexNames: []string{"index.html"},
|
|
AcceptByteRange: true,
|
|
Compress: true,
|
|
CompressedFileSuffix: ".gz",
|
|
CompressBrotli: true,
|
|
PathRewrite: fasthttp.NewPathPrefixStripper(len(prefix) - 1),
|
|
}
|
|
return fs.NewRequestHandler()
|
|
}
|
|
|
|
func (s *Server) ListenAndServe(addr string) error {
|
|
logger.Infof("Catch the swell at %s", color.Cyan("http://localhost"+addr))
|
|
return s.fasthttpServer.ListenAndServe(addr)
|
|
}
|
|
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
return s.fasthttpServer.ShutdownWithContext(ctx)
|
|
}
|
|
|
|
func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) {
|
|
start := time.Now()
|
|
method := string(ctx.Method())
|
|
path := string(ctx.Path())
|
|
|
|
// Route lookup
|
|
bytecode, params, found := s.luaRouter.Lookup(method, path)
|
|
if !found {
|
|
s.send404(ctx)
|
|
s.logRequest(ctx, method, path, time.Since(start))
|
|
return
|
|
}
|
|
|
|
if len(bytecode) == 0 {
|
|
s.send500(ctx, nil)
|
|
s.logRequest(ctx, method, path, time.Since(start))
|
|
return
|
|
}
|
|
|
|
// Get session
|
|
session := s.sessionManager.GetSessionFromRequest(ctx)
|
|
|
|
// Execute Lua script
|
|
response, err := s.luaRunner.ExecuteHTTP(bytecode, ctx, params, session)
|
|
if err != nil {
|
|
logger.Errorf("Lua execution error: %v", err)
|
|
s.send500(ctx, err)
|
|
s.logRequest(ctx, method, path, time.Since(start))
|
|
return
|
|
}
|
|
|
|
// Apply response
|
|
s.applyResponse(ctx, response, session)
|
|
runner.ReleaseResponse(response)
|
|
|
|
s.logRequest(ctx, method, path, time.Since(start))
|
|
}
|
|
|
|
func (s *Server) applyResponse(ctx *fasthttp.RequestCtx, resp *runner.Response, session *sessions.Session) {
|
|
// Handle session updates
|
|
if len(resp.SessionData) > 0 {
|
|
if _, clearAll := resp.SessionData["__clear_all"]; clearAll {
|
|
session.Clear()
|
|
session.ClearFlash()
|
|
delete(resp.SessionData, "__clear_all")
|
|
}
|
|
|
|
for k, v := range resp.SessionData {
|
|
if v == "__DELETE__" {
|
|
session.Delete(k)
|
|
} else {
|
|
session.Set(k, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle flash data
|
|
if flashData, ok := resp.Metadata["flash"].(map[string]any); ok {
|
|
for k, v := range flashData {
|
|
if err := session.FlashSafe(k, v); err != nil && s.debugMode {
|
|
logger.Warnf("Error setting flash data %s: %v", k, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply session cookie
|
|
s.sessionManager.ApplySessionCookie(ctx, session)
|
|
|
|
// Apply HTTP response
|
|
runner.ApplyResponse(resp, ctx)
|
|
}
|
|
|
|
func (s *Server) send404(ctx *fasthttp.RequestCtx) {
|
|
ctx.SetContentType("text/html; charset=utf-8")
|
|
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
|
cacheMu.RLock()
|
|
ctx.SetBody(cached404)
|
|
cacheMu.RUnlock()
|
|
}
|
|
|
|
func (s *Server) send500(ctx *fasthttp.RequestCtx, err error) {
|
|
ctx.SetContentType("text/html; charset=utf-8")
|
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
|
|
|
if err == nil {
|
|
cacheMu.RLock()
|
|
ctx.SetBody(cached500)
|
|
cacheMu.RUnlock()
|
|
} else {
|
|
errorConfig := utils.ErrorPageConfig{
|
|
OverrideDir: s.cfg.Dirs.Override,
|
|
DebugMode: s.debugMode,
|
|
}
|
|
ctx.SetBody([]byte(utils.InternalErrorPage(errorConfig, string(ctx.Path()), err.Error())))
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) {
|
|
stats := utils.CollectSystemStats(s.cfg)
|
|
stats.Components = utils.ComponentStats{
|
|
RouteCount: 0, // TODO: Get from router
|
|
BytecodeBytes: 0, // TODO: Get from router
|
|
SessionStats: s.sessionManager.GetCacheStats(),
|
|
}
|
|
ctx.SetContentType("text/html; charset=utf-8")
|
|
ctx.SetStatusCode(fasthttp.StatusOK)
|
|
ctx.SetBody([]byte(utils.DebugStatsPage(stats)))
|
|
}
|
|
|
|
func (s *Server) logRequest(ctx *fasthttp.RequestCtx, method, path string, duration time.Duration) {
|
|
if s.cfg.Server.HTTPLogging {
|
|
logger.Request(ctx.Response.StatusCode(), method, path, duration)
|
|
}
|
|
}
|