webserv.go 4.9 KB

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