|
9 | 9 |
|
10 | 10 | = Request Bodies |
11 | 11 |
|
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, |
14 | 14 | 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. |
18 | 16 |
|
19 | 17 | == How the Body Is Framed |
20 | 18 |
|
21 | 19 | 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: |
26 | 22 |
|
27 | 23 | * 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 |
29 | 25 | 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 |
34 | 31 |
|
35 | | -== Strings |
| 32 | +=== Strings |
36 | 33 |
|
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`: |
39 | 36 |
|
40 | 37 | [source,cpp] |
41 | 38 | ---- |
42 | | -auto r = co_await client.post("https://example.com/post") |
| 39 | +auto [ec, r] = co_await client.post("https://example.com/post") |
43 | 40 | .body("plain text payload") |
44 | 41 | .send(); |
45 | 42 | ---- |
46 | 43 |
|
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: |
51 | 45 |
|
52 | 46 | [source,cpp] |
53 | 47 | ---- |
54 | | -auto r = co_await client.post("https://example.com/post") |
| 48 | +auto [ec, r] = co_await client.post("https://example.com/post") |
55 | 49 | .body("<note>hi</note>") |
56 | 50 | .header(http::field::content_type, "application/xml") |
57 | 51 | .send(); |
58 | 52 | ---- |
59 | 53 |
|
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: |
61 | 59 |
|
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`. |
65 | 60 | [source,cpp] |
66 | 61 | ---- |
67 | 62 | json::object obj({ { "user", "John" }, { "lang", "En" } }); |
68 | 63 |
|
69 | | -auto r = co_await client.post("https://example.com/post") |
| 64 | +auto [ec, r] = co_await client.post("https://example.com/post") |
70 | 65 | .body(obj) |
71 | | - .as<json::value>(); |
| 66 | + .send(); |
72 | 67 | ---- |
73 | 68 |
|
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: |
75 | 70 |
|
76 | 71 | [source,cpp] |
77 | 72 | ---- |
78 | | -auto r = co_await client.post("https://example.com/post") |
| 73 | +auto [ec, r] = co_await client.post("https://example.com/post") |
79 | 74 | .body<json::array>({ 1, 2, 3 }) |
80 | | - .as<json::value>(); |
| 75 | + .send(); |
81 | 76 | ---- |
82 | 77 |
|
83 | | -== URL-Encoded Forms |
| 78 | +=== URL-Encoded Forms |
84 | 79 |
|
85 | 80 | 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: |
88 | 84 |
|
89 | 85 | [source,cpp] |
90 | 86 | ---- |
91 | | -auto r = co_await client.post("https://example.com/post") |
| 87 | +auto [ec, r] = co_await client.post("https://example.com/post") |
92 | 88 | .body(burl::urlencoded_form() |
93 | 89 | .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(); |
96 | 110 | ---- |
97 | 111 |
|
98 | | -== Multipart Forms |
| 112 | +=== Multipart Forms |
99 | 113 |
|
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]: |
103 | 117 |
|
104 | 118 | [source,cpp] |
105 | 119 | ---- |
106 | | -auto r = co_await client.post("https://example.com/upload") |
| 120 | +auto [ec, r] = co_await client.post("https://example.com/upload") |
107 | 121 | .body(burl::multipart_form() |
108 | 122 | .text("priority", "high") |
109 | 123 | .file("attachment", "./report.log")) |
110 | | - .as<json::value>(); |
| 124 | + .send(); |
111 | 125 | ---- |
112 | 126 |
|
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. |
120 | 133 |
|
121 | | -== Files |
| 134 | +=== Files |
122 | 135 |
|
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: |
126 | 140 |
|
127 | 141 | [source,cpp] |
128 | 142 | ---- |
129 | | -auto r = co_await client.put("https://example.com/put") |
| 143 | +auto [ec, r] = co_await client.put("https://example.com/put") |
130 | 144 | .body<std::filesystem::path>("./report.log") |
131 | 145 | .send(); |
132 | 146 | ---- |
133 | 147 |
|
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. |
139 | 158 |
|
140 | 159 | == Next Steps |
141 | 160 |
|
142 | 161 | * 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 |
144 | 163 | * xref:2.guide/2e.headers.adoc[] — Overriding the `Content-Type` |
0 commit comments