fix(SSE-text/event-stream): sse response body is empty in res.getBody() for app and cli (#8212)

This commit is contained in:
sachin-thakur-bruno
2026-06-10 18:40:47 +05:30
committed by GitHub
parent 6791e0a674
commit 79504ed729
3 changed files with 85 additions and 1 deletions

View File

@@ -73,6 +73,12 @@ const hasStreamHeaders = (headers) => {
return headerSplit.indexOf('text/event-stream') > -1;
};
const buildResponseBodyFromStreamChunks = (sseChunks, headers, disableParsingResponseJson) => {
const dataBuffer = Buffer.concat(sseChunks);
const { data } = parseDataFromResponse({ data: dataBuffer, headers }, disableParsingResponseJson);
return { data, dataBuffer };
};
const promisifyStream = async (stream, abortController, closeOnFirst) => {
const chunks = [];
@@ -1014,6 +1020,7 @@ const registerNetworkIpc = (mainWindow) => {
}
let response, responseTime, axiosDataStream;
const sseChunks = [];
try {
/** @type {import('axios').AxiosResponse} */
response = await axiosInstance(request);
@@ -1243,7 +1250,22 @@ const registerNetworkIpc = (mainWindow) => {
}
};
if (isResponseStream) {
axiosDataStream.on('close', () => runPostScripts().then());
axiosDataStream.on('close', () => {
try {
const { data, dataBuffer } = buildResponseBodyFromStreamChunks(
sseChunks,
response.headers,
request.__brunoDisableParsingResponseJson
);
response.data = data;
response.dataBuffer = dataBuffer;
} catch (error) {
console.error('Error rebuilding response body from SSE chunks:', error);
}
runPostScripts().catch((error) => {
console.error('Error running post-response scripts for SSE stream:', error);
});
});
} else {
await runPostScripts();
}
@@ -1254,6 +1276,7 @@ const registerNetworkIpc = (mainWindow) => {
headers: response.headers,
data: response.data,
stream: isResponseStream ? axiosDataStream : null,
sseChunks: isResponseStream ? sseChunks : null,
cancelTokenUid: cancelTokenUid,
dataBuffer: response.dataBuffer.toString('base64'),
size: Buffer.byteLength(response.dataBuffer),
@@ -1332,6 +1355,8 @@ const registerNetworkIpc = (mainWindow) => {
response.stream = { running: response.status >= 200 && response.status < 300 };
stream.on('data', (newData) => {
// Collect the raw chunk so runRequest can rebuild the full body on stream close.
response.sseChunks?.push(newData);
seq += 1;
const parsed = parseDataFromResponse({ data: newData, headers: {} });
@@ -2248,3 +2273,4 @@ module.exports.configureRequest = configureRequest;
module.exports.getCertsAndProxyConfig = getCertsAndProxyConfig;
module.exports.fetchGqlSchemaHandler = fetchGqlSchemaHandler;
module.exports.executeRequestOnFailHandler = executeRequestOnFailHandler;
module.exports.buildResponseBodyFromStreamChunks = buildResponseBodyFromStreamChunks;

View File

@@ -0,0 +1,35 @@
meta {
name: sse finite stream
type: http
seq: 1
}
get {
url: {{localhost}}/api/sse/finite
body: none
auth: none
}
script:post-response {
const body = res.getBody();
bru.setVar("sseBody", typeof body === "string" ? body : JSON.stringify(body));
}
tests {
test("status is 200", function() {
expect(res.status).to.equal(200);
});
test("content-type is text/event-stream", function() {
expect(res.headers["content-type"]).to.include("text/event-stream");
});
test("res.getBody() contains all 3 SSE events in order", function() {
const body = res.getBody();
expect(body).to.include("data: Hello");
expect(body).to.include("data: from");
expect(body).to.include("data: SSE");
expect(body.indexOf("data: Hello")).to.be.lessThan(body.indexOf("data: from"));
expect(body.indexOf("data: from")).to.be.lessThan(body.indexOf("data: SSE"));
});
}

View File

@@ -55,4 +55,27 @@ router.post('/reset', (req, res) => {
res.json({ message: 'Reset complete', activeConnections: 0 });
});
// GET /api/sse/finite - Sends one event per message then closes.
router.get('/finite', (req, res) => {
const messages = ['Hello', 'from', 'SSE'];
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
let index = 0;
const interval = setInterval(() => {
res.write(`data: ${messages[index]}\n\n`);
index += 1;
if (index >= messages.length) {
clearInterval(interval);
res.end();
}
}, 50);
req.on('close', () => clearInterval(interval));
});
module.exports = router;