Introduce streaming endpoint for NDJSON scan progress, incorporate scan checkpoints into scan details, and update UI components to display this new information. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 0d01f99a-ea6a-447d-82fd-311715434a39 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 2852b526-3bf8-4a93-a62a-a50e26291074 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/e32d2b99-1721-47dd-833c-98b372f48008/0d01f99a-ea6a-447d-82fd-311715434a39/8MCgDZm Replit-Helium-Checkpoint-Created: true
1.9 KiB
| name | description |
|---|---|
| NDJSON streaming under Express behind the Replit proxy | Pitfalls for live/streaming HTTP responses (NDJSON/SSE) from an Express API on Replit — client-disconnect detection, persistence, and proxy buffering |
Streaming responses (NDJSON) from Express on Replit
For a long-lived POST that streams progress (e.g. application/x-ndjson with
res.flushHeaders() then line-delimited JSON writes):
Detect a real client abort with res.on("close") + res.writableFinished — never
req.on("close").
req.on("close") fires as soon as the POST request body has been fully consumed,
which is normal and happens immediately. Gating writes on that wrongly suppresses the
entire stream. Use the response close event, and treat it as an abort only when
!res.writableFinished.
Persist the result regardless of disconnect. Run the analysis and the DB write
inside the handler's try unconditionally; only guard the res.write() calls on
"connection still open". This way a client that disconnects mid-stream still gets its
result persisted (and the client must NOT re-submit on a mid-stream error, or it
creates a duplicate row — see below).
The Replit path-proxy does NOT buffer the stream. Incremental flushes arrive line
by line through $REPLIT_DEV_DOMAIN, same as localhost. No special proxy header or
chunk-padding needed.
Client fetch must be root-relative (/api/...), not import.meta.env.BASE_URL.
That matches the generated API client and is what routes correctly through the proxy.
Fallback gating to avoid duplicate persists: a client that streams and also falls
back to a non-streaming POST on error must only fall back when the server never
processed the request (fetch rejected, or !res.ok with a 5xx). Once the response
body has started streaming, any read error means the server is already persisting —
falling back then duplicates the record. Distinguish 4xx (show message, no retry)
from transport failures.