Files
cpp-httplib/docs-src/pages/en/cookbook/s07-multipart-reader.md
T
yhirose 9bfd2ddf1c Fix README and docs pages that no longer match the code
Bring the READMEs and the docs pages (en and ja) in line with the
current header:

- Fix the client sample that does not compile, the Transfer-Encoding
  statement for default headers, the verifier callback comment, the
  thread pool spawn rule, the sanitize_filename description, and the
  inverted Accept-Encoding sentence.
- Describe recent behaviour: TLS errors behind Read/Write failures,
  Host validation, Connection: close for a body without length,
  content_reader rules, refused CONNECT, WebSocketClient proxies,
  CPPHTTPLIB_INTERIM_RESPONSE_MAX_COUNT, the SSE event size limit, and
  the payload limit of the Stream API.
- Complete the API references of WebSocket, WebSocketClient and the
  stream:: helpers, and the SSE reconnect rules.
- Fix cookbook pages: keychain loading on every backend, received Close
  status, a multipart sample that trusted the client's file name, an
  echo sample that sent binary as text, SNI, pre-request handler scope,
  stop() with WebSocket handlers, and the thread pool notes.
- Restore the mutex in the LLM app's Chapter 5 code.
2026-10-18 10:13:57 -04:00

112 lines
4.0 KiB
Markdown

---
title: "S07. Receive Multipart Data as a Stream"
order: 26
status: "draft"
---
A naive upload handler puts the whole request into `req.body`, which blows up memory for large files. Use `HandlerWithContentReader` to receive the body chunk by chunk.
## Basic usage
```cpp
svr.Post("/upload",
[](const httplib::Request &req, httplib::Response &res,
const httplib::ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
content_reader(
// headers of each part
[&](const httplib::FormData &file) {
std::cout << "name: " << file.name
<< ", filename: " << file.filename << std::endl;
return true;
},
// body of each part (called multiple times)
[&](const char *data, size_t len) {
// write to disk here, for example
return true;
});
} else {
// plain request body
content_reader([&](const char *data, size_t len) {
return true;
});
}
res.set_content("ok", "text/plain");
});
```
The `content_reader` has two call shapes. For multipart data, pass two callbacks (one for headers, one for body). For plain bodies, pass just one.
## Write directly to disk
Here's how to stream an uploaded file to disk.
```cpp
svr.Post("/upload",
[](const httplib::Request &req, httplib::Response &res,
const httplib::ContentReader &content_reader) {
std::ofstream ofs;
auto ok = content_reader(
[&](const httplib::FormData &file) {
if (ofs.is_open()) { ofs.close(); } // done with the previous file
if (file.filename.empty()) { return true; } // a plain field, skip it
auto name = httplib::sanitize_filename(file.filename);
if (name.empty()) { return false; } // reject an unsafe name
ofs.open("uploads/" + name, std::ios::binary);
return ofs.is_open();
},
[&](const char *data, size_t len) {
if (!ofs.is_open()) { return true; } // not part of a file
ofs.write(data, len);
return static_cast<bool>(ofs);
});
if (!ok) { return; } // the status is already set
res.set_content("uploaded", "text/plain");
});
```
The header callback runs at the start of every part, so it closes the previous file before opening the next one. `file.filename` is whatever the client sent, so always pass it through `httplib::sanitize_filename()`. An empty result means the name is unsafe.
Only a small chunk sits in memory at any moment, so gigabyte-scale files are no problem.
## Count the parts yourself
There is a cap on the number of parts, `CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT` (1024 by default), but it only applies to the buffered path, where every part is accumulated into `req.form`. The `ContentReader` keeps nothing on the library side, so the cap does not apply here.
If you want an upper bound, count the parts yourself and return `false` from the header callback. The parser stops right there.
```cpp
svr.Post("/upload",
[](const httplib::Request &req, httplib::Response &res,
const httplib::ContentReader &content_reader) {
size_t count = 0;
auto ok = content_reader(
[&](const httplib::FormData &file) {
if (++count > 100) { return false; } // stop here
return true;
},
[&](const char *data, size_t len) {
return true;
});
if (!ok) {
res.status = httplib::StatusCode::BadRequest_400;
return;
}
res.set_content("ok", "text/plain");
});
```
When `content_reader` returns `false`, the response status becomes 400 (413 if the body exceeded the size limit). Set it yourself if you want a different one. The rest of the body is left unread and the connection is closed, so a client that is still sending sees the connection drop.
> **Warning:** When you use `HandlerWithContentReader`, `req.body` stays **empty**. Handle the body yourself inside the callbacks.
> For the client side of multipart uploads, see [C07. Upload a file as multipart form data](../c07-multipart-upload).