mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix(SSE-text/event-stream): sse response body is empty in res.getBody() for app and cli (#8212)
This commit is contained in:
committed by
GitHub
parent
6791e0a674
commit
79504ed729
@@ -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;
|
||||
|
||||
35
packages/bruno-tests/collection/sse/sse finite stream.bru
Normal file
35
packages/bruno-tests/collection/sse/sse finite stream.bru
Normal 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"));
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user