Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
* xref:index.adoc[Introduction]
* xref:quick-start.adoc[Quick Start]
* xref:quick-start.adoc[]
* Guide
** xref:2.guide/2a.making-requests.adoc[Making Requests]
** xref:2.guide/2b.request-bodies.adoc[Request Bodies]
** xref:2.guide/2c.responses.adoc[Responses]
** xref:2.guide/2d.error-handling.adoc[Error Handling]
** xref:2.guide/2e.headers.adoc[Headers]
** xref:2.guide/2f.query-parameters.adoc[Query Parameters]
** xref:2.guide/2g.authentication.adoc[Authentication]
** xref:2.guide/2h.redirects.adoc[Redirects]
** xref:2.guide/2i.cookies.adoc[Cookies]
** xref:2.guide/2j.timeouts.adoc[Timeouts]
** xref:2.guide/2k.connection-pool.adoc[Connection Pool]
** xref:2.guide/2l.proxies.adoc[Proxies]
** xref:2.guide/2m.compression.adoc[Compression]
** xref:2.guide/2n.extending.adoc[Extending]
** xref:2.guide/2a.making-requests.adoc[]
** xref:2.guide/2b.request-bodies.adoc[]
** xref:2.guide/2c.responses.adoc[]
** xref:2.guide/2d.error-handling.adoc[]
** xref:2.guide/2e.headers.adoc[]
** xref:2.guide/2f.query-parameters.adoc[]
** xref:2.guide/2g.authentication.adoc[]
** xref:2.guide/2h.redirects.adoc[]
** xref:2.guide/2i.cookies.adoc[]
** xref:2.guide/2j.timeouts.adoc[]
** xref:2.guide/2k.connection-pool.adoc[]
** xref:2.guide/2l.proxies.adoc[]
** xref:2.guide/2m.compression.adoc[]
** xref:2.guide/2n.extending.adoc[]
* Examples
** xref:examples/usage.adoc[Usage]
** xref:examples/nlohmann_json.adoc[nlohmann/json]
* xref:testing.adoc[Testing]
** xref:examples/usage.adoc[]
** xref:examples/nlohmann_json.adoc[]
* xref:testing.adoc[]
* xref:reference:boost/burl.adoc[Reference]
14 changes: 6 additions & 8 deletions doc/modules/ROOT/pages/2.guide/2a.making-requests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ feature. A chain is finished by one of three terminal calls.

== Finishing: send

cpp:request_builder::send[send()] sends the request and reads the status line
cpp:request_builder::send[send] sends the request and reads the status line
and headers, leaving the body unread. It yields `(error_code, response)`:

[source,cpp]
Expand Down Expand Up @@ -135,7 +135,7 @@ xref:2.guide/2d.error-handling.adoc[error handling] section.

== Finishing: build and execute

cpp:request_builder::build[build()] does not send anything. It returns a
cpp:request_builder::build[build] does not send anything. It returns a
cpp:request[] — a plain value holding the method, URL, headers, body, and
per-request options — that can be stored, passed around, and executed later
through cpp:client::execute[]:
Expand All @@ -159,9 +159,7 @@ client; it can be executed by whichever client is convenient.

== Next Steps

* xref:2.guide/2b.request-bodies.adoc[Request Bodies] — Attaching a payload
* xref:2.guide/2c.responses.adoc[Responses] — Reading the result
* xref:2.guide/2d.error-handling.adoc[Error Handling] — What the error code
means
* xref:2.guide/2e.headers.adoc[Headers and Query Parameters] — Shaping the
request
* xref:2.guide/2b.request-bodies.adoc[] — Attaching a payload
* xref:2.guide/2c.responses.adoc[] — Reading the result
* xref:2.guide/2d.error-handling.adoc[] — What the error code means
* xref:2.guide/2e.headers.adoc[] — Shaping the request
149 changes: 83 additions & 66 deletions doc/modules/ROOT/pages/2.guide/2b.request-bodies.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,138 +9,155 @@

= Request Bodies

A request body is attached with the builder's cpp:request_builder::body[body]
function. The type of the value decides three things: the `Content-Type` header,
A request body is attached with the cpp: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. This section covers the
built-in body types, xref:2.guide/2n.extending.adoc[extending] shows how to add
your own type.
encoding, and how the bytes are produced on the wire.

== How the Body Is Framed

cpp:request_builder::body[body] accepts any value for which a conversion is
defined, by calling `tag_invoke` with a cpp:body_from_tag<T>[]. The result is an
cpp:any_request_body[].

Two rules hold for every body type:
defined, by calling `tag_invoke` with a cpp:body_from_tag<T>[]; the result is an
cpp:any_request_body[]. Two rules then hold for every body type:

