zoraxy/src/mod/webserv/webserv.go
Toby Chui 4a37a989a0 Added Disable Chunk Transfer Encoding option
- Added disable chunk transfer encoding on UI #685
- Added optional to disable static web server listen to all interface #688
2025-06-15 13:46:35 +08:00

223 lines
5.9 KiB
Go

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
/*
WebServerOptions define the default option for the webserv
might get override by user settings loaded from db
Any changes in here might need to also update the StaticWebServerStatus struct
in handler.go. See handler.go for more information.
*/
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
DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost
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
//Set disable listen to all interface
disableListenToAll := ws.option.DisableListenToAllInterface
ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll)
ws.option.DisableListenToAllInterface = disableListenToAll
//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))
listenAddr := ":" + ws.option.Port
if ws.option.DisableListenToAllInterface {
listenAddr = "127.0.0.1:" + ws.option.Port
}
ws.server = &http.Server{
Addr: listenAddr,
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()
}