Request Bodies
A request body is attached with the request_builder::body function.
The type of the value decides three things: the Content-Type header,
whether the body is sent with a known Content-Length or with chunked transfer
encoding, and how the bytes are produced on the wire.
How the Body Is Framed
body accepts any value for which a conversion is
defined, by calling tag_invoke with a body_from_tag<T>; the result is an
any_request_body. Two rules then hold for every body type:
-
The body’s
Content-Typeis sent unless the request already sets that header explicitly — settinghttp::field::content_typeon the request always wins. -
The framing — a
Content-Length, or chunked transfer encoding — is always taken from the body and cannot be overridden. A body whose size is known in advance is sent with aContent-Length; one whose size is not is sent chunked.
Built-in Body Types
Strings
A std::string, a std::string_view, or a string literal is sent as
text/plain; charset=utf-8 with a Content-Length:
auto [ec, r] = co_await client.post("https://example.com/post")
.body("plain text payload")
.send();
The default Content-Type can be overridden by setting the header explicitly:
auto [ec, r] = co_await client.post("https://example.com/post")
.body("<note>hi</note>")
.header(http::field::content_type, "application/xml")
.send();
JSON
The Boost.JSON types are sent as application/json, serialized incrementally as
the request goes out. Their serialized size is not known beforehand, so this is
the one built-in body sent with chunked transfer encoding:
json::object obj({ { "user", "John" }, { "lang", "En" } });
auto [ec, r] = co_await client.post("https://example.com/post")
.body(obj)
.send();
Naming the type builds the value inline, with no named variable:
auto [ec, r] = co_await client.post("https://example.com/post")
.body<json::array>({ 1, 2, 3 })
.send();
URL-Encoded Forms
urlencoded_form builds an application/x-www-form-urlencoded body from
name/value pairs, sent with a Content-Length.
append chains, and the same name may be added more
than once to submit a multi-valued field:
auto [ec, r] = co_await client.post("https://example.com/post")
.body(burl::urlencoded_form()
.append("user", "John")
.append("color", "blue")
.append("color", "green"))
.send();
// user=John&color=blue&color=green
Names and values are percent-encoded: only the RFC 3986 unreserved characters
pass through, spaces become +, and the & and = separators are escaped, so a
value can never break out of the field structure. A form can also be built from
an initializer list or any range of pairs:
std::map<std::string, std::string> fields = {
{ "lang", "En" },
{ "user", "John" } };
auto [ec, r] = co_await client.post("https://example.com/post")
.body<burl::urlencoded_form>(fields)
.send();
Multipart Forms
multipart_form builds a multipart/form-data body from a sequence of
parts separated by a generated boundary, as specified by
RFC 7578:
auto [ec, r] = co_await client.post("https://example.com/upload")
.body(burl::multipart_form()
.text("priority", "high")
.file("attachment", "./report.log"))
.send();
text adds a plain field, while
file adds a file that is streamed from disk as the
request is sent; only the path is held, with its filename and Content-Type
deduced from it unless set explicitly. When the data is already in memory but
should still arrive as a file, bytes writes a part
with a filename from an in-memory buffer.
Files
Naming std::filesystem::path uploads a file, streamed from disk so the
whole file is never held in memory. The Content-Type is deduced from the
filename extension, defaulting to application/octet-stream, and the
Content-Length is the file’s size:
auto [ec, r] = co_await client.put("https://example.com/put")
.body<std::filesystem::path>("./report.log")
.send();
The size is read when request_builder::body is called, so a path whose
size cannot be determined throws std::filesystem::filesystem_error from
that call. Because the length is then fixed, a file that shrinks before the
transfer completes fails the request with error::file_changed.
Your Own Types
request_builder::body accepts any type for which a tag_invoke conversion
is defined. Extending shows how to write one for a type
of your own.
Next Steps
-
Responses — Reading the body that comes back
-
Error Handling — Handling failures
-
Headers — Overriding the
Content-Type