Skip to content

Conversation

@mathieucarbou
Copy link
Member

@mathieucarbou mathieucarbou commented Nov 28, 2025

  1. Fix length passed to callback
    The calculated len was not taking in consideration the remaining bytes to send when a content-length was set.

  2. Fix ChunkPrint so that it returns written bytes and not the available bytes to write

Summary by CodeRabbit

  • Bug Fixes
    • Fixed async response byte counting to accurately report written bytes instead of requested length.
    • Improved content length enforcement to prevent reading beyond intended message boundaries.
    • Streamlined chunked buffer write operations with enhanced empty buffer handling.

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

@mathieucarbou mathieucarbou self-assigned this Nov 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

The pull request refactors byte tracking in async response classes and the ChunkPrint helper. Three JSON response methods now return actual bytes written rather than requested length, ChunkPrint fields are renamed to better reflect position semantics, and content-length constraints are properly enforced during reads.

Changes

Cohort / File(s) Summary
Async JSON Response Buffer Filling
src/AsyncJson.cpp
Updated AsyncJsonResponse::_fillBuffer, PrettyAsyncJsonResponse::_fillBuffer, and AsyncMessagePackResponse::_fillBuffer to return dest.written() instead of len, reflecting actual bytes written to destination.
ChunkPrint Byte Tracking Refactor
src/ChunkPrint.h
Replaced internal fields _to_skip, _to_write, _pos with _from, _len, _index. Added public written() method returning current _index. Constructor converted to inline definition.
ChunkPrint Write Logic
src/ChunkPrint.cpp
Removed public constructor; rewrote write(uint8_t) to use early bailout on zero length, refactored skip logic with _from field, and rewrote write accumulation using _index instead of previous tracking approach.
Content Length Read Limiting
src/WebResponses.cpp
Modified AsyncAbstractResponse write path to limit buffer reads to minimum of current buffer size, TCP window, and remaining content length (_contentLength - _sentLength), preventing reads beyond intended content boundary.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Verify the byte tracking changes in ChunkPrint correctly preserve skip/write semantics with new field names
  • Confirm written() return value accurately reflects bytes written in all three JSON response classes
  • Validate that content-length limiting logic in WebResponses doesn't inadvertently truncate partial writes

Poem

🐰 Bytes now tracked with careful grace,
No more guessing for their place,
ChunkPrint fields dance anew,
Content limits, tried and true!
Hop, skip, write—precision's art! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 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 changes: fixes to chunk processing (Json) and the length passed to the fill send buffer callback, which directly correspond to the modifications in ChunkPrint, AsyncJson, and WebResponses files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-346

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.

@mathieucarbou
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/WebResponses.cpp (1)

472-485: Clamp by remaining content length only when Content‑Length is known

The new std::min(..., _contentLength - _sentLength) correctly prevents over‑reading when a Content‑Length is set, but it also forces len == 0 for responses where _sendContentLength == false and _contentLength is left at 0 (e.g., AsyncCallbackResponse constructed with len == 0 to stream until the filler returns 0). In that case _fillBufferAndProcessTemplates is always called with len == 0, so callbacks that previously streamed data now send nothing, and _contentLength - _sentLength can also underflow if _sentLength ever exceeds _contentLength.

To preserve existing behavior for unknown‑length responses while still fixing the Content‑Length bug, consider something like:

