Examples

A runnable catalog of every feature, extracted from example/usage.cpp. Each snippet is the body of a capy::task<> coroutine; the surrounding main and codec setup is shown once in Quick Start. Each entry links to the guide chapter that covers it in depth.

Basic GET Request

The type passed to as<T> selects how the body is parsed.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

// Response body as a string
auto r1 = co_await client.get("https://example.com")
    .as<std::string>();

std::cout << r1 << '\n';

// Response body as JSON
auto r2 = co_await client.get("https://postman-echo.com/get")
    .as<json::value>();

std::cout << r2 << '\n';

Inspect Response Status and Headers

send returns the response with headers ready and the body still unread.

See Responses.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

// send() yields the response without reading the body, so the status
// and headers can be inspected before the body is consumed.
auto [ec, r] = co_await client.get("https://example.com").send();

if(ec)
    throw std::system_error(ec);

std::cout << "status:  " << r.status_int() << '\n';
std::cout << "reason:  " << r.reason() << '\n';
std::cout << "headers: " << r.headers() << '\n';
std::cout << "body:    " << co_await r.as<std::string>() << '\n';

Handle Error Status Codes

try_as<T> returns an error_code instead of throwing on 4xx/5xx.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

try
{
    auto r1 = co_await client.get("https://example.com/not-found")
        .as<std::string>();
}
catch(std::system_error const&e)
{
    // HTTP 404 Not Found
    std::cerr << e.what() << '\n';
}

// Or inspect the error code instead of throwing
auto [ec, r2] = co_await client.get("https://example.com/not-found")
    .try_as<std::string>();

if(ec == burl::condition::client_error)
{
    // HTTP 404 Not Found
    std::cerr << ec.message() << '\n';
}

Add Query Parameters

query appends percent-encoded key/value pairs to the URL.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

auto r = co_await client.get("https://postman-echo.com/get")
    .query("category", "shoes")
    .query("color", "blue")
    .as<json::object>();

std::cout << r << '\n';

Set Request Headers

Client headers apply to every request; request headers apply to one.

See Headers.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

// Default headers on the client, sent with every request
client.headers().set(http::field::user_agent, "BoostBurl/1.0");

// Per-request headers
auto r = co_await client.get("https://postman-echo.com/get")
    .header(http::field::accept_language, "da, en-gb;q=0.8, en;q=0.7")
    .header("X-Trace-Id", "abc123")
    .as<json::object>();

std::cout << r << '\n';

Authentication

basic_auth/bearer_auth set Authorization, on the client or per request.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

// Default auth, sent with every request
client.basic_auth("user", "pass");
// or client.bearer_auth("TOKEN");

auto r = co_await client.get("https://postman-echo.com/basic-auth")
    .basic_auth("postman", "password") // per-request override
    // or .bearer_auth("TOKEN")
    .as<json::object>();

std::cout << r << '\n';

POST a String Body

A string body defaults to text/plain; override with a content_type header.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

auto r = co_await client.post("https://postman-echo.com/post")
    // A string body defaults to Content-Type: text/plain; charset=utf-8
    .body("<note>hi</note>")
    // Override the Content-Type:
    .header(http::field::content_type, "application/xml")
    .as<json::object>();

std::cout << r << '\n';

POST a JSON Body

A json::value is serialized and sent as application/json.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

json::object body({ { "user", "John" }, { "lang", "En" } });
auto r1 = co_await client.post("https://postman-echo.com/post")
    .body(body)
    .as<json::object>();

std::cout << r1 << '\n';

// Or inline
auto r2 = co_await client.post("https://postman-echo.com/post")
    .body<json::array>({ 1, 2, 3 })
    .as<json::object>();

std::cout << r2 << '\n';

POST a URL-Encoded Form

urlencoded_form builds an application/x-www-form-urlencoded body.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

burl::urlencoded_form form;
form.append("user", "John");
form.append("lang", "En");

auto r1 = co_await client.post("https://postman-echo.com/post")
    .body(form)
    .as<json::object>();

std::cout << r1 << '\n';

// Or inline
auto r2 = co_await client.post("https://postman-echo.com/post")
    .body(burl::urlencoded_form()
        .append("user", "John")
        .append("lang", "En"))
    .as<json::object>();

std::cout << r2 << '\n';

POST a Multipart Form

multipart_form mixes file uploads and text fields as multipart/form-data.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

burl::multipart_form form;
// filename and MIME type are deduced from the path (or can be passed in)
form.file("attachment", "./crash_report.log");
form.text("priority", "high");

auto r1 = co_await client.post("https://postman-echo.com/post")
    .body(form)
    .as<json::object>();

std::cout << r1 << '\n';

// Or inline
auto r2 = co_await client.post("https://postman-echo.com/post")
    .body(burl::multipart_form()
        .file("attachment", "./crash_report.log")
        .text("priority", "high"))
    .as<json::object>();

std::cout << r2 << '\n';

Upload and Download a File

body<fs::path> streams from a file; as<fs::path> streams to one.

