2.1 KiB
title, order, status
| title | order | status |
|---|---|---|
| S05. Stream a Large File in the Response | 24 | draft |
When the response is a huge file or data generated on the fly, loading the whole thing into memory isn't realistic. Use Response::set_content_provider() to produce data in chunks as you send it.
When the size is known
svr.Get("/download", [](const httplib::Request &req, httplib::Response &res) {
size_t total_size = get_file_size("large.bin");
res.set_content_provider(
total_size, "application/octet-stream",
[](size_t offset, size_t length, httplib::DataSink &sink) {
auto data = read_range_from_file("large.bin", offset, length);
sink.write(data.data(), data.size());
return true;
});
});
The lambda is called repeatedly with offset and length. Read just that range and write it to sink. Only a small chunk sits in memory at any given time.
Just send a file
If you only want to serve a file, set_file_content() is far simpler.
svr.Get("/download", [](const httplib::Request &req, httplib::Response &res) {
res.set_file_content("large.bin", "application/octet-stream");
});
It streams internally, so even huge files are safe. Omit the Content-Type and it's guessed from the extension.
When the size is unknown — chunked transfer
For data generated on the fly, where you don't know the total size up front, use set_chunked_content_provider(). It's sent with HTTP chunked transfer encoding.
svr.Get("/events", [](const httplib::Request &req, httplib::Response &res) {
res.set_chunked_content_provider(
"text/plain",
[](size_t offset, httplib::DataSink &sink) {
auto chunk = produce_next_chunk();
if (chunk.empty()) {
sink.done(); // done sending
return true;
}
sink.write(chunk.data(), chunk.size());
return true;
});
});
Call sink.done() to signal the end.
Note: The provider lambda is called multiple times. Watch out for the lifetime of captured variables — wrap them in a
std::shared_ptrif needed.
To serve the file as a download, see S06. Return a File Download Response.