Skip to content

fix(runtime): prevent response stream truncation for large event-stre…#6696

Open
ltanme wants to merge 1 commit intousebruno:mainfrom
ltanme:fix/event-stream-truncation
Open

fix(runtime): prevent response stream truncation for large event-stre…#6696
ltanme wants to merge 1 commit intousebruno:mainfrom
ltanme:fix/event-stream-truncation

Conversation

@ltanme
Copy link
Copy Markdown

@ltanme ltanme commented Jan 6, 2026

Description

This PR focuses on fixing streaming response truncation for text/event-stream (SSE) responses, especially when large payloads or UTF‑8 multi-byte characters are split across network chunk boundaries.

Context / Problem

When Bruno requests an SSE endpoint and the server streams a large amount of data:

  • Individual stream chunks may split UTF‑8 multi-byte sequences, causing corrupted text or “missing” characters if decoded independently.
  • A single SSE event can arrive across multiple chunks; without SSE frame reassembly, the UI may show incomplete fragments (e.g. truncated JSON).
  • Very large chunks can cause heavy UI work (hexdump generation), making it appear like messages are dropped due to slowdown.

What changed

  • Add a streaming-safe UTF‑8 decoder (StringDecoder) in the Electron main process, so no bytes are lost across chunk boundaries.
  • Reassemble SSE frames using standard SSE separators (\\n\\n and \\r\\n\\r\\n) and emit a complete SSE event to the renderer as a single message.
    • Only data: lines are extracted and joined per the SSE parsing rules.
    • Includes a safety valve to flush buffered text if no SSE separator appears for too long, preventing unbounded buffering.
  • Limit per-message hexdump generation in the UI (cap to 32KB) to prevent UI stalls on large stream chunks.

Tests

Adds coverage for:

  • No UTF‑8 loss when multi-byte characters are split across chunk boundaries.
  • Reassembly of a large single SSE event split across many chunks.
  • Fallback behavior when no SSE separators appear (buffer flush).

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • Bug Fixes

    • Optimized hexdump generation for large streaming responses to improve performance and prevent excessive memory usage.
  • New Features

    • Enhanced Server-Sent Events (SSE) streaming support with improved data parsing, chunk reassembly, and fallback handling for fragmented message streams.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 6, 2026

Walkthrough

These changes introduce a new SSE (Server-Sent Events) event stream parser module to handle streaming HTTP responses more robustly, integrate it into the network layer's data handling pipeline, and add a hexdump length limiter to prevent excessive memory consumption from large streaming chunks.

Changes

Cohort / File(s) Summary
SSE Event Stream Parser
packages/bruno-electron/src/ipc/network/event-stream.js, packages/bruno-electron/tests/network/event-stream-decoding.spec.js
New module implementing createEventStreamEmitter that parses Server-Sent Events with frame boundary detection (CRLF/LF), UTF-8 multi-byte handling via StringDecoder, and a safety valve that emits raw buffered content when exceeding maxBufferedChars threshold. Test suite covers UTF-8 boundary splitting, large payload reassembly, and fallback behavior for non-SSE data.
Network Layer Integration
packages/bruno-electron/src/ipc/network/index.js
Refactored streaming data handling to route HTTP response chunks through the new event stream emitter instead of direct parsing, preserving IPC messaging but now with structured payload delivery (collectionUid, itemUid, seq, timestamp, data).
Hexdump Memory Optimization
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
Introduces MAX_HEXDUMP_BYTES constant (32 KB) to limit hexdump generation length in streamDataReceived, preventing excessively large hexdumps from streaming chunks.

Sequence Diagram(s)

sequenceDiagram
    participant HTTP as HTTP Stream
    participant ESE as Event Stream<br/>Emitter
    participant IPC as Electron IPC
    participant App as Redux Store<br/>(collections)
    
    rect rgb(230, 245, 255)
    Note over HTTP,App: New SSE Streaming Flow
    end
    
    HTTP->>ESE: write(chunk)
    ESE->>ESE: Accumulate in buffer<br/>(StringDecoder utf8)
    ESE->>ESE: drain(): Scan for frame<br/>boundaries (CRLF/LF)
    
    alt Frame detected
        ESE->>ESE: Extract & parse SSE data
        ESE->>IPC: onMessage(parsed data)
    else Buffer exceeds threshold
        ESE->>IPC: onMessage(raw buffer)<br/>(safety valve)
    end
    
    IPC->>App: main:http-stream-new-data<br/>{seq, timestamp, data}
    App->>App: Limit hexdump to<br/>MAX_HEXDUMP_BYTES
    
    HTTP->>ESE: end()
    ESE->>ESE: Flush decoder & drain
    ESE->>IPC: onMessage(final data)
    IPC->>App: main:http-stream-end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • bijin-bruno

Poem

