gomeshalerter/webserver.go

144 lines
3.5 KiB
Go

package main
import (
"context"
"html/template"
"net/http"
"strings"
"time"
)
// webServer runs the web interface on port 8080
func webServer(shutdown chan struct{}) {
// Load templates
tmpl, err := template.ParseFiles("templates/index.html")
if err != nil {
logInfo("web", "Error loading templates", map[string]interface{}{
"error": err.Error(),
})
return
}
// Define HTTP handlers
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data, err := getDashboardData()
if err != nil {
http.Error(w, "Error fetching data: "+err.Error(), http.StatusInternalServerError)
return
}
err = tmpl.Execute(w, data)
if err != nil {
// Check if it's already too late to write headers
if !isResponseHeaderWritten(err) {
http.Error(w, "Error rendering template: "+err.Error(), http.StatusInternalServerError)
} else {
// Log the error but don't try to write headers again
logInfo("web", "Template execution error after headers sent", map[string]interface{}{
"error": err.Error(),
})
}
return
}
})
// Start the server
server := &http.Server{
Addr: ":8080",
Handler: nil, // Use default mux
}
// Create a goroutine for the server
go func() {
logInfo("web", "Starting web server", map[string]interface{}{
"port": 8080,
})
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logInfo("web", "Web server error", map[string]interface{}{
"error": err.Error(),
})
}
}()
// Wait for shutdown signal
<-shutdown
// Create a deadline for server shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Attempt graceful shutdown
if err := server.Shutdown(ctx); err != nil {
logInfo("web", "Error during server shutdown", map[string]interface{}{
"error": err.Error(),
})
}
logInfo("web", "Web server stopped", nil)
}
// getDashboardData fetches the data needed for the dashboard
func getDashboardData() (DashboardData, error) {
articles := loadArticles()
now := time.Now()
hourAgo := now.Add(-60 * time.Minute)
// Prepare the data structure
data := DashboardData{
LastUpdated: now.Format("Jan 02, 2006 15:04:05 MST"),
TotalArticles: len(articles),
}
// Count broadcast articles, recent articles, and unsummarized articles
for _, a := range articles {
if !a.BroadcastTime.IsZero() && a.BroadcastTime.Unix() > 1 {
data.TotalBroadcast++
}
if a.FirstSeen.After(hourAgo) {
data.NewInLastHour++
}
if a.Summary == "" || a.Importance == 0 {
data.UnsummarizedCount++
}
}
// Get broadcast history (last 100)
history, err := getBroadcastHistory(100)
if err != nil {
return data, err
}
data.History = history
// Get next up articles (importance sorted, less than 24 hours old)
nextUp, err := getNextUpArticles()
if err != nil {
return data, err
}
data.NextUp = nextUp
// Get recent logs
recentLogs, err := getRecentLogs(100)
if err != nil {
logInfo("web", "Error fetching recent logs", map[string]interface{}{
"error": err.Error(),
})
// Continue with empty logs list
recentLogs = []LogEntry{}
}
data.RecentLogs = recentLogs
return data, nil
}
// isResponseHeaderWritten checks if an error indicates headers were already written
func isResponseHeaderWritten(err error) bool {
// Check common patterns that indicate the response was already partially written
errStr := err.Error()
return strings.Contains(errStr, "write: broken pipe") ||
strings.Contains(errStr, "write: connection reset by peer") ||
strings.Contains(errStr, "http: superfluous response.WriteHeader")
}