Streaming results¶
Most Baldr handlers return synchronously — a value, an IResult subclass, or void. When the body is large or produced lazily, return an IStreamingResult instead. The framework will emit Transfer-Encoding: chunked and stream chunks as the producer makes them available.
When to use streaming¶
- The body is large (multi-megabyte file, generated report) and you want to avoid buffering it all in memory.
- The body is produced incrementally — server-sent events, tailing a log, proxying a producer-consumer pipeline.
- You need backpressure-friendly reads where the producer can stop producing if the client disconnects.
IStreamingResult¶
| StreamingResult.hpp | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Two rules:
- Implementations must not set
Content-LengthorTransfer-Encodingheaders — the framework writesTransfer-Encoding: chunkeditself. nextChunkis called repeatedly; returningfalsesignals end-of-body. Mutable state is allowed on the implementation becausenextChunkisconstonly by convention (the result object is held inside astd::shared_ptrso it can outlive the handler call).
Built-in streaming results¶
ChunkedStreamResult¶
Driven by a user-supplied callback that fills the next chunk. Useful for SSE or for reading from a producer.
app->MapGet("/events", [] {
return ChunkedStreamResult([](std::string& out) -> bool {
out = "data: tick\n\n";
return true;
});
});
ChunkedStreamResult::Producer is std::function<bool(std::string&)>. Returning false terminates the stream.
FileStreamResult¶
Streams a file from disk in 64 KiB chunks. The response sets Content-Type and Content-Disposition: attachment; filename="...".
#include <Baldr/Http/Results/FileStreamResult.hpp>
#include <fstream>
app->MapGet("/report.pdf", [] {
std::ifstream in("/var/data/report.pdf", std::ios::binary);
return FileStreamResult(std::move(in), "application/pdf", "report.pdf");
});
The full program is in examples/FileStream.
Custom streaming results¶
Derive from IStreamingResult and implement nextChunk. Use headers() for Content-Type and any other fixed headers (for example Cache-Control).
#pragma once
#include <Baldr/Http/Results/StreamingResult.hpp>
class SseStreamResult final : public IStreamingResult
{
public:
void headers(std::vector<std::pair<std::string, std::string>>& out) const override
{
out.clear();
out.emplace_back("Content-Type", "text/event-stream");
out.emplace_back("Cache-Control", "no-cache");
}
bool nextChunk(std::string& out) const override
{
if (finished)
return false;
out = "data: " + nextEvent() + "\n\n";
return true;
}
};
Interaction with middleware¶
CompressionMiddleware skips streaming responses — chunked transfer encoding is incompatible with on-the-fly body rewrites, so streamed payloads are sent uncompressed regardless of Accept-Encoding. Use a buffered IResult for compressible responses.
Response.streaming is set by the framework after the handler runs (src/Baldr/Http/Router.hpp and src/Baldr/Application/WebApplication.hpp) — middleware that needs to inspect or replace streaming bodies can read this field before passing control on.
Next steps¶
- See the
examples/FileStreamprogram for a complete file-streaming endpoint and an upload handler. - Combine with Static files for efficient static-asset serving.