-      } else {
-        size_t const readLen = _fillBufferAndProcessTemplates(_send_buffer->data(), std::min(std::min(_send_buffer->size(), tcp_win), _contentLength - _sentLength));
+      } else {
+        size_t maxLen = std::min(_send_buffer->size(), tcp_win);
+        if (_sendContentLength && _sentLength < _contentLength) {
+          maxLen = std::min(maxLen, _contentLength - _sentLength);
+        }
+        size_t const readLen = _fillBufferAndProcessTemplates(_send_buffer->data(), maxLen);

This keeps the new “respect remaining Content‑Length” behavior for known‑length responses, avoids size_t underflow, and leaves the len == 0/no‑Content‑Length streaming callbacks working as before.

🧹 Nitpick comments (1)
src/ChunkPrint.h (1)

11-23: ChunkPrint state and inline constructor look correct; written() could be const

The new _from/_len/_index fields and inline constructor align with the updated write(uint8_t) logic, and exposing written() is exactly what the JSON/MsgPack paths need. As a small style/readability tweak, you could mark written() as const since it doesn’t mutate state:

-  size_t written() {
-    return _index;
-  }
+  size_t written() const {
+    return _index;
+  }

Purely optional; behavior is fine as is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 079139a and 4ff1443.

📒 Files selected for processing (4)
  • src/AsyncJson.cpp (3 hunks)
  • src/ChunkPrint.cpp (1 hunks)
  • src/ChunkPrint.h (1 hunks)
  • src/WebResponses.cpp (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/ChunkPrint.h (1)
src/ChunkPrint.cpp (2)
  • write (6-23)
  • write (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: RPI (arduino-cli) (rpipico2w)
  • GitHub Check: RPI (arduino-cli) (rpipicow)
  • GitHub Check: ESP8266 (pio) (d1_mini)
  • GitHub Check: ESP8266 (pio) (huzzah)
  • GitHub Check: ESP8266 (arduino-cli)
  • GitHub Check: ESP32 (pio) - Arduino 2 (esp32-s3-devkitc-1)
  • GitHub Check: ESP32 (pio) - Arduino 3 (esp32-s3-devkitc-1)
  • GitHub Check: ESP32 (pio) - Arduino 3 (esp32-s2-saola-1)
  • GitHub Check: ESP32 (pio) - Specific Envs (ci-no-json)
  • GitHub Check: ESP32 (pio) - Specific Envs (ci-arduino-3-esp-idf-log)
  • GitHub Check: ESP32 (pio) - Arduino 3 (esp32-p4)
  • GitHub Check: ESP32 (pio) - Specific Envs (ci-latest-asynctcp)
  • GitHub Check: ESP32 (pio) - Specific Envs (ci-regex)
  • GitHub Check: ESP32 (pio) - Specific Envs (ci-arduino-2-esp-idf-log)
  • GitHub Check: ESP32 (pio) - Arduino 3 (esp32dev)
  • GitHub Check: ESP32 (pio) - Specific Envs (ci-no-chunk-inflight)
  • GitHub Check: ESP32 (pio) - Arduino 2 (esp32dev)
  • GitHub Check: ESP32 (pio) - Arduino 2 (esp32-s2-saola-1)
  • GitHub Check: ESP32 (arduino-cli) - Dev
  • GitHub Check: ESP32 (arduino-cli) - Release
🔇 Additional comments (2)
src/AsyncJson.cpp (1)

57-65: Returning dest.written() correctly reports actual bytes produced

Switching the JSON/pretty‑JSON/MessagePack _fillBuffer methods to return dest.written() instead of the requested len fixes over‑reporting near the end of the payload and keeps _sentLength aligned with the bytes actually serialized into the buffer. This matches the new ChunkPrint semantics and is the right behavior for the streaming response machinery.

Also applies to: 85-93, 106-110

src/ChunkPrint.cpp (1)

6-22: ChunkPrint::write(uint8_t) correctly enforces skip and length window

The new implementation cleanly skips the first _from bytes, then writes at most _len bytes into _destination, and finally returns 0 once the window is exhausted, which is exactly what the JSON/MsgPack chunking logic expects. The early _len == 0 bailout also avoids unnecessary work for zero‑length calls; no changes needed here.

The calculated len was not taking in consideration the remaining bytes to send when a content-length was set.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes two bugs related to buffer length handling in the web server response system:

  • Corrects the length calculation in write_send_buffs to account for remaining content when content-length is set
  • Updates ChunkPrint to return actual bytes written instead of buffer capacity

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/WebResponses.cpp Adds _contentLength - _sentLength constraint to prevent reading beyond remaining content in non-chunked responses
src/ChunkPrint.h Refactors member variables for clarity, moves constructor inline, and adds written() method to return actual bytes written
src/ChunkPrint.cpp Updates write() implementation with clearer logic and comments for byte skipping and writing
src/AsyncJson.cpp Changes three methods to return dest.written() instead of len to report actual bytes written rather than buffer size

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mathieucarbou
Copy link
Member Author

@me-no-dev @vortigont @willmmiles : please have a look at these bug fixes when you can: serving json content is not correctly working for big documents currently.
Thanks!

Ref: #346 (reply in thread)

@mathieucarbou mathieucarbou changed the title Several fixes Several fixes around Chunk processing (Json) of a large Json document and available length passed to fill send buffer Nov 28, 2025
@vortigont
Copy link

Just a quick view while I'm on travel
_fillBufferAndProcessTemplates(_send_buffer->data(), std::min(std::min(_send_buffer->size(), tcp_win), _contentLength - _sentLength));

this looks logically wrong to me
to _fillBufferAndProcessTemplates args we pass buffer pointer and it's size, it should not depend on content length anyhow. It's just a buffer, it has only two dedicated vars to track it's use - _send_buffer_len and _send_buffer_offset.
Probably the issue should be corrected somewhere else.
If it fixes the problem it's probably OK to merge, but I want to take a closer look to it when I'll get back.

@willmmiles
Copy link

Just a quick view while I'm on travel _fillBufferAndProcessTemplates(_send_buffer->data(), std::min(std::min(_send_buffer->size(), tcp_win), _contentLength - _sentLength));

this looks logically wrong to me to _fillBufferAndProcessTemplates args we pass buffer pointer and it's size, it should not depend on content length anyhow. It's just a buffer, it has only two dedicated vars to track it's use - _send_buffer_len and _send_buffer_offset. Probably the issue should be corrected somewhere else. If it fixes the problem it's probably OK to merge, but I want to take a closer look to it when I'll get back.

That was my first thought too, actually! So I went back and checked older versions. It turns out that limiting the length passed to the callback has been the behaviour for over a decade.

https://github.com/ESP32Async/ESPAsyncWebServer/blame/b015b8db548030ef8bb5de01e395e5b6a15af048/src/WebResponses.cpp#L298

I'm not sure I'd do it that way on a clean sheet design, but for a library like this, it's probably better to keep the behaviour constant.

@mathieucarbou
Copy link
Member Author

Probably the issue should be corrected somewhere else. If it fixes the problem it's probably OK to merge, but I want to take a closer look to it when I'll get back.

Yes right now the idea is to merge this fix asap and release until a more correct approach is done.

There were 2 issues that are corrected here:

  1. The behavior change (when a content type was set, length was matching the remaining bytes if lower
  2. the chunk process, which was returning len instead of returning the written bytes

Changing the behavior and sending a differently calculated len to the callbacks may also impacts other parts of the code, not only the json implementation where the bug was seen.

So if you change the behavior, you also need to review the template code to make sure it remains valid.

@mathieucarbou mathieucarbou merged commit 73b0edf into main Nov 29, 2025
33 checks passed
@mathieucarbou mathieucarbou deleted the issue-346 branch November 29, 2025 10:28
mathieucarbou added a commit that referenced this pull request Jan 1, 2026
See: #349

Fix bug for non-chucked responses without a known content-length (see LargeResponse example)
mathieucarbou added a commit that referenced this pull request Jan 1, 2026
URGENT - Fix bug introduced in #349
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants