package executionpool import ( "context" "crypto/tls" "net/http" "net/http/httptrace" "time" ) type ProbeTarget struct { RouteProfile RouteProfile URL string Method string Timeout time.Duration } type ProbeResult struct { Reachable bool StatusCode int DNSDuration time.Duration TCPDuration time.Duration TLSDuration time.Duration FirstByte time.Duration SampledAt time.Time ErrorClass string } type NetworkProbe interface { Probe(context.Context, ProbeTarget) ProbeResult } type HTTPProbe struct { Client *http.Client Now func() time.Time } func (p HTTPProbe) Probe(ctx context.Context, target ProbeTarget) ProbeResult { result := ProbeResult{} now := time.Now if p.Now != nil { now = p.Now } result.SampledAt = now() method := target.Method if method == "" { method = http.MethodHead } timeout := target.Timeout if timeout <= 0 { timeout = 3 * time.Second } probeCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() var dnsStart, tcpStart, tlsStart time.Time requestStart := now() trace := &httptrace.ClientTrace{ DNSStart: func(httptrace.DNSStartInfo) { dnsStart = now() }, DNSDone: func(httptrace.DNSDoneInfo) { if !dnsStart.IsZero() { result.DNSDuration = now().Sub(dnsStart) } }, ConnectStart: func(_, _ string) { tcpStart = now() }, ConnectDone: func(_, _ string, _ error) { if !tcpStart.IsZero() { result.TCPDuration = now().Sub(tcpStart) } }, TLSHandshakeStart: func() { tlsStart = now() }, TLSHandshakeDone: func(tls.ConnectionState, error) { if !tlsStart.IsZero() { result.TLSDuration = now().Sub(tlsStart) } }, GotFirstResponseByte: func() { result.FirstByte = now().Sub(requestStart) }, } request, err := http.NewRequestWithContext(httptrace.WithClientTrace(probeCtx, trace), method, target.URL, nil) if err != nil { result.ErrorClass = "invalid_target" return result } client := p.Client if client == nil { client = http.DefaultClient } response, err := client.Do(request) if err != nil { if probeCtx.Err() != nil { result.ErrorClass = "timeout" } else { result.ErrorClass = "network" } return result } defer response.Body.Close() result.StatusCode = response.StatusCode // Any HTTP response, including 401/403/404/405, proves transport reachability. result.Reachable = true return result }