namespace fs = std::filesystem;

burl::client client(co_await capy::this_coro::executor, tls_ctx);

fs::path r = co_await client.put("https://postman-echo.com/put")
    .body<fs::path>("./crash_report.log") // Load the request body from a file
    // Override the auto-deduced Content-Type:
    // .header(http::field::content_type, "application/octet-stream")
    .as<fs::path>("./resp.txt"); // Save the response body to a file

std::cout << "Response body saved to:" << r << '\n';

Stream a Response Body

pull the body in chunks instead of buffering it all in memory.

See Responses.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

auto [ec, r] = co_await client.get("https://archives.boost.io/"
    "release/1.91.0/source/boost_1_91_0.tar.bz2")
    .send();

if(ec)
    throw std::system_error(ec);

// Read the body incrementally instead of buffering it all in memory
auto source = r.as_buffer_source();
hash2::sha2_256 hash;
for(;;)
{
    capy::const_buffer arr[8];
    auto [ec, bufs] = co_await source.pull(arr);
    if(ec == capy::cond::eof)
        break;
    if(ec)
        throw std::system_error(ec);
    for(auto const& buf : bufs)
    {
        hash.update(buf.data(), buf.size());
        source.consume(buf.size());
    }
}
std::cout << "sha256: " << hash.result() << '\n';

Read a Response Body In Place

When the body fits in response_inplace_buffer, as_view reads it straight from the connection’s internal buffer with no extra allocation.

See Responses.

burl::client::config cfg;
cfg.response_inplace_buffer = 1024 * 1024;

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

auto [ec, r] = co_await client.get("https://www.boost.org").send();

if(ec)
    throw std::system_error(ec);

// Use the internal inplace buffer for reading the whole body
// the most efficient method if we know the body always fits.
std::cout << co_await r.as_view() << '\n';

Set Timeouts

Separate limits for connect, each I/O step, and the whole operation.

See Timeouts.

// Client timeouts, applied to every request
burl::client::config cfg;

// Connect timeout, including TLS handshake and proxy connect
cfg.connect_timeout = std::chrono::seconds(30);

// Per read/write timeout, for detecting unresponsive servers regardless
// of the request/response size
cfg.io_timeout = std::chrono::seconds(5);

// Timeout for the whole operation, including retrieving the response
cfg.timeout = std::chrono::seconds(60);

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

auto r = co_await client.get("https://example.com")
    .timeout(std::chrono::seconds(3)) // per-request override
    .as<std::string>();

std::cout << r << '\n';

Follow Redirects

Chase Location up to maxredirs hops; the response reports the final URL.

See Redirects.

burl::client::config cfg;

// Follow redirects (enable by default)
cfg.followlocation = true;
cfg.maxredirs = 10;

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

auto [ec1, r1] = co_await client.get("http://boost.org").send();

if(ec1)
    throw std::system_error(ec1);

// The final URL after following redirects
std::cout << r1.url() << '\n';

auto [ec2, r2] = co_await client.get("http://boost.org")
    .followlocation(false) // per-request override
    .send();

if(ec2)
    throw std::system_error(ec2);

std::cout << r2.status_int() << '\n'; // e.g. 301

Enable Cookies

Set cookies to keep a jar that stores and replays cookies per host.

See Cookies.

burl::client::config cfg;

// Cookies (disabled by default)
cfg.cookies = true;

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

auto r = co_await client.get("https://postman-echo.com/cookies/set?foo=bar")
    .as<std::string>();

// Print the stored cookies in Netscape format
std::cout << client.cookie_jar().to_netscape();

Reuse Connections With the Pool

Idle connections are kept alive so later requests skip the handshakes.

burl::client::config cfg;
cfg.pool_idle_timeout = std::chrono::seconds(60);
cfg.pool_max_idle_per_host = 10;

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

auto r1 = co_await client.get("https://www.boost.org")
    .as<std::string>();

// Reuses the connection established by the first request
auto r2 = co_await client.get("https://www.boost.org")
    .as<std::string>();

Use a Proxy

Set proxy to route requests through an HTTP or SOCKS5 proxy.

See Proxies.

burl::client::config cfg;
// SOCKS5 and HTTP proxies are supported
cfg.proxy = urls::url("socks5h://user:pass@localhost:8080");

burl::client client(co_await capy::this_coro::executor, tls_ctx, cfg);

// Sent through the proxy
auto r = co_await client.get("https://example.com")
    .as<std::string>();

std::cout << r;

Build a Request and Execute It Later

build captures a request value to store and run later with execute.

burl::client client(co_await capy::this_coro::executor, tls_ctx);

// build() produces a request that can be stored and executed later
burl::request req = client.post("https://postman-echo.com/post")
    .header("X-Debug", "1")
    .body("payload")
    .build();

auto [ec, r] = co_await client.execute(std::move(req));
if(ec)
    throw std::system_error(ec);

std::cout << co_await r.as<json::value>() << '\n';

Next Steps