* The body's `Content-Type` is sent *unless* the request already sets that
header explicitly. Setting `http::field::content_type` on the request always
header explicitly — setting `http::field::content_type` on the request always
wins.
* The framing — a `Content-Length` header, 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 a `Content-Length`; one whose size is not
is sent chunked.
* 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 a `Content-Length`; one whose size is not is sent chunked.

== Built-in Body Types

== Strings
=== Strings

A cpp:std::string[], a cpp:std::string_view[], or a string literal becomes a
body with `Content-Type: text/plain; charset=utf-8` and a `Content-Length`:
A cpp:std::string[], a cpp:std::string_view[], or a string literal is sent as
`text/plain; charset=utf-8` with a `Content-Length`:

[source,cpp]
----
auto r = co_await client.post("https://example.com/post")
auto [ec, r] = co_await client.post("https://example.com/post")
.body("plain text payload")
.send();
----

A cpp:std::string[] body owns its data. A cpp:std::string_view[] or
string-literal body refers to the characters in place without copying, so the
underlying buffer must outlive the request. To send text under a different media
type, set the header explicitly:
The default `Content-Type` can be overridden by setting the header explicitly:

[source,cpp]
----
auto r = co_await client.post("https://example.com/post")
auto [ec, r] = co_await client.post("https://example.com/post")
.body("<note>hi</note>")
.header(http::field::content_type, "application/xml")
.send();
----

== JSON
=== 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:

The Boost.JSON types — `json::value`, `json::object`, `json::array`, and
`json::string` — are serialized incrementally as the request is sent, under
`Content-Type: application/json`.
[source,cpp]
----
json::object obj({ { "user", "John" }, { "lang", "En" } });

auto r = co_await client.post("https://example.com/post")
auto [ec, r] = co_await client.post("https://example.com/post")
.body(obj)
.as<json::value>();
.send();
----

A literal value can be constructed inline by naming the type explicitly:
Naming the type builds the value inline, with no named variable:

[source,cpp]
----
auto r = co_await client.post("https://example.com/post")
auto [ec, r] = co_await client.post("https://example.com/post")
.body<json::array>({ 1, 2, 3 })
.as<json::value>();
.send();
----

== URL-Encoded Forms
=== URL-Encoded Forms

cpp:urlencoded_form[] builds an `application/x-www-form-urlencoded` body from
name and value pairs. Names and values are percent-encoded, with spaces written
as `+`. cpp:urlencoded_form::append[append] chains:
name/value pairs, sent with a `Content-Length`.
cpp:urlencoded_form::append[append] chains, and the same name may be added more
than once to submit a multi-valued field:

[source,cpp]
----
auto r = co_await client.post("https://example.com/post")
auto [ec, r] = co_await client.post("https://example.com/post")
.body(burl::urlencoded_form()
.append("user", "John")
.append("lang", "En"))
.as<json::value>();
.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:

[source,cpp]
----
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 Forms

cpp:multipart_form[] builds a `multipart/form-data` body, the format browsers
use for file uploads. It mixes text parts and file parts, separated by a
randomly generated boundary:
cpp:multipart_form[] builds a `multipart/form-data` body from a sequence of
parts separated by a generated boundary, as specified by
https://www.rfc-editor.org/rfc/rfc7578[RFC 7578]:

[source,cpp]
----
auto r = co_await client.post("https://example.com/upload")
auto [ec, r] = co_await client.post("https://example.com/upload")
.body(burl::multipart_form()
.text("priority", "high")
.file("attachment", "./report.log"))
.as<json::value>();
.send();
----

A file part is streamed from disk while the request is sent; only the path is
held in the form. The filename and `Content-Type` reported in the part header
are deduced from the path — falling back to `application/octet-stream` — and
either can be given explicitly. When the contents are already in memory but
should still be presented to the server as a file,
cpp:multipart_form::bytes[bytes] writes a part with a filename from an in-memory
buffer.
cpp:multipart_form::text[text] adds a plain field, while
cpp:multipart_form::file[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, cpp:multipart_form::bytes[bytes] writes a part
with a filename from an in-memory buffer.

== Files
=== Files

Naming cpp:std::filesystem::path[] as the body type uploads a file. Its contents
are streamed while the request is sent, so the whole file is never held in
memory:
Naming cpp: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:

[source,cpp]
----
auto r = co_await client.put("https://example.com/put")
auto [ec, r] = co_await client.put("https://example.com/put")
.body<std::filesystem::path>("./report.log")
.send();
----

The `Content-Type` is deduced from the filename extension, falling back to
`application/octet-stream`, and the `Content-Length` is the file's size at the
time of the call. Because the length is fixed up front, a file that *shrinks*
before the transfer completes fails the request with cpp:error::file_changed[],
rather than sending a truncated or mis-framed body.
The size is read when cpp:request_builder::body[] is called, so a path whose
size cannot be determined throws cpp: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 cpp:error::file_changed[].

== Your Own Types

cpp:request_builder::body[] accepts any type for which a `tag_invoke` conversion
is defined. xref:2.guide/2n.extending.adoc[] shows how to write one for a type
of your own.

== Next Steps

* xref:2.guide/2c.responses.adoc[Responses] — Reading the body that comes back
* xref:2.guide/2n.extending.adoc[Extending] — Teaching `body` about your own
types
* xref:2.guide/2e.headers.adoc[Headers and Query Parameters] — Overriding the
`Content-Type`
* xref:2.guide/2c.responses.adoc[] — Reading the body that comes back
* xref:2.guide/2d.error-handling.adoc[] — Handling failures
* xref:2.guide/2e.headers.adoc[] — Overriding the `Content-Type`
14 changes: 5 additions & 9 deletions doc/modules/ROOT/pages/2.guide/2c.responses.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ r.raise_for_status(); // throws on 4xx and 5xx
r.version(); // http::version
r.headers(); // const http::fields_base&
r.url(); // urls::url_view, the final URL after redirects
r.content_length(); // optional<uint64_t>, empty for a chunked body
r.content_length(); // std::optional<std::uint64_t>, empty for a chunked body
----

== Reading the Body
Expand Down Expand Up @@ -158,11 +158,7 @@ case the connection is simply closed on destruction rather than pooled.

== Next Steps

* xref:2.guide/2d.error-handling.adoc[Error Handling] — What the read can fail
with
* xref:2.guide/2m.compression.adoc[Compression] — Transparent decompression
of the body
* xref:2.guide/2k.connection-pool.adoc[Connection Pool] — Where the connection
goes next
* xref:2.guide/2n.extending.adoc[Extending] — Converting the body to your own
types
* xref:2.guide/2d.error-handling.adoc[] — What the read can fail with
* xref:2.guide/2m.compression.adoc[] — Transparent decompression of the body
* xref:2.guide/2k.connection-pool.adoc[] — Where the connection goes next
* xref:2.guide/2n.extending.adoc[] — Converting the body to your own types
8 changes: 3 additions & 5 deletions doc/modules/ROOT/pages/2.guide/2d.error-handling.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,7 @@ the *earliest* failure in this order:

== Next Steps

* xref:2.guide/2c.responses.adoc[Responses] — Reading a body once the status is
known
* xref:2.guide/2j.timeouts.adoc[Timeouts] — A common source of transport
failures
* xref:2.guide/2h.redirects.adoc[Redirects] — Where
* xref:2.guide/2c.responses.adoc[] — Reading a body once the status is known
* xref:2.guide/2j.timeouts.adoc[] — A common source of transport failures
* xref:2.guide/2h.redirects.adoc[] — Where
cpp:error::too_many_redirects[too_many_redirects] comes from
17 changes: 7 additions & 10 deletions doc/modules/ROOT/pages/2.guide/2e.headers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ request, or on an individual request.

== Default Headers

cpp:client::headers[client::headers()] is the set of headers sent with every
cpp:client::headers[] is the set of headers sent with every
request. A natural place for things that do not change between requests, such as
a `User-Agent` or an `Accept`:

Expand Down Expand Up @@ -77,10 +77,10 @@ known
| The request URL

| `Authorization`
| xref:2.guide/2g.authentication.adoc[Authentication]
| xref:2.guide/2g.authentication.adoc[]

| `Cookie`
| The xref:2.guide/2i.cookies.adoc[cookie jar]
| The cpp:client::cookie_jar[cookie jar]

| `Accept-Encoding`
| The xref:2.guide/2m.compression.adoc[compression] settings, unless you set it
Expand All @@ -89,10 +89,7 @@ known

== Next Steps

* xref:2.guide/2f.query-parameters.adoc[Query Parameters] — Adding parameters to
the URL
* xref:2.guide/2g.authentication.adoc[Authentication] — The managed
`Authorization` header
* xref:2.guide/2b.request-bodies.adoc[Request Bodies] — Where `Content-Type`
comes from
* xref:2.guide/2i.cookies.adoc[Cookies] — The managed `Cookie` header
* xref:2.guide/2f.query-parameters.adoc[] — Adding parameters to the URL
* xref:2.guide/2g.authentication.adoc[] — The managed `Authorization` header
* xref:2.guide/2b.request-bodies.adoc[] — Where `Content-Type` comes from
* xref:2.guide/2i.cookies.adoc[] — The managed `Cookie` header
Loading
Loading