🌊 Streams flow through frames now,
SSE parser gently buffers chunks,
Hexdumps trimmed to reason,
Large data tamed with grace—
Efficient bytes dance past. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing response stream truncation for large event-stream responses, which aligns with the PR's core objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7918431 and 7eabbfb.

📒 Files selected for processing (4)
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-electron/src/ipc/network/event-stream.js
  • packages/bruno-electron/src/ipc/network/index.js
  • packages/bruno-electron/tests/network/event-stream-decoding.spec.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-electron/tests/network/event-stream-decoding.spec.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-electron/src/ipc/network/event-stream.js
  • packages/bruno-electron/src/ipc/network/index.js
🧠 Learnings (2)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • packages/bruno-electron/tests/network/event-stream-decoding.spec.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-electron/tests/network/event-stream-decoding.spec.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-electron/src/ipc/network/event-stream.js
  • packages/bruno-electron/src/ipc/network/index.js
🧬 Code graph analysis (2)
packages/bruno-electron/tests/network/event-stream-decoding.spec.js (1)
packages/bruno-electron/src/ipc/network/event-stream.js (2)
  • require (1-1)
  • createEventStreamEmitter (29-81)
packages/bruno-electron/src/ipc/network/index.js (1)
packages/bruno-electron/src/ipc/network/event-stream.js (1)
  • createEventStreamEmitter (29-81)
🔇 Additional comments (10)
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js (1)

3127-3134: LGTM! Prevents UI stalls from large stream chunks.

The 32KB hexdump limit is a sensible safeguard. The constant is defined inline, which is acceptable for single-use. If reuse across reducers is anticipated, consider moving it to module scope.

packages/bruno-electron/src/ipc/network/index.js (3)

39-39: LGTM! Clean import of the new event stream emitter.


1015-1026: LGTM! Clean integration of SSE emitter with IPC message dispatch.

The onMessage callback properly structures the payload and maintains sequence ordering. This centralizes the parsing logic in the event-stream module.


1028-1036: LGTM! Proper stream termination sequence.

The emitter.end() call on line 1035 ensures any buffered content is flushed before the http-stream-end signal is sent on line 1037. This prevents data loss at stream boundaries.

packages/bruno-electron/tests/network/event-stream-decoding.spec.js (3)

4-17: LGTM! Critical test for UTF-8 boundary handling.

Splitting the buffer at buf.length - 1 forces an incomplete multi-byte sequence across chunk boundaries, directly testing the StringDecoder's ability to preserve UTF-8 integrity. This validates the core fix for stream truncation.


19-35: LGTM! Validates large payload reassembly.

The 1KB chunk size realistically simulates TCP packet boundaries. Verifying a single complete message ensures the SSE frame detection and buffering logic works correctly at scale.


37-45: LGTM! Tests safety valve for unbounded buffering.

Setting maxBufferedChars: 10 and sending 15 bytes without SSE separators verifies the fallback flush behavior prevents unbounded memory growth.

packages/bruno-electron/src/ipc/network/event-stream.js (3)

3-27: LGTM! SSE parsing follows the spec correctly.

The .replace(/^\s/, '') on line 14 removes exactly one leading space per the SSE specification. The fallback on line 26 handles non-compliant servers gracefully by surfacing raw text rather than silently dropping data.


29-65: LGTM! Robust frame detection with safety valve.

The StringDecoder on line 30 ensures UTF-8 integrity across chunk boundaries. The drain logic correctly handles both \r\n\r\n and \n\n separators. The safety valve (lines 60-64) prevents unbounded memory growth when no SSE separators appear, emitting raw buffered content instead.


67-81: LGTM! Proper stream finalization.

The end() method on line 72 ensures all buffered data is flushed: decoder.end() finalizes any incomplete UTF-8 sequences, drain() processes remaining frames, and the final check on line 75 emits any leftover content. No data is lost at stream termination.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ltanme
Copy link
Copy Markdown
Author

ltanme commented Jan 6, 2026

image When the backend returns large streaming text responses (e.g. text/event-stream or long incremental text chunks), Bruno may truncate the response content.

In practice, this shows up as:

The streamed text being cut off in the middle

Broken UTF-8 characters

Partial JSON output that cannot be fully viewed or formatted in the UI

The same endpoint works correctly with curl, indicating the request itself is valid and the issue is on the client-side streaming/decoding path.

Copy link
Copy Markdown
Collaborator

@sid-bruno sid-bruno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General review

  1. The EventSource spec while correctly referenced is not always followed and so it is valid to have sse text/event-stream responses where I might never get data: , there's also json based streams in certain cases for example, https://sse.dev/testpage.html , so I can't really go forward

  2. The backpressure handling makes sense but you are in a node.js environment and can directly use one of the https://nodejs.org/api/stream.html solutions instead of the custom one here since that's already a lot more tested and will handle the backpressure better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants