Commit c5855911 authored by Vitaly Lipatov's avatar Vitaly Lipatov

web-api: instant first SSE event, resolve/routes/whois stream async

Send initial "check" event immediately (just domain name), then resolve IPs, find routes, fetch whois and gateway checks all run in parallel. Each result streams via its own SSE event: - resolve: IPs + routes - gateway: per-gateway availability - whois: domain registration info - throttle: slow download detection Eliminates all delay before first visible response. Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent a2d006a2
......@@ -1112,39 +1112,11 @@ function renderCheck(data) {
const container = $('check-gateways');
container.textContent = '';
// Two-column row: left = gateways (filled by SSE), right = info
// Two-column row: left = gateways (filled by SSE), right = info (filled by SSE)
const cols = mkDiv('check-columns');
const colGw = mkDiv('check-col');
colGw.id = 'check-col-gw';
const colInfo = mkDiv('check-col');
// Right column: IPs + routes (whois arrives via SSE)
if (data.ips && data.ips.length) {
const sec = mkDiv('check-section');
sec.appendChild(mkDiv('check-section-title', 'IP-\\u0430\\u0434\\u0440\\u0435\\u0441\\u0430'));
sec.appendChild(mkDiv('check-ips', data.ips.join(', ')));
colInfo.appendChild(sec);
}
const rsec = mkDiv('check-section');
rsec.appendChild(mkDiv('check-section-title', '\\u0412 \\u0441\\u043f\\u0438\\u0441\\u043a\\u0430\\u0445 \\u043c\\u0430\\u0440\\u0448\\u0440\\u0443\\u0442\\u043e\\u0432'));
if (data.routes && data.routes.length) {
const byList = {};
data.routes.forEach(r => {
const key = r.group + '/' + r.list;
if (!byList[key]) byList[key] = [];
byList[key].push(r.ip ? r.ip + ' \\u2208 ' + r.entry : r.entry);
});
for (const [key, items] of Object.entries(byList)) {
const unique = [...new Set(items)];
const el = mkDiv('check-route', key + ': ' + unique.join(', '));
rsec.appendChild(el);
}
} else {
rsec.appendChild(mkDiv('check-no-route', '\\u041d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d\\u043e \\u0432 \\u0441\\u043f\\u0438\\u0441\\u043a\\u0430\\u0445 \\u043c\\u0430\\u0440\\u0448\\u0440\\u0443\\u0442\\u043e\\u0432'));
}
colInfo.appendChild(rsec);
cols.appendChild(colGw);
cols.appendChild(colInfo);
container.appendChild(cols);
......@@ -1245,6 +1217,34 @@ async function checkDomain() {
el.textContent = gw + ': ' + info.speed_str;
}
gwWrap.appendChild(el);
} else if (evType === 'resolve') {
// Add IPs + routes to right column
const colInfo1 = document.querySelector('#check-gateways .check-col:last-child');
if (colInfo1) {
if (parsed.ips && parsed.ips.length) {
const sec = mkDiv('check-section');
sec.appendChild(mkDiv('check-section-title', 'IP-\\u0430\\u0434\\u0440\\u0435\\u0441\\u0430'));
sec.appendChild(mkDiv('check-ips', parsed.ips.join(', ')));
colInfo1.appendChild(sec);
}
const rsec = mkDiv('check-section');
rsec.appendChild(mkDiv('check-section-title', '\\u0412 \\u0441\\u043f\\u0438\\u0441\\u043a\\u0430\\u0445 \\u043c\\u0430\\u0440\\u0448\\u0440\\u0443\\u0442\\u043e\\u0432'));
if (parsed.routes && parsed.routes.length) {
const byList = {};
parsed.routes.forEach(r => {
const key = r.group + '/' + r.list;
if (!byList[key]) byList[key] = [];
byList[key].push(r.ip ? r.ip + ' \\u2208 ' + r.entry : r.entry);
});
for (const [key, items] of Object.entries(byList)) {
const unique = [...new Set(items)];
rsec.appendChild(mkDiv('check-route', key + ': ' + unique.join(', ')));
}
} else {
rsec.appendChild(mkDiv('check-no-route', '\\u041d\\u0435 \\u043d\\u0430\\u0439\\u0434\\u0435\\u043d\\u043e \\u0432 \\u0441\\u043f\\u0438\\u0441\\u043a\\u0430\\u0445 \\u043c\\u0430\\u0440\\u0448\\u0440\\u0443\\u0442\\u043e\\u0432'));
}
colInfo1.appendChild(rsec);
}
} else if (evType === 'whois') {
// Add whois to right column
const colInfo = document.querySelector('#check-gateways .check-col:last-child');
......@@ -1736,24 +1736,34 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
self.send_header("Cache-Control", "no-cache")
self.end_headers()
# Phase 0: quick data (IPs, routes) — instant
ips = resolve_domain(domain)
routes = find_in_routes(domain, ips)
base = {"domain": domain, "ips": ips, "routes": routes}
# Send check event immediately — no blocking
base = {"domain": domain}
if file_url:
base["file_url"] = file_url
self.send_sse("check", base)
if file_url:
# File URL mode: whois + speed test
whois_info = get_whois(domain)
# File URL mode: resolve + whois + speed test
def _resolve_and_routes():
ips = resolve_domain(domain)
routes = find_in_routes(domain, ips)
return ips, routes
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
resolve_future = pool.submit(_resolve_and_routes)
whois_future = pool.submit(get_whois, domain)
ips, routes = resolve_future.result()
self.send_sse("resolve", {"ips": ips, "routes": routes})
whois_info = whois_future.result()
if whois_info:
self.send_sse("whois", {"whois": whois_info})
def on_speed(gw_name, res):
self.send_sse("speed", {"gateway": gw_name, "result": res})
run_speed_test(file_url, on_speed)
else:
# Domain mode: stream gateway checks
# Domain mode: resolve + gateways + whois all in parallel
url = "https://%s/" % domain
has_v6 = False
try:
......@@ -1762,8 +1772,14 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
except socket.gaierror:
pass
workers = len(CHECK_GATEWAYS) * 2 + 2
def _resolve_and_routes():
ips = resolve_domain(domain)
routes = find_in_routes(domain, ips)
return ips, routes
workers = len(CHECK_GATEWAYS) * 2 + 4
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as pool:
resolve_future = pool.submit(_resolve_and_routes)
gw_futures = {}
for name, proxy_v4, proxy_v6 in CHECK_GATEWAYS:
f4 = pool.submit(_check_one, name, proxy_v4, url, "-4")
......@@ -1774,6 +1790,10 @@ class RouteHandler(http.server.BaseHTTPRequestHandler):
whois_future = pool.submit(get_whois, domain)
assets_future = pool.submit(_find_assets, CHECK_GATEWAYS[0][1], url)
# Send resolve as soon as ready
ips, routes = resolve_future.result()
self.send_sse("resolve", {"ips": ips, "routes": routes})
for future in concurrent.futures.as_completed(gw_futures):
name, ipver, status, code = future.result()
self.send_sse("gateway", {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment