package main import ( "crypto/tls" "crypto/x509" "fmt" "net" "net/http" "net/url" "strings" "time" ) // refreshStatuses checks every monitor, updates the store, and broadcasts changes. func refreshStatuses(store *statusStore, client *http.Client, monitors []serviceMonitor, hub *wsHub) { results := make([]serviceStatus, 0, len(monitors)) for _, monitor := range monitors { result := checkService(client, monitor) results = append(results, result) store.upsert(result) store.sort() hub.broadcast(wsEnvelope{ Type: "service_update", GeneratedAt: time.Now(), Service: &result, }) } sortStatuses(results) store.set(results) } // checkService dispatches a monitor to the appropriate protocol-specific checker. func checkService(client *http.Client, monitor serviceMonitor) serviceStatus { parsed, err := parseMonitorURL(monitor.URL) if err != nil { return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: "invalid", Healthy: false, ResponseTime: "0s", LastChecked: time.Now(), Message: "invalid monitor URL", Details: err.Error(), } } switch parsed.Scheme { case "http", "https": return checkHTTPService(client, monitor, parsed) case "dns": return checkDNSService(monitor, parsed) case "ping": return checkPingService(monitor, parsed) case "tls": return checkTLSService(monitor, parsed) default: return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: false, ResponseTime: "0s", LastChecked: time.Now(), Message: "unsupported monitor scheme", Details: "use http://, https://, dns://, ping://, or tls://", } } } // checkHTTPService issues an HTTP GET request and reports the response status. func checkHTTPService(client *http.Client, monitor serviceMonitor, parsed *url.URL) serviceStatus { start := time.Now() req, err := http.NewRequest(http.MethodGet, parsed.String(), nil) if err != nil { return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: false, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: "invalid HTTP request", Details: err.Error(), } } req.Header.Set("User-Agent", "status-page-monitor/1.0") resp, err := client.Do(req) if err != nil { return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: false, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: "HTTP request failed", Details: err.Error(), } } defer resp.Body.Close() healthy := resp.StatusCode >= 200 && resp.StatusCode < 400 message := "service responded normally" if !healthy { message = "service returned an unhealthy response" } return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: healthy, StatusCode: resp.StatusCode, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: message, Details: "HTTP " + resp.Status, } } // checkDNSService resolves the monitor target and reports the lookup result. func checkDNSService(monitor serviceMonitor, parsed *url.URL) serviceStatus { start := time.Now() target, err := monitorTarget(parsed) if err != nil { return failedStatus(monitor, parsed.Scheme, start, "invalid DNS target", err.Error()) } addrs, err := net.LookupHost(target) if err != nil { return failedStatus(monitor, parsed.Scheme, start, "DNS lookup failed", err.Error()) } return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: len(addrs) > 0, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: fmt.Sprintf("resolved %d DNS record(s)", len(addrs)), Details: strings.Join(addrs, ", "), } } // checkPingService performs a TCP dial to confirm the target is reachable. func checkPingService(monitor serviceMonitor, parsed *url.URL) serviceStatus { start := time.Now() address, err := targetAddress(parsed, "443") if err != nil { return failedStatus(monitor, parsed.Scheme, start, "invalid ping target", err.Error()) } conn, err := net.DialTimeout("tcp", address, 5*time.Second) if err != nil { return failedStatus(monitor, parsed.Scheme, start, "TCP ping failed", err.Error()) } _ = conn.Close() return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: true, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: "TCP ping succeeded", Details: "connected to " + address, } } // checkTLSService inspects the remote TLS certificate and validates its basics. func checkTLSService(monitor serviceMonitor, parsed *url.URL) serviceStatus { start := time.Now() address, err := targetAddress(parsed, "443") if err != nil { return failedStatus(monitor, parsed.Scheme, start, "invalid TLS target", err.Error()) } host := addressHost(address) dialer := &net.Dialer{Timeout: 5 * time.Second} conn, err := tls.DialWithDialer(dialer, "tcp", address, &tls.Config{ ServerName: host, InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, }) if err != nil { return failedStatus(monitor, parsed.Scheme, start, "TLS handshake failed", err.Error()) } defer conn.Close() state := conn.ConnectionState() if len(state.PeerCertificates) == 0 { return failedStatus(monitor, parsed.Scheme, start, "no TLS certificate presented", "") } leaf := state.PeerCertificates[0] intermediates := x509.NewCertPool() for _, cert := range state.PeerCertificates[1:] { intermediates.AddCert(cert) } now := time.Now() hostnameValid := leaf.VerifyHostname(host) == nil timeValid := !now.Before(leaf.NotBefore) && !now.After(leaf.NotAfter) _, verifyErr := leaf.Verify(x509.VerifyOptions{ Intermediates: intermediates, CurrentTime: now, }) signed := verifyErr == nil healthy := signed && hostnameValid && timeValid details := []string{ "signed: " + yesNo(signed), "valid: " + yesNo(healthy), "expires: " + leaf.NotAfter.Format(time.RFC1123), } if !hostnameValid { details = append(details, "hostname mismatch") } if verifyErr != nil { details = append(details, "verify error: "+verifyErr.Error()) } message := "TLS certificate is healthy" if !healthy { message = "TLS certificate check failed" } return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: parsed.Scheme, Healthy: healthy, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: message, Details: strings.Join(details, " | "), } } // failedStatus builds a failed service status using a consistent shape. func failedStatus(monitor serviceMonitor, protocol string, start time.Time, message, details string) serviceStatus { return serviceStatus{ Name: monitor.Name, URL: monitor.URL, Category: monitor.Category, Protocol: protocol, Healthy: false, ResponseTime: time.Since(start).Round(time.Millisecond).String(), LastChecked: time.Now(), Message: message, Details: details, } } // parseMonitorURL parses and minimally validates a monitor URL. func parseMonitorURL(raw string) (*url.URL, error) { parsed, err := url.Parse(raw) if err != nil { return nil, err } if parsed.Scheme == "" { return nil, fmt.Errorf("missing scheme") } switch parsed.Scheme { case "http", "https", "dns", "ping", "tls": return parsed, nil default: return parsed, nil } } // monitorTarget extracts the host-like target from a parsed monitor URL. func monitorTarget(parsed *url.URL) (string, error) { switch { case parsed.Host != "": return parsed.Hostname(), nil case parsed.Opaque != "": return strings.TrimPrefix(parsed.Opaque, "//"), nil case parsed.Path != "": return strings.TrimPrefix(parsed.Path, "/"), nil default: return "", fmt.Errorf("missing target host") } } // targetAddress returns a host:port address for monitors that require dialing. func targetAddress(parsed *url.URL, defaultPort string) (string, error) { target, err := monitorTarget(parsed) if err != nil { return "", err } host, port, err := net.SplitHostPort(target) if err == nil { return net.JoinHostPort(host, port), nil } if strings.Contains(err.Error(), "missing port in address") { return net.JoinHostPort(target, defaultPort), nil } return "", err } // addressHost strips the port from a host:port address when present. func addressHost(address string) string { host, _, err := net.SplitHostPort(address) if err != nil { return address } return host } // yesNo converts a boolean into a stable yes or no string. func yesNo(value bool) string { if value { return "yes" } return "no" }