Skip to content

Add streamed response callback with chunked transfer support#1151

Open
Kirpatik wants to merge 1 commit into
CrowCpp:masterfrom
Kirpatik:feature/streamed-response-chunked-send
Open

Add streamed response callback with chunked transfer support#1151
Kirpatik wants to merge 1 commit into
CrowCpp:masterfrom
Kirpatik:feature/streamed-response-chunked-send

Conversation

@Kirpatik

Copy link
Copy Markdown

Summary

This PR adds explicit streamed response sending via callback, including support for unknown total size responses.

Added API

  • Known-length streaming:
    • set_streamed_body(std::function<size_t(void*, size_t)> reader, size_t content_length, std::string content_type = "", size_t chunk_size = 16 * 1024)
  • Unknown-length streaming:
    • set_streamed_body(std::function<size_t(void*, size_t)> reader, std::string content_type = "", size_t chunk_size = 16 * 1024)

Callback contract:

  • Crow provides (buffer, max_size)
  • callback writes up to max_size bytes
  • callback returns written size
  • callback returns 0 to indicate end-of-stream

Behavior

  • HTTP/1.1+ unknown-size responses are sent using Transfer-Encoding: chunked.
  • HTTP/1.0 falls back to connection-close delimited streaming (no chunked encoding).
  • chunk_size is configurable per response; default remains 16KB.
  • HEAD with known-length streamed responses sends headers only and preserves Content-Length.

Implementation Notes

  • Streaming logic is handled in Connection::do_write_streamed().
  • Response metadata now stores stream mode and chunk size.
  • Existing non-streaming behavior is unchanged.

Tests

Added/updated tests:

  • streamed_response
  • streamed_response_unknown_size_chunked
  • streamed_response_unknown_size_http10_fallback
  • streamed_response_head_known_length_no_body

@gittiver

Copy link
Copy Markdown
Member

Sounds great. :)

@gittiver

Copy link
Copy Markdown
Member

The current implementation would block one working thread per running client request, right?

@Kirpatik

Kirpatik commented Mar 2, 2026

Copy link
Copy Markdown
Author

The current implementation would block one working thread per running client request, right?

Yes.

do_write_streamed() is called synchronously from complete_request() and performs blocking asio::write calls in a loop, so one worker thread stays occupied by that request until the stream finishes.

@gittiver gittiver added the discussion The viability / implementation of the issue is up for debate label Jun 21, 2026
@draminski

Copy link
Copy Markdown

We have a concrete use case for this feature in a C++ real-time imaging application.

Our HTTP server exposes a long-lived MJPEG endpoint using:

Content-Type: multipart/x-mixed-replace; boundary=frame

Each connected client waits for a newly produced JPEG frame and then sends the multipart header and image bytes. We currently use cpp-httplib because its chunked content provider supports this model.

We evaluated Crow 1.3.3, but response::write() buffers data until response::end(), so it cannot implement this endpoint. The unknown-length set_streamed_body() API proposed here appears to provide exactly the missing functionality.

For our expected deployment, the blocking implementation may be acceptable because the number of simultaneous MJPEG viewers is small. However, cancellation when a client disconnects and clean server shutdown are important to us.

Is this API still being considered for Crow? We would also be interested in testing the branch with our MJPEG workload and reporting the results.

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

Labels

discussion The viability / implementation of the issue is up for debate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants