5. Request Body¶
There are five body sources. Only one per request can be used — mixing two or more is rejected
with request_protocol_error ("single body source").
| Source | Method | Content-Type |
|---|---|---|
| typed JSON DTO | body(dto) |
application/json (automatic) |
| raw | body(content, content_type) |
as specified |
| form | repeated form(name, value) |
application/x-www-form-urlencoded (automatic) |
| multipart | multipart(...) / multipart_file(...) |
multipart/form-data; boundary=... (automatic) |
| streaming | body_stream(provider, content_type) |
as specified + chunked |
Typed JSON DTO¶
Once you define an nlohmann ADL function (to_json) for a DTO, body(dto) serializes it as JSON.
The convention is that application code doesn't assemble nlohmann::json directly.
struct create_game_http_req_t
{
std::string game_name;
};
void to_json (nlohmann::json &json, const create_game_http_req_t &value)
{
json = nlohmann::json{{"gameName", value.game_name}};
}
auto created = client.post ("/games")
.body (create_game_http_req_t{.game_name = "ranked-match-0611"})
.fetch<create_game_http_res_t> ();
Raw Body¶
For a non-JSON payload, provide the content and content-type together.
// When an external payment gateway requires XML
auto receipt = client.post ("/billing/receipts")
.body (R"(<receipt order="ord-77231" amount="4900" currency="KRW"/>)",
"application/xml")
.submit_raw ()
.result ();
Form (x-www-form-urlencoded)¶
Accumulating form(name, value) builds an urlencoded body. Values are automatically
percent-encoded.
// An API that requires a form, like an OAuth token endpoint
auto token = client.post ("/oauth/token")
.form ("grant_type", "client_credentials")
.form ("client_id", "matchmaker-svc")
.form ("client_secret", service_secret)
.submit_raw ()
.result ();
Multipart/Form-Data¶
Text fields go on with multipart, and files with multipart_file. The boundary is generated
automatically.
auto uploaded = client.post ("/players/7281/avatar")
.multipart ("visibility", "public")
.multipart_file ("file", "avatar.png", png_bytes, "image/png")
.submit_raw ()
.result ();
Streaming Upload (Chunked)¶
For large transfers where the whole body can't fit in memory, use body_stream. The provider keeps
returning chunks until it returns std::nullopt, which ends it. It's sent with chunked
transfer-encoding.
std::ifstream replay ("/var/games/replays/r-99182.bin", std::ios::binary);
auto result = client.post ("/replays")
.body_stream (
[&replay] () -> std::optional<std::string> {
std::string chunk (64 * 1024, '\0');
replay.read (chunk.data (), chunk.size ());
chunk.resize (static_cast<std::size_t> (replay.gcount ()));
if (chunk.empty ()) {
return std::nullopt; // end of transfer
}
return chunk;
},
"application/octet-stream")
.submit_raw ()
.result ();
Constraint — since the provider can't be rewound once consumed:
- A streaming upload always goes over a fresh connection, bypassing the connection pool.
- It's excluded from automatic retry. If a retry is needed, the caller builds a new provider and calls it again.