Body
xpp::http::Body is the request/response body value — move-only, hyper-style. It satisfies the AsyncReader concept, so io::read_all / io::copy work on it directly.
Kinds
A Body is one of three kinds:
| Kind | Construction | Read behavior |
|---|---|---|
Empty | Body::empty() | immediate EOF (read() returns 0) |
Once | Body::from(bytes / Vec<uint8_t> / String / const char*) | one-shot bytes |
Channel | Body::from_channel(mpsc::Receiver<Bytes>) | streamed — read() suspends when the channel is empty, resumes on wake |
Body a = Body::empty();
Body b = Body::from(xpp::Bytes::copy("hi", 2));
Body c = Body::from(std::string("text"));
auto [tx, rx] = xpp::sync::mpsc::channel<xpp::Bytes>(64);
Body s = Body::from_channel(std::move(rx));
tx.send(xpp::Bytes::copy("chunk", 5)); // feeds the stream
tx.close(); // EOF for the reader
Reading
C++20 — coroutines:
// Whole body:
auto bytes = co_await body.bytes(); // Result<Bytes>
auto text = co_await body.text(); // Result<String> (UTF-8)
// Streaming (AsyncReader):
char buf[4096];
ssize_t n;
while ((n = co_await body.read(buf, sizeof(buf))) > 0) { /* process */ }
// n == 0 at EOF
C++11 — .await():
// Whole body:
auto bytes = body.bytes().await().unwrap(); // Promise<Result<Bytes>>
auto text = body.text().await().unwrap(); // Promise<Result<String>> (UTF-8)
// Streaming (AsyncReader):
char buf[4096];
ssize_t n;
while ((n = body.read(buf, sizeof(buf)).await()) > 0) { /* process */ }
// n == 0 at EOF
For a channel body, read() returns a Pending promise when the channel is empty; the reading coroutine/fiber suspends until a chunk arrives or the sender closes. This is waker-driven — nothing busy-polls.
Lifetime:
read()does not extend the Body's lifetime. Keep theBodyin a named variable while awaiting (unlikebytes()/text(), which move it into anArcinternally).
Observers
bool empty = body.is_empty(); // Empty kind or exhausted
bool channel = body.is_channel(); // backed by a stream
xpp::Bytes once = body.into_once_bytes(); // Once/Empty only (channel → empty)
Streaming Request Bodies
The server-side request body is a channel fed by libx's on_data callback — req.into_body() gives you a channel Body that streams with backpressure:
C++20 — coroutine:
.route("POST /echo", [](Request req) -> Promise<Result<Response>> {
auto body = co_await req.into_body().bytes(); // full body, streamed
return Response::ok(body);
})
C++11 — .then() chain:
.route("POST /echo", [](Request req) -> Promise<Result<Response>> {
return req.into_body().bytes().then([](Result<Bytes> b) {
return Response::ok(b.unwrap());
});
})
The server channel has fixed capacity; when full, libx pauses the connection and resumes once you drain it (natural backpressure).
Client-side request upload only supports Once bodies today — into_once_bytes() returns empty for a channel body (documented limitation).
Streaming Response Bodies
Return a channel Body from a handler and the server streams it:
C++20 — coroutine producer (pass the lambda directly to spawn — see the lifetime note below):
.route("GET /stream", [](Request req) -> Result<Response> {
auto [tx, rx] = xpp::sync::mpsc::channel<xpp::Bytes>(4);
xpp::spawn([tx]() mutable -> Promise<void> {
co_await tx.send(xpp::Bytes::copy("part1", 5));
co_await tx.send(xpp::Bytes::copy("-part2", 6));
tx.close();
co_return;
});
return Response::ok(Body::from_channel(std::move(rx)));
})
C++11 — recursive .then() producer:
// A struct whose operator() sends one chunk and chains itself for the
// next — the .then()-era equivalent of a coroutine loop.
struct StreamProducer {
xpp::sync::mpsc::Sender<xpp::Bytes> tx;
int i = 2;
xpp::Promise<void> operator()() {
if (i == 0) {
tx.close(); // EOF for the reader
return xpp::resolve();
}
return tx.send(chunk(i)).then([this]() {
--i;
return (*this)();
});
}
};
.route("GET /stream", [](Request req) -> Result<Response> {
auto [tx, rx] = xpp::sync::mpsc::channel<xpp::Bytes>(4);
xpp::spawn(StreamProducer{tx}); // defer node keeps a heap copy alive
// for the whole chain
return Response::ok(Body::from_channel(std::move(rx)));
})
Each chunk is written via xHttpCtxWrite; closing the channel ends the stream (xHttpCtxEndStream). HTTP/1 streams are close-delimited (Connection: close); H2 uses nghttp2 streams.