package webserv import ( "embed" _ "embed" "errors" "fmt" "net/http" "os" "path/filepath" "sync" "imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/webserv/filemanager" ) /* Static Web Server package This module host a static web server */ //go:embed templates/* var templates embed.FS type WebServerOptions struct { Port string //Port for listening EnableDirectoryListing bool //Enable listing of directory WebRoot string //Folder for stroing the static web folders EnableWebDirManager bool //Enable web file manager to handle files in web directory Logger *logger.Logger //System logger Sysdb *database.Database //Database for storing configs } type WebServer struct { FileManager *filemanager.FileManager mux *http.ServeMux server *http.Server option *WebServerOptions isRunning bool mu sync.Mutex } // NewWebServer creates a new WebServer instance. One instance only func NewWebServer(options *WebServerOptions) *WebServer { if options.Logger == nil { options.Logger, _ = logger.NewFmtLogger() } if !utils.FileExists(options.WebRoot) { //Web root folder not exists. Create one with default templates os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775) os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775) indexTemplate, err := templates.ReadFile("templates/index.html") if err != nil { options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err) } else { os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775) } } //Create a new file manager if it is enabled var newDirManager *filemanager.FileManager if options.EnableWebDirManager { fm := filemanager.NewFileManager(filepath.Join(options.WebRoot, "/html")) newDirManager = fm } //Create new table to store the config options.Sysdb.NewTable("webserv") return &WebServer{ mux: http.NewServeMux(), FileManager: newDirManager, option: options, isRunning: false, mu: sync.Mutex{}, } } // Restore the configuration to previous config func (ws *WebServer) RestorePreviousState() { //Set the port port := ws.option.Port ws.option.Sysdb.Read("webserv", "port", &port) ws.option.Port = port //Set the enable directory list enableDirList := ws.option.EnableDirectoryListing ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList) ws.option.EnableDirectoryListing = enableDirList //Check the running state webservRunning := true ws.option.Sysdb.Read("webserv", "enabled", &webservRunning) if webservRunning { ws.Start() } else { ws.Stop() } } // ChangePort changes the server's port. func (ws *WebServer) ChangePort(port string) error { if IsPortInUse(port) { return errors.New("selected port is used by another process") } if ws.isRunning { if err := ws.Stop(); err != nil { return err } } ws.option.Port = port ws.server.Addr = ":" + port err := ws.Start() if err != nil { return err } ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil) ws.option.Sysdb.Write("webserv", "port", port) return nil } // Get current using port in options func (ws *WebServer) GetListeningPort() string { return ws.option.Port } // Start starts the web server. func (ws *WebServer) Start() error { ws.mu.Lock() defer ws.mu.Unlock() //Check if server already running if ws.isRunning { return fmt.Errorf("web server is already running") } //Check if the port is usable if IsPortInUse(ws.option.Port) { return errors.New("port already in use or access denied by host OS") } //Dispose the old mux and create a new one ws.mux = http.NewServeMux() //Create a static web server fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html"))) ws.mux.Handle("/", ws.fsMiddleware(fs)) ws.server = &http.Server{ Addr: ":" + ws.option.Port, Handler: ws.mux, } go func() { if err := ws.server.ListenAndServe(); err != nil { if err != http.ErrServerClosed { ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err) } } }() ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil) ws.isRunning = true ws.option.Sysdb.Write("webserv", "enabled", true) return nil } // Stop stops the web server. func (ws *WebServer) Stop() error { ws.mu.Lock() defer ws.mu.Unlock() if !ws.isRunning { return fmt.Errorf("web server is not running") } if err := ws.server.Close(); err != nil { return err } ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil) ws.isRunning = false ws.option.Sysdb.Write("webserv", "enabled", false) return nil } // UpdateDirectoryListing enables or disables directory listing. func (ws *WebServer) UpdateDirectoryListing(enable bool) { ws.option.EnableDirectoryListing = enable ws.option.Sysdb.Write("webserv", "dirlist", enable) } // Close stops the web server without returning an error. func (ws *WebServer) Close() { ws.Stop() }