package monitor import ( "context" "io" "net/http" "strings" "time" "yaum/internal/model" ) var httpClient = &http.Client{ Timeout: 10 * time.Second, // do NOT set InsecureSkipVerify — we want proper TLS } func CheckHTTP(ctx context.Context, url string, keyword string) (model.Heartbeat, *model.SSLInfo) { start := time.Now() req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { hb := heartbeatFromError(start, err) return hb, nil } resp, err := httpClient.Do(req) elapsed := time.Since(start).Milliseconds() if err != nil { return model.Heartbeat{ StatusCode: 0, ResponseTimeMs: elapsed, IsUp: false, ErrorMessage: err.Error(), TestedAt: time.Now(), }, nil } defer resp.Body.Close() isUp := resp.StatusCode >= 200 && resp.StatusCode < 400 errMsg := "" body, readErr := io.ReadAll(resp.Body) if readErr != nil { isUp = false errMsg = "Erro ao ler corpo: " + readErr.Error() } else { bodyStr := string(body) if keyword != "" && isUp { if !strings.Contains(bodyStr, keyword) { isUp = false errMsg = "Palavra-chave nao encontrada" } } if !isUp { snippet := strings.TrimSpace(bodyStr) if len(snippet) > 300 { snippet = snippet[:300] } if errMsg == "" { errMsg = snippet } else { errMsg += " | " + snippet } } } hb := model.Heartbeat{ StatusCode: resp.StatusCode, ResponseTimeMs: elapsed, IsUp: isUp, ErrorMessage: errMsg, TestedAt: time.Now(), } // Extract TLS certificate info var ssl *model.SSLInfo if resp.TLS != nil && len(resp.TLS.PeerCertificates) > 0 { cert := resp.TLS.PeerCertificates[0] days := int(time.Until(cert.NotAfter).Hours() / 24) ssl = &model.SSLInfo{ ServiceID: 0, // filled in by caller Issuer: cert.Issuer.CommonName, ExpiresAt: cert.NotAfter, DaysRemaining: days, CheckedAt: time.Now(), } } return hb, ssl } func heartbeatFromError(start time.Time, err error) model.Heartbeat { return model.Heartbeat{ StatusCode: 0, ResponseTimeMs: time.Since(start).Milliseconds(), IsUp: false, ErrorMessage: err.Error(), TestedAt: time.Now(), } }