webserv.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package webserv
  2. import (
  3. "embed"
  4. _ "embed"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "sync"
  11. "imuslab.com/zoraxy/mod/database"
  12. "imuslab.com/zoraxy/mod/info/logger"
  13. "imuslab.com/zoraxy/mod/utils"
  14. "imuslab.com/zoraxy/mod/webserv/filemanager"
  15. )
  16. /*
  17. Static Web Server package
  18. This module host a static web server
  19. */
  20. //go:embed templates/*
  21. var templates embed.FS
  22. type WebServerOptions struct {
  23. Port string //Port for listening
  24. EnableDirectoryListing bool //Enable listing of directory
  25. WebRoot string //Folder for stroing the static web folders
  26. EnableWebDirManager bool //Enable web file manager to handle files in web directory
  27. Logger *logger.Logger //System logger
  28. Sysdb *database.Database //Database for storing configs
  29. }
  30. type WebServer struct {
  31. FileManager *filemanager.FileManager
  32. mux *http.ServeMux
  33. server *http.Server
  34. option *WebServerOptions
  35. isRunning bool
  36. mu sync.Mutex
  37. }
  38. // NewWebServer creates a new WebServer instance. One instance only
  39. func NewWebServer(options *WebServerOptions) *WebServer {
  40. if options.Logger == nil {
  41. options.Logger, _ = logger.NewFmtLogger()
  42. }
  43. if !utils.FileExists(options.WebRoot) {
  44. //Web root folder not exists. Create one with default templates
  45. os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775)
  46. os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775)
  47. indexTemplate, err := templates.ReadFile("templates/index.html")
  48. if err != nil {
  49. options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err)
  50. } else {
  51. os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
  52. }
  53. }
  54. //Create a new file manager if it is enabled
  55. var newDirManager *filemanager.FileManager
  56. if options.EnableWebDirManager {
  57. fm := filemanager.NewFileManager(filepath.Join(options.WebRoot, "/html"))
  58. newDirManager = fm
  59. }
  60. //Create new table to store the config
  61. options.Sysdb.NewTable("webserv")
  62. return &WebServer{
  63. mux: http.NewServeMux(),
  64. FileManager: newDirManager,
  65. option: options,
  66. isRunning: false,
  67. mu: sync.Mutex{},
  68. }
  69. }
  70. // Restore the configuration to previous config
  71. func (ws *WebServer) RestorePreviousState() {
  72. //Set the port
  73. port := ws.option.Port
  74. ws.option.Sysdb.Read("webserv", "port", &port)
  75. ws.option.Port = port
  76. //Set the enable directory list
  77. enableDirList := ws.option.EnableDirectoryListing
  78. ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
  79. ws.option.EnableDirectoryListing = enableDirList
  80. //Check the running state
  81. webservRunning := true
  82. ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
  83. if webservRunning {
  84. ws.Start()
  85. } else {
  86. ws.Stop()
  87. }
  88. }
  89. // ChangePort changes the server's port.
  90. func (ws *WebServer) ChangePort(port string) error {
  91. if IsPortInUse(port) {
  92. return errors.New("selected port is used by another process")
  93. }
  94. if ws.isRunning {
  95. if err := ws.Stop(); err != nil {
  96. return err
  97. }
  98. }
  99. ws.option.Port = port
  100. ws.server.Addr = ":" + port
  101. err := ws.Start()
  102. if err != nil {
  103. return err
  104. }
  105. ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil)
  106. ws.option.Sysdb.Write("webserv", "port", port)
  107. return nil
  108. }
  109. // Get current using port in options
  110. func (ws *WebServer) GetListeningPort() string {
  111. return ws.option.Port
  112. }
  113. // Start starts the web server.
  114. func (ws *WebServer) Start() error {
  115. ws.mu.Lock()
  116. defer ws.mu.Unlock()
  117. //Check if server already running
  118. if ws.isRunning {
  119. return fmt.Errorf("web server is already running")
  120. }
  121. //Check if the port is usable
  122. if IsPortInUse(ws.option.Port) {
  123. return errors.New("port already in use or access denied by host OS")
  124. }
  125. //Dispose the old mux and create a new one
  126. ws.mux = http.NewServeMux()
  127. //Create a static web server
  128. fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
  129. ws.mux.Handle("/", ws.fsMiddleware(fs))
  130. ws.server = &http.Server{
  131. Addr: ":" + ws.option.Port,
  132. Handler: ws.mux,
  133. }
  134. go func() {
  135. if err := ws.server.ListenAndServe(); err != nil {
  136. if err != http.ErrServerClosed {
  137. ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err)
  138. }
  139. }
  140. }()
  141. ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil)
  142. ws.isRunning = true
  143. ws.option.Sysdb.Write("webserv", "enabled", true)
  144. return nil
  145. }
  146. // Stop stops the web server.
  147. func (ws *WebServer) Stop() error {
  148. ws.mu.Lock()
  149. defer ws.mu.Unlock()
  150. if !ws.isRunning {
  151. return fmt.Errorf("web server is not running")
  152. }
  153. if err := ws.server.Close(); err != nil {
  154. return err
  155. }
  156. ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil)
  157. ws.isRunning = false
  158. ws.option.Sysdb.Write("webserv", "enabled", false)
  159. return nil
  160. }
  161. // UpdateDirectoryListing enables or disables directory listing.
  162. func (ws *WebServer) UpdateDirectoryListing(enable bool) {
  163. ws.option.EnableDirectoryListing = enable
  164. ws.option.Sysdb.Write("webserv", "dirlist", enable)
  165. }
  166. // Close stops the web server without returning an error.
  167. func (ws *WebServer) Close() {
  168. ws.Stop()
  169. }