Usage

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.

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';

See Responses.

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 name/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.

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';

See Headers.

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.

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';

See Responses.

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.

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';

See Responses.

Set Timeouts

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

// 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';

See Timeouts.

Follow Redirects

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

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

See Redirects.

Enable Cookies

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

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();

See Cookies.

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.

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;

See Proxies.

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