server 1
This commit is contained in:
parent
c48ab8c433
commit
75a307551c
44
core/http/httplogger.go
Normal file
44
core/http/httplogger.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.sharkk.net/Sky/Moonshark/core/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatusColors for different status code ranges
|
||||||
|
const (
|
||||||
|
colorGreen = "\033[32m" // 2xx - Success
|
||||||
|
colorCyan = "\033[36m" // 3xx - Redirection
|
||||||
|
colorYellow = "\033[33m" // 4xx - Client Errors
|
||||||
|
colorRed = "\033[31m" // 5xx - Server Errors
|
||||||
|
colorReset = "\033[0m" // Reset color
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogRequest logs an HTTP request with custom formatting
|
||||||
|
func LogRequest(log *logger.Logger, statusCode int, r *http.Request, duration time.Duration) {
|
||||||
|
statusColor := getStatusColor(statusCode)
|
||||||
|
|
||||||
|
// Use the logger's raw message writer to bypass the standard format
|
||||||
|
log.LogRaw("%s [%s%d%s] %s %s (%v)",
|
||||||
|
time.Now().Format(log.TimeFormat()),
|
||||||
|
statusColor, statusCode, colorReset,
|
||||||
|
r.Method, r.URL.Path, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStatusColor returns the ANSI color code for a status code
|
||||||
|
func getStatusColor(code int) string {
|
||||||
|
switch {
|
||||||
|
case code >= 200 && code < 300:
|
||||||
|
return colorGreen
|
||||||
|
case code >= 300 && code < 400:
|
||||||
|
return colorCyan
|
||||||
|
case code >= 400 && code < 500:
|
||||||
|
return colorYellow
|
||||||
|
case code >= 500:
|
||||||
|
return colorRed
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
184
core/http/server.go
Normal file
184
core/http/server.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.sharkk.net/Sky/Moonshark/core/logger"
|
||||||
|
"git.sharkk.net/Sky/Moonshark/core/routers"
|
||||||
|
"git.sharkk.net/Sky/Moonshark/core/workers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server handles HTTP requests using Lua and static file routers
|
||||||
|
type Server struct {
|
||||||
|
luaRouter *routers.LuaRouter
|
||||||
|
staticRouter *routers.StaticRouter
|
||||||
|
workerPool *workers.Pool
|
||||||
|
logger *logger.Logger
|
||||||
|
httpServer *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new HTTP server
|
||||||
|
func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter, pool *workers.Pool, log *logger.Logger) *Server {
|
||||||
|
server := &Server{
|
||||||
|
luaRouter: luaRouter,
|
||||||
|
staticRouter: staticRouter,
|
||||||
|
workerPool: pool,
|
||||||
|
logger: log,
|
||||||
|
httpServer: &http.Server{},
|
||||||
|
}
|
||||||
|
server.httpServer.Handler = server
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe starts the server on the given address
|
||||||
|
func (s *Server) ListenAndServe(addr string) error {
|
||||||
|
s.httpServer.Addr = addr
|
||||||
|
s.logger.Info("Server starting on %s", addr)
|
||||||
|
return s.httpServer.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully shuts down the server
|
||||||
|
func (s *Server) Shutdown(ctx context.Context) error {
|
||||||
|
s.logger.Info("Server shutting down...")
|
||||||
|
return s.httpServer.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP handles HTTP requests
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Wrap the ResponseWriter to capture status code
|
||||||
|
wrappedWriter := newStatusCaptureWriter(w)
|
||||||
|
|
||||||
|
// Process the request
|
||||||
|
s.handleRequest(wrappedWriter, r)
|
||||||
|
|
||||||
|
// Calculate request duration
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
// Get the status code
|
||||||
|
statusCode := wrappedWriter.StatusCode()
|
||||||
|
|
||||||
|
// Log the request with our custom format
|
||||||
|
LogRequest(s.logger, statusCode, r, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRequest processes the actual request
|
||||||
|
func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.logger.Debug("Processing request %s %s", r.Method, r.URL.Path)
|
||||||
|
|
||||||
|
// Try Lua routes first
|
||||||
|
params := &routers.Params{}
|
||||||
|
if bytecode, found := s.luaRouter.GetBytecode(r.Method, r.URL.Path, params); found {
|
||||||
|
s.logger.Debug("Found Lua route match for %s %s with %d params", r.Method, r.URL.Path, params.Count)
|
||||||
|
s.handleLuaRoute(w, r, bytecode, params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try static files
|
||||||
|
if filePath, found := s.staticRouter.Match(r.URL.Path); found {
|
||||||
|
http.ServeFile(w, r, filePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No route found
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleLuaRoute executes a Lua route
|
||||||
|
func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode []byte, params *routers.Params) {
|
||||||
|
ctx := workers.NewContext()
|
||||||
|
|
||||||
|
// Log bytecode size
|
||||||
|
s.logger.Debug("Executing Lua route with %d bytes of bytecode", len(bytecode))
|
||||||
|
|
||||||
|
// Add request info directly to context
|
||||||
|
ctx.Set("method", r.Method)
|
||||||
|
ctx.Set("path", r.URL.Path)
|
||||||
|
ctx.Set("host", r.Host)
|
||||||
|
ctx.Set("headers", makeHeaderMap(r.Header))
|
||||||
|
|
||||||
|
// Add URL parameters
|
||||||
|
if params.Count > 0 {
|
||||||
|
paramMap := make(map[string]any, params.Count)
|
||||||
|
for i := 0; i < params.Count; i++ {
|
||||||
|
paramMap[params.Keys[i]] = params.Values[i]
|
||||||
|
}
|
||||||
|
ctx.Set("params", paramMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add query parameters
|
||||||
|
if queryParams := QueryToLua(r); queryParams != nil {
|
||||||
|
ctx.Set("query", queryParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add form data
|
||||||
|
if r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch {
|
||||||
|
if formData, err := ParseForm(r); err == nil && len(formData) > 0 {
|
||||||
|
ctx.Set("form", formData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute Lua script
|
||||||
|
result, err := s.workerPool.Submit(bytecode, ctx)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Error executing Lua route: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeResponse(w, result, s.logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeHeaderMap converts HTTP headers to a map
|
||||||
|
func makeHeaderMap(header http.Header) map[string]any {
|
||||||
|
result := make(map[string]any, len(header))
|
||||||
|
for name, values := range header {
|
||||||
|
if len(values) == 1 {
|
||||||
|
result[name] = values[0]
|
||||||
|
} else {
|
||||||
|
result[name] = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeResponse writes the Lua result to the HTTP response
|
||||||
|
func writeResponse(w http.ResponseWriter, result any, log *logger.Logger) {
|
||||||
|
if result == nil {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch res := result.(type) {
|
||||||
|
case string:
|
||||||
|
// String result
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.Write([]byte(res))
|
||||||
|
|
||||||
|
case map[string]any:
|
||||||
|
// Table result - convert to JSON
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to marshal response: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Other result types - convert to JSON
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to marshal response: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
}
|
33
core/http/status.go
Normal file
33
core/http/status.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// statusCaptureWriter is a ResponseWriter that captures the status code
|
||||||
|
type statusCaptureWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader captures the status code and passes it to the wrapped ResponseWriter
|
||||||
|
func (w *statusCaptureWriter) WriteHeader(code int) {
|
||||||
|
w.statusCode = code
|
||||||
|
w.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusCode returns the captured status code
|
||||||
|
func (w *statusCaptureWriter) StatusCode() int {
|
||||||
|
if w.statusCode == 0 {
|
||||||
|
return http.StatusOK // Default to 200 if not explicitly set
|
||||||
|
}
|
||||||
|
return w.statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStatusCaptureWriter creates a new statusCaptureWriter
|
||||||
|
func newStatusCaptureWriter(w http.ResponseWriter) *statusCaptureWriter {
|
||||||
|
return &statusCaptureWriter{
|
||||||
|
ResponseWriter: w,
|
||||||
|
statusCode: 0,
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ const timeFormat = "15:04:05"
|
||||||
type logMessage struct {
|
type logMessage struct {
|
||||||
level int
|
level int
|
||||||
message string
|
message string
|
||||||
|
rawMode bool // Indicates if raw formatting should be used
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger handles logging operations
|
// Logger handles logging operations
|
||||||
|
@ -84,11 +85,11 @@ func (l *Logger) SetOutput(w io.Writer) {
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
l.writer = w
|
l.writer = w
|
||||||
// Disable colors if not writing to a terminal
|
}
|
||||||
if _, ok := w.(*os.File); !ok {
|
|
||||||
// Don't auto-disable colors anymore - let the caller control this
|
// TimeFormat returns the current time format
|
||||||
// l.useColors = false
|
func (l *Logger) TimeFormat() string {
|
||||||
}
|
return l.timeFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTimeFormat changes the time format string
|
// SetTimeFormat changes the time format string
|
||||||
|
@ -139,16 +140,23 @@ func (l *Logger) processLogs() {
|
||||||
|
|
||||||
// writeMessage writes a formatted log message
|
// writeMessage writes a formatted log message
|
||||||
func (l *Logger) writeMessage(msg logMessage) {
|
func (l *Logger) writeMessage(msg logMessage) {
|
||||||
now := time.Now().Format(l.timeFormat)
|
|
||||||
props := levelProps[msg.level]
|
|
||||||
|
|
||||||
var logLine string
|
var logLine string
|
||||||
if l.useColors {
|
|
||||||
logLine = fmt.Sprintf("%s %s[%s]%s %s\n",
|
if msg.rawMode {
|
||||||
now, props.color, props.tag, colorReset, msg.message)
|
// Raw mode - message is already formatted, just append newline
|
||||||
|
logLine = msg.message + "\n"
|
||||||
} else {
|
} else {
|
||||||
logLine = fmt.Sprintf("%s [%s] %s\n",
|
// Standard format with timestamp, level tag, and message
|
||||||
now, props.tag, msg.message)
|
now := time.Now().Format(l.timeFormat)
|
||||||
|
props := levelProps[msg.level]
|
||||||
|
|
||||||
|
if l.useColors {
|
||||||
|
logLine = fmt.Sprintf("%s %s[%s]%s %s\n",
|
||||||
|
now, props.color, props.tag, colorReset, msg.message)
|
||||||
|
} else {
|
||||||
|
logLine = fmt.Sprintf("%s [%s] %s\n",
|
||||||
|
now, props.tag, msg.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronize writing
|
// Synchronize writing
|
||||||
|
@ -165,7 +173,7 @@ func (l *Logger) writeMessage(msg logMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// log sends a message to the logger goroutine
|
// log sends a message to the logger goroutine
|
||||||
func (l *Logger) log(level int, format string, args ...interface{}) {
|
func (l *Logger) log(level int, format string, args ...any) {
|
||||||
if level < l.level {
|
if level < l.level {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -179,11 +187,11 @@ func (l *Logger) log(level int, format string, args ...interface{}) {
|
||||||
|
|
||||||
// Don't block if channel is full
|
// Don't block if channel is full
|
||||||
select {
|
select {
|
||||||
case l.messages <- logMessage{level: level, message: message}:
|
case l.messages <- logMessage{level: level, message: message, rawMode: false}:
|
||||||
// Message sent
|
// Message sent
|
||||||
default:
|
default:
|
||||||
// Channel full, write directly
|
// Channel full, write directly
|
||||||
l.writeMessage(logMessage{level: level, message: message})
|
l.writeMessage(logMessage{level: level, message: message, rawMode: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit on fatal errors
|
// Exit on fatal errors
|
||||||
|
@ -193,28 +201,83 @@ func (l *Logger) log(level int, format string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogRaw logs a message with raw formatting, bypassing the standard format
|
||||||
|
func (l *Logger) LogRaw(format string, args ...any) {
|
||||||
|
// Use info level for filtering
|
||||||
|
if LevelInfo < l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var message string
|
||||||
|
if len(args) > 0 {
|
||||||
|
message = fmt.Sprintf(format, args...)
|
||||||
|
} else {
|
||||||
|
message = format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't apply colors if disabled
|
||||||
|
if !l.useColors {
|
||||||
|
// Strip ANSI color codes if colors are disabled
|
||||||
|
// Simple approach to strip common ANSI codes
|
||||||
|
message = removeAnsiColors(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't block if channel is full
|
||||||
|
select {
|
||||||
|
case l.messages <- logMessage{level: LevelInfo, message: message, rawMode: true}:
|
||||||
|
// Message sent
|
||||||
|
default:
|
||||||
|
// Channel full, write directly
|
||||||
|
l.writeMessage(logMessage{level: LevelInfo, message: message, rawMode: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple helper to remove ANSI color codes
|
||||||
|
func removeAnsiColors(s string) string {
|
||||||
|
result := ""
|
||||||
|
inEscape := false
|
||||||
|
|
||||||
|
for _, c := range s {
|
||||||
|
if inEscape {
|
||||||
|
if c == 'm' {
|
||||||
|
inEscape = false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == '\033' {
|
||||||
|
inEscape = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result += string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Debug logs a debug message
|
// Debug logs a debug message
|
||||||
func (l *Logger) Debug(format string, args ...interface{}) {
|
func (l *Logger) Debug(format string, args ...any) {
|
||||||
l.log(LevelDebug, format, args...)
|
l.log(LevelDebug, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs an informational message
|
// Info logs an informational message
|
||||||
func (l *Logger) Info(format string, args ...interface{}) {
|
func (l *Logger) Info(format string, args ...any) {
|
||||||
l.log(LevelInfo, format, args...)
|
l.log(LevelInfo, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning logs a warning message
|
// Warning logs a warning message
|
||||||
func (l *Logger) Warning(format string, args ...interface{}) {
|
func (l *Logger) Warning(format string, args ...any) {
|
||||||
l.log(LevelWarning, format, args...)
|
l.log(LevelWarning, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs an error message
|
// Error logs an error message
|
||||||
func (l *Logger) Error(format string, args ...interface{}) {
|
func (l *Logger) Error(format string, args ...any) {
|
||||||
l.log(LevelError, format, args...)
|
l.log(LevelError, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal logs a fatal error message and exits
|
// Fatal logs a fatal error message and exits
|
||||||
func (l *Logger) Fatal(format string, args ...interface{}) {
|
func (l *Logger) Fatal(format string, args ...any) {
|
||||||
l.log(LevelFatal, format, args...)
|
l.log(LevelFatal, format, args...)
|
||||||
// No need for os.Exit here as it's handled in log()
|
// No need for os.Exit here as it's handled in log()
|
||||||
}
|
}
|
||||||
|
@ -230,30 +293,35 @@ func (l *Logger) Close() {
|
||||||
var defaultLogger = New(LevelInfo, true)
|
var defaultLogger = New(LevelInfo, true)
|
||||||
|
|
||||||
// Debug logs a debug message to the default logger
|
// Debug logs a debug message to the default logger
|
||||||
func Debug(format string, args ...interface{}) {
|
func Debug(format string, args ...any) {
|
||||||
defaultLogger.Debug(format, args...)
|
defaultLogger.Debug(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs an informational message to the default logger
|
// Info logs an informational message to the default logger
|
||||||
func Info(format string, args ...interface{}) {
|
func Info(format string, args ...any) {
|
||||||
defaultLogger.Info(format, args...)
|
defaultLogger.Info(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning logs a warning message to the default logger
|
// Warning logs a warning message to the default logger
|
||||||
func Warning(format string, args ...interface{}) {
|
func Warning(format string, args ...any) {
|
||||||
defaultLogger.Warning(format, args...)
|
defaultLogger.Warning(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs an error message to the default logger
|
// Error logs an error message to the default logger
|
||||||
func Error(format string, args ...interface{}) {
|
func Error(format string, args ...any) {
|
||||||
defaultLogger.Error(format, args...)
|
defaultLogger.Error(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal logs a fatal error message to the default logger and exits
|
// Fatal logs a fatal error message to the default logger and exits
|
||||||
func Fatal(format string, args ...interface{}) {
|
func Fatal(format string, args ...any) {
|
||||||
defaultLogger.Fatal(format, args...)
|
defaultLogger.Fatal(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogRaw logs a raw message to the default logger
|
||||||
|
func LogRaw(format string, args ...any) {
|
||||||
|
defaultLogger.LogRaw(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
// SetLevel changes the minimum log level of the default logger
|
// SetLevel changes the minimum log level of the default logger
|
||||||
func SetLevel(level int) {
|
func SetLevel(level int) {
|
||||||
defaultLogger.SetLevel(level)
|
defaultLogger.SetLevel(level)
|
||||||
|
|
69
moonshark.go
69
moonshark.go
|
@ -1,15 +1,24 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.sharkk.net/Sky/Moonshark/core/config"
|
"git.sharkk.net/Sky/Moonshark/core/config"
|
||||||
|
"git.sharkk.net/Sky/Moonshark/core/http"
|
||||||
"git.sharkk.net/Sky/Moonshark/core/logger"
|
"git.sharkk.net/Sky/Moonshark/core/logger"
|
||||||
"git.sharkk.net/Sky/Moonshark/core/routers"
|
"git.sharkk.net/Sky/Moonshark/core/routers"
|
||||||
"git.sharkk.net/Sky/Moonshark/core/utils"
|
"git.sharkk.net/Sky/Moonshark/core/utils"
|
||||||
|
"git.sharkk.net/Sky/Moonshark/core/workers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
log := logger.New(logger.LevelInfo, true)
|
log := logger.New(logger.LevelDebug, true)
|
||||||
defer log.Close()
|
defer log.Close()
|
||||||
|
|
||||||
log.Info("Starting Moonshark server")
|
log.Info("Starting Moonshark server")
|
||||||
|
@ -29,32 +38,68 @@ func main() {
|
||||||
routesDir := cfg.GetString("routes_dir", "./routes")
|
routesDir := cfg.GetString("routes_dir", "./routes")
|
||||||
staticDir := cfg.GetString("static_dir", "./static")
|
staticDir := cfg.GetString("static_dir", "./static")
|
||||||
|
|
||||||
// Ensure the Lua routes directory exists
|
// Get worker pool size from config or use default
|
||||||
err = utils.EnsureDir(routesDir)
|
workerPoolSize := cfg.GetInt("worker_pool_size", 4)
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Routes directory doesn't exist, and could not create it. %v", err)
|
// Ensure directories exist
|
||||||
|
if err = utils.EnsureDir(routesDir); err != nil {
|
||||||
|
log.Fatal("Routes directory doesn't exist, and could not create it: %v", err)
|
||||||
|
}
|
||||||
|
if err = utils.EnsureDir(staticDir); err != nil {
|
||||||
|
log.Fatal("Static directory doesn't exist, and could not create it: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the static directory exists
|
// Initialize worker pool
|
||||||
err = utils.EnsureDir(staticDir)
|
pool, err := workers.NewPool(workerPoolSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Static directory doesn't exist, and could not create it. %v", err)
|
log.Fatal("Failed to initialize worker pool: %v", err)
|
||||||
}
|
}
|
||||||
|
log.Info("Worker pool initialized with %d workers", workerPoolSize)
|
||||||
|
defer pool.Shutdown()
|
||||||
|
|
||||||
// Initialize Lua router for dynamic routes
|
// Initialize Lua router for dynamic routes
|
||||||
_, err = routers.NewLuaRouter(routesDir)
|
luaRouter, err := routers.NewLuaRouter(routesDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to initialize Lua router: %v", err)
|
log.Fatal("Failed to initialize Lua router: %v", err)
|
||||||
}
|
}
|
||||||
log.Info("Lua router initialized with routes from %s", routesDir)
|
log.Info("Lua router initialized with routes from %s", routesDir)
|
||||||
|
|
||||||
// Initialize static file router
|
// Initialize static file router
|
||||||
_, err = routers.NewStaticRouter(staticDir)
|
staticRouter, err := routers.NewStaticRouter(staticDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to initialize static router: %v", err)
|
log.Fatal("Failed to initialize static router: %v", err)
|
||||||
}
|
}
|
||||||
log.Info("Static router initialized with files from %s", staticDir)
|
log.Info("Static router initialized with files from %s", staticDir)
|
||||||
|
|
||||||
// Output the port number
|
// Create HTTP server
|
||||||
log.Info("Moonshark server listening on port %d", port)
|
server := http.New(luaRouter, staticRouter, pool, log)
|
||||||
|
|
||||||
|
// Handle graceful shutdown
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Start server in a goroutine
|
||||||
|
go func() {
|
||||||
|
addr := fmt.Sprintf(":%d", port)
|
||||||
|
log.Info("Server listening on http://localhost%s", addr)
|
||||||
|
if err := server.ListenAndServe(addr); err != nil {
|
||||||
|
if err.Error() != "http: Server closed" {
|
||||||
|
log.Error("Server error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for interrupt signal
|
||||||
|
<-stop
|
||||||
|
log.Info("Shutdown signal received")
|
||||||
|
|
||||||
|
// Gracefully shut down the server
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
log.Error("Server shutdown error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Server stopped")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user