182 lines
4.5 KiB
Go
182 lines
4.5 KiB
Go
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 := `<html>
|
|
<body>
|
|
<div class="container">
|
|
{{body}}
|
|
</div>
|
|
</body>
|
|
</html>`
|
|
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()
|
|
}
|