Skip to content

Commit be325cb

Browse files
committed
doc: restructure request-bodies guide
1 parent 06194a2 commit be325cb

3 files changed

Lines changed: 89 additions & 69 deletions

File tree

doc/modules/ROOT/pages/2.guide/2b.request-bodies.adoc

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,136 +9,155 @@
99

1010
= Request Bodies
1111

12-
A request body is attached with the builder's cpp:request_builder::body[body]
13-
function. The type of the value decides three things: the `Content-Type` header,
12+
A request body is attached with the cpp:request_builder::body[] function.
13+
The type of the value decides three things: the `Content-Type` header,
1414
whether the body is sent with a known `Content-Length` or with chunked transfer
15-
encoding, and how the bytes are produced on the wire. This section covers the
16-
built-in body types, xref:2.guide/2n.extending.adoc[extending] shows how to add
17-
your own type.
15+
encoding, and how the bytes are produced on the wire.
1816

1917
== How the Body Is Framed
2018

2119
cpp:request_builder::body[body] accepts any value for which a conversion is
22-
defined, by calling `tag_invoke` with a cpp:body_from_tag<T>[]. The result is an
23-
cpp:any_request_body[].
24-
25-
Two rules hold for every body type:
20+
defined, by calling `tag_invoke` with a cpp:body_from_tag<T>[]; the result is an
21+
cpp:any_request_body[]. Two rules then hold for every body type:
2622

2723
* The body's `Content-Type` is sent *unless* the request already sets that
28-
header explicitly. Setting `http::field::content_type` on the request always
24+
header explicitly — setting `http::field::content_type` on the request always
2925
wins.
30-
* The framing — a `Content-Length` header, or chunked transfer encoding — is
31-
*always* taken from the body and cannot be overridden. A body whose size is
32-
known in advance is sent with a `Content-Length`; one whose size is not
33-
is sent chunked.
26+
* The framing — a `Content-Length`, or chunked transfer encoding — is *always*
27+
taken from the body and cannot be overridden. A body whose size is known in
28+
advance is sent with a `Content-Length`; one whose size is not is sent chunked.
29+
30+
== Built-in Body Types
3431

35-
== Strings
32+
=== Strings
3633

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

4037
[source,cpp]
4138
----
42-
auto r = co_await client.post("https://example.com/post")
39+
auto [ec, r] = co_await client.post("https://example.com/post")
4340
.body("plain text payload")
4441
.send();
4542
----
4643

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

5246
[source,cpp]
5347
----
54-
auto r = co_await client.post("https://example.com/post")
48+
auto [ec, r] = co_await client.post("https://example.com/post")
5549
.body("<note>hi</note>")
5650
.header(http::field::content_type, "application/xml")
5751
.send();
5852
----
5953

60-
== JSON
54+
=== JSON
55+
56+
The Boost.JSON types are sent as `application/json`, serialized incrementally as
57+
the request goes out. Their serialized size is not known beforehand, so this is
58+
the one built-in body sent with chunked transfer encoding:
6159

62-
The Boost.JSON types — `json::value`, `json::object`, `json::array`, and
63-
`json::string` — are serialized incrementally as the request is sent, under
64-
`Content-Type: application/json`.
6560
[source,cpp]
6661
----
6762
json::object obj({ { "user", "John" }, { "lang", "En" } });
6863
69-
auto r = co_await client.post("https://example.com/post")
64+
auto [ec, r] = co_await client.post("https://example.com/post")
7065
.body(obj)
71-
.as<json::value>();
66+
.send();
7267
----
7368

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

7671
[source,cpp]
7772
----
78-
auto r = co_await client.post("https://example.com/post")
73+
auto [ec, r] = co_await client.post("https://example.com/post")
7974
.body<json::array>({ 1, 2, 3 })
80-
.as<json::value>();
75+
.send();
8176
----
8277

83-
== URL-Encoded Forms
78+
=== URL-Encoded Forms
8479

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

8985
[source,cpp]
9086
----
91-
auto r = co_await client.post("https://example.com/post")
87+
auto [ec, r] = co_await client.post("https://example.com/post")
9288
.body(burl::urlencoded_form()
9389
.append("user", "John")
94-
.append("lang", "En"))
95-
.as<json::value>();
90+
.append("color", "blue")
91+
.append("color", "green"))
92+
.send();
93+
// user=John&color=blue&color=green
94+
----
95+
96+
Names and values are percent-encoded: only the RFC 3986 unreserved characters
97+
pass through, spaces become `+`, and the `&` and `=` separators are escaped, so a
98+
value can never break out of the field structure. A form can also be built from
99+
an initializer list or any range of pairs:
100+
101+
[source,cpp]
102+
----
103+
std::map<std::string, std::string> fields = {
104+
{ "lang", "En" },
105+
{ "user", "John" } };
106+
107+
auto [ec, r] = co_await client.post("https://example.com/post")
108+
.body<burl::urlencoded_form>(fields)
109+
.send();
96110
----
97111

98-
== Multipart Forms
112+
=== Multipart Forms
99113

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

104118
[source,cpp]
105119
----
106-
auto r = co_await client.post("https://example.com/upload")
120+
auto [ec, r] = co_await client.post("https://example.com/upload")
107121
.body(burl::multipart_form()
108122
.text("priority", "high")
109123
.file("attachment", "./report.log"))
110-
.as<json::value>();
124+
.send();
111125
----
112126

113-
A file part is streamed from disk while the request is sent; only the path is
114-
held in the form. The filename and `Content-Type` reported in the part header
115-
are deduced from the path — falling back to `application/octet-stream` — and
116-
either can be given explicitly. When the contents are already in memory but
117-
should still be presented to the server as a file,
118-
cpp:multipart_form::bytes[bytes] writes a part with a filename from an in-memory
119-
buffer.
127+
cpp:multipart_form::text[text] adds a plain field, while
128+
cpp:multipart_form::file[file] adds a file that is streamed from disk as the
129+
request is sent; only the path is held, with its filename and `Content-Type`
130+
deduced from it unless set explicitly. When the data is already in memory but
131+
should still arrive as a file, cpp:multipart_form::bytes[bytes] writes a part
132+
with a filename from an in-memory buffer.
120133

121-
== Files
134+
=== Files
122135

123-
Naming cpp:std::filesystem::path[] as the body type uploads a file. Its contents
124-
are streamed while the request is sent, so the whole file is never held in
125-
memory:
136+
Naming cpp:std::filesystem::path[] uploads a file, streamed from disk so the
137+
whole file is never held in memory. The `Content-Type` is deduced from the
138+
filename extension, defaulting to `application/octet-stream`, and the
139+
`Content-Length` is the file's size:
126140

127141
[source,cpp]
128142
----
129-
auto r = co_await client.put("https://example.com/put")
143+
auto [ec, r] = co_await client.put("https://example.com/put")
130144
.body<std::filesystem::path>("./report.log")
131145
.send();
132146
----
133147

134-
The `Content-Type` is deduced from the filename extension, falling back to
135-
`application/octet-stream`, and the `Content-Length` is the file's size at the
136-
time of the call. Because the length is fixed up front, a file that *shrinks*
137-
before the transfer completes fails the request with cpp:error::file_changed[],
138-
rather than sending a truncated or mis-framed body.
148+
The size is read when cpp:request_builder::body[] is called, so a path whose
149+
size cannot be determined throws cpp:std::filesystem::filesystem_error[] from
150+
that call. Because the length is then fixed, a file that shrinks before the
151+
transfer completes fails the request with cpp:error::file_changed[].
152+
153+
== Your Own Types
154+
155+
cpp:request_builder::body[] accepts any type for which a `tag_invoke` conversion
156+
is defined. xref:2.guide/2n.extending.adoc[] shows how to write one for a type
157+
of your own.
139158

140159
== Next Steps
141160

142161
* xref:2.guide/2c.responses.adoc[] — Reading the body that comes back
143-
* xref:2.guide/2n.extending.adoc[] — Teaching `body` about your own types
162+
* xref:2.guide/2d.error-handling.adoc[] — Handling failures
144163
* xref:2.guide/2e.headers.adoc[] — Overriding the `Content-Type`

doc/modules/ROOT/pages/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,5 +133,5 @@ The xref:quick-start.adoc[Quick Start] shows the surrounding program in full.
133133
builder
134134
* xref:2.guide/2c.responses.adoc[] — Status, headers, and reading the body
135135
* xref:2.guide/2d.error-handling.adoc[] — The value-and-exception error model
136-
* xref:examples/usage.adoc[Examples] — A runnable tour of the API
136+
* xref:examples/usage.adoc[] — A runnable tour of the API
137137
* xref:reference:boost/burl.adoc[Reference] — The generated API reference

doc/modules/ROOT/pages/quick-start.adoc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ The examples that follow assume this scaffolding and show only the body of
5353

5454
== Parse the Body as JSON
5555

56-
Asking for a different type changes how the body comes back. With
57-
Boost.JSON included, request a `json::value` and the body is parsed as it
58-
arrives:
56+
The program above asked for a `std::string` and got the raw body bytes. The
57+
type argument selects the conversion: ask cpp:request_builder::as[as<T>] for a
58+
`json::value` and the same call parses the response as JSON, incrementally as
59+
it arrives, and hands back a value you can navigate directly:
5960

6061
[source,cpp]
6162
----
@@ -183,8 +184,8 @@ std::cout << "body: " << co_await r.as<std::string>() << '\n';
183184
== Handle Errors Without Throwing
184185

185186
cpp:request_builder::as[as<T>] throws on any failure, including a 4xx or 5xx
186-
status. To inspect an error instead, use the `try_` forms, which yield
187-
`(error_code, T)`:
187+
status. To inspect an error instead, use
188+
cpp:request_builder::try_as[try_as<T>], which yields `(error_code, T)`:
188189

189190
[source,cpp]
190191
----
@@ -204,4 +205,4 @@ full model, including the order in which different failures take precedence.
204205
execution
205206
* xref:2.guide/2b.request-bodies.adoc[] — Strings, JSON, forms, and files
206207
* xref:2.guide/2d.error-handling.adoc[] — Errors as values
207-
* xref:examples/usage.adoc[Examples] — A runnable tour of every feature
208+
* xref:examples/usage.adoc[] — A runnable tour of every feature

0 commit comments

Comments
 (0)