package srv import ( db "chat/db" "compress/gzip" "encoding/json" "fmt" "io" "net" "net/http" "os" "path/filepath" "strings" "sync" "time" ) type Config struct { Server struct { IpAddress string `json:"ipAddress"` Port int `json:"port"` } `json:"server"` Paths struct { DatabasePath string `json:"databasePath"` IndexJsPath string `json:"indexJsPath"` IndexCssPath string `json:"indexCssPath"` IndexHtmlPath string `json:"indexHtmlPath"` MessagesHtmlPath string `json:"messagesHtmlPath"` } `json:"paths"` Options struct { MessageMaxAge int `json:"messageMaxAge"` NameMaxLength int `json:"nameMaxLength"` } `json:"options"` } func LoadConfig(filepath string) Config { contents, _ := os.ReadFile(filepath) var config Config err := json.Unmarshal(contents, &config) config.Paths.IndexHtmlPath = pathMaker(config.Paths.IndexHtmlPath) config.Paths.IndexJsPath = pathMaker(config.Paths.IndexJsPath) config.Paths.IndexCssPath = pathMaker(config.Paths.IndexCssPath) config.Paths.MessagesHtmlPath = pathMaker(config.Paths.MessagesHtmlPath) config.Paths.DatabasePath = pathMaker(config.Paths.DatabasePath) if err != nil { fmt.Println("Error parsing config file: ", err) os.Exit(1) } return config } func pathMaker(path string) string { absPath, _ := filepath.Abs(path) absPath = filepath.Clean(absPath) fmt.Println(absPath) return absPath } type gzipResponseWriter struct { http.ResponseWriter io.Writer } func (g *gzipResponseWriter) Write(data []byte) (int, error) { return g.Writer.Write(data) } func GzipMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } gz := gzip.NewWriter(w) defer gz.Close() w.Header().Set("Content-Encoding", "gzip") next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) }) } func getMessageTemplate(body string) string { template := `
{{body}}
` return strings.Replace(template, "{{body}}", body, 1) } func getClientIP(r *http.Request) string { if fwdIP := r.Header.Get("X-Forwarded-For"); fwdIP != "" { return strings.Split(fwdIP, ",")[0] } clientIP := r.RemoteAddr if host, _, err := net.SplitHostPort(clientIP); err == nil { return host } return clientIP } func readFile(filepath string) []byte { contents, _ := os.ReadFile(filepath) return contents } func validUsername(username string) bool { for _, c := range username { if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') { return false } } return true } type Server struct { Connected map[string]time.Time // Map IP -> Last activity time Database *db.Database Config Config mu sync.Mutex // For thread safety } func NewServer(config Config) *Server { return &Server{ Connected: make(map[string]time.Time), Database: db.OpenDatabase(config.Paths.DatabasePath), Config: config, mu: sync.Mutex{}, } } func (s *Server) AddMessage(userip string, contents string) { s.Database.MessageAdd(userip, contents) } func (s *Server) updateActivity(ip string) { s.mu.Lock() defer s.mu.Unlock() s.Connected[ip] = time.Now() } func (s *Server) cleanupActivity() { s.mu.Lock() defer s.mu.Unlock() for ip, lastActivity := range s.Connected { if time.Since(lastActivity) > 10*time.Second { delete(s.Connected, ip) } } } func (s *Server) Run() { s.Database.DbCreateTableMessages() s.Database.DbCreateTableUsers() s.Database.DeleteOldMessages(s.Config.Options.MessageMaxAge) handler := http.NewServeMux() handler.HandleFunc("/", s.handleRoot) handler.HandleFunc("/root.js", s.handleJs) handler.HandleFunc("/root.css", s.handleCss) handler.HandleFunc("/ping", s.handlePing) handler.HandleFunc("/timezone", s.handleTimezone) handler.HandleFunc("/username/status", s.handleUsernameStatus) handler.HandleFunc("/messages/length", s.handleMessagesLength) handler.HandleFunc("/users", s.handleUsers) handler.HandleFunc("/username", s.handleUsername) handler.HandleFunc("/messages", s.handleMessages) fmt.Printf("Server starting on %s:%d\n", s.Config.Server.IpAddress, s.Config.Server.Port) defer s.Stop() if err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.Config.Server.IpAddress, s.Config.Server.Port), GzipMiddleware(handler)); err != nil { fmt.Printf("Server error: %v\n", err) } } func (s *Server) Stop() { s.Database.Close() }