Spaces:
Paused
Paused
icebear icebear0828 commited on
fix: handle stream disconnection properly in curl transport and proxy handler (#108)
Browse files1. curl-cli-transport: signal error (not close) when curl exits with
non-zero code mid-stream, so downstream readers get a proper error
instead of a silent truncation.
2. proxy-handler: catch s.write() failures during streaming to detect
client disconnects, abort the upstream curl process, and stop the
stream cleanly instead of leaking resources.
3. proxy-handler: don't re-throw after sending error SSE event — the
stream is already closing, re-throwing causes unhandled rejection.
Co-authored-by: icebear0828 <icebear0828@users.noreply.github.com>
src/routes/shared/proxy-handler.ts
CHANGED
|
@@ -153,7 +153,13 @@ export async function handleProxyRequest(
|
|
| 153 |
() => {},
|
| 154 |
req.tupleSchema,
|
| 155 |
)) {
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
}
|
| 158 |
} catch (err) {
|
| 159 |
// P2-8: Send error SSE event to client before closing
|
|
@@ -161,7 +167,6 @@ export async function handleProxyRequest(
|
|
| 161 |
const errMsg = err instanceof Error ? err.message : "Stream interrupted";
|
| 162 |
await s.write(`data: ${JSON.stringify({ error: { message: errMsg, type: "stream_error" } })}\n\n`);
|
| 163 |
} catch { /* client already gone */ }
|
| 164 |
-
throw err;
|
| 165 |
} finally {
|
| 166 |
// P0-2: Kill curl subprocess if still running
|
| 167 |
abortController.abort();
|
|
|
|
| 153 |
() => {},
|
| 154 |
req.tupleSchema,
|
| 155 |
)) {
|
| 156 |
+
try {
|
| 157 |
+
await s.write(chunk);
|
| 158 |
+
} catch {
|
| 159 |
+
// Client disconnected mid-stream — stop reading upstream
|
| 160 |
+
abortController.abort();
|
| 161 |
+
return;
|
| 162 |
+
}
|
| 163 |
}
|
| 164 |
} catch (err) {
|
| 165 |
// P2-8: Send error SSE event to client before closing
|
|
|
|
| 167 |
const errMsg = err instanceof Error ? err.message : "Stream interrupted";
|
| 168 |
await s.write(`data: ${JSON.stringify({ error: { message: errMsg, type: "stream_error" } })}\n\n`);
|
| 169 |
} catch { /* client already gone */ }
|
|
|
|
| 170 |
} finally {
|
| 171 |
// P0-2: Kill curl subprocess if still running
|
| 172 |
abortController.abort();
|
src/tls/curl-cli-transport.ts
CHANGED
|
@@ -151,8 +151,14 @@ export class CurlCliTransport implements TlsTransport {
|
|
| 151 |
}
|
| 152 |
if (!headersParsed) {
|
| 153 |
reject(new Error(`curl exited with code ${code}: ${stderrBuf}`));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
}
|
| 155 |
-
bodyController?.close();
|
| 156 |
});
|
| 157 |
|
| 158 |
child.on("error", (err) => {
|
|
|
|
| 151 |
}
|
| 152 |
if (!headersParsed) {
|
| 153 |
reject(new Error(`curl exited with code ${code}: ${stderrBuf}`));
|
| 154 |
+
} else if (code !== 0 && code !== null) {
|
| 155 |
+
// curl died mid-stream (e.g. connection reset, SIGPIPE) — signal error to reader
|
| 156 |
+
try {
|
| 157 |
+
bodyController?.error(new Error(`curl exited with code ${code} mid-stream: ${stderrBuf.trim() || "connection lost"}`));
|
| 158 |
+
} catch { /* stream already closed */ }
|
| 159 |
+
} else {
|
| 160 |
+
bodyController?.close();
|
| 161 |
}
|
|
|
|
| 162 |
});
|
| 163 |
|
| 164 |
child.on("error", (err) => {
|