The HTTP probe reports the warm per-request round-trip, which reads
lower than the delay figure client apps show for the same server. Add a
third "real" test mode that reuses the temp-instance HTTP probe but
reports the cold request's full elapsed time - tunnel establishment
included - and skips the warm request. UDP-transport outbounds forced
out of the TCP lane still report "http"; in real mode they report
"real". The mode joins the TCP/HTTP toggle on the outbounds tab, with
the label translated in all 13 locales.
Since the batched prober replaced the single tester, the reported delay
came from one cold request with keep-alives disabled, so it stacked the
SOCKS handshake, proxy dial, proxy TLS, target TCP and target TLS on top
of the round-trip. Users upgrading from v2.9.4 - whose tester warmed the
connection first and timed a second request - saw several times the real
connection time.
The cold request still proves reachability and supplies the HTTP status
plus the connect/TLS/TTFB breakdown; the delay is now re-measured on a
second request over the kept-alive connection, falling back to the cold
total when the warm request fails. Bodies are drained (bounded) so the
connection returns to the pool, and the batch test asserts both requests
of a probe share one connection.
Replace the per-outbound burstObservatory polling (one temp xray spawn +
up to 15s of /debug/vars polling per outbound, serialised) with one
shared temp xray instance per batch: every tested outbound gets its own
loopback SOCKS inbound plus an inboundTag->outboundTag routing rule, and
the panel times a real HTTP request through each one in parallel. The
probe returns as soon as the response lands and records the HTTP status
plus an httptrace breakdown (proxy connect / TLS via outbound / first
byte) shown in the result popover.
New POST /panel/api/xray/testOutbounds endpoint (array in, results in
input order, max 50); the legacy /testOutbound endpoint now delegates to
the same engine. Test All chunks HTTP probes 16 per request, and a batch
whose shared process never comes up (one structurally-broken outbound
poisons the config) retries each item in an isolated instance so the
broken outbound reports xray's real error while the rest still test.