Making Requests

Every request begins at a client and is assembled by a request_builder. This section covers both, and the three ways a builder is finished.

The Client

A client is the entry point for performing requests. It owns the configuration, a connection pool, a set of default headers, and a cookie jar, all shared by every request made through it. Construct one client and reuse it: that is what lets connections to the same origin be kept alive and reused across requests.

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

The constructor takes the executor that asynchronous operations run on and a corosio::tls_context for https connections. A third argument, a client::config, customizes behavior.

A client is movable but not copyable. It is not safe to use a single client concurrently from operations running on different threads.

Verbs

Six member functions create a builder for the common methods:

Function Method

client::get

GET

client::head

HEAD

client::post

POST

client::put

PUT

client::patch

PATCH

client::delete_

DELETE

For any other method, client::request takes an http::method and the URL:

auto rb = client.request(http::method::options, "https://example.com");

Only the http and https schemes are supported; any other scheme fails the request with error::unsupported_url_scheme.

The Request Builder

The verb functions return a request_builder. Its member functions configure the request and return the builder again, so calls chain:

auto r = co_await client.get("https://example.com/search")
    .query("q", "boost")
    .header(http::field::accept, "application/json")
    .timeout(std::chrono::seconds(10))
    .send();

The builder holds a reference to the client, which must stay alive until the request has been sent. The configuration calls — query, header, basic_auth, bearer_auth, timeout, followlocation, and body — are each documented in the section for their feature. A chain is finished by one of three terminal calls.

Finishing: send

send() sends the request and reads the status line and headers, leaving the body unread. It yields (error_code, response):

auto [ec, r] = co_await client.get("https://example.com").send();
if(ec)
    throw std::system_error(ec);

// inspect r.status_int(), r.headers(), then read the body

Use send when a decision depends on the status or headers before the body is consumed — choosing how to parse it, or short-circuiting on an error. A 4xx or 5xx status sets ec but still returns the response, so the error body remains readable. The responses section covers what the returned response offers.

Finishing: as and try_as

When the goal is simply the body as a particular type, as<T> and try_as<T> combine sending and reading into one step:

// throws on failure
auto body = co_await client.get("https://example.com").as<std::string>();

// yields (error_code, T)
auto [ec, v] = co_await client.get("https://example.com").try_as<json::value>();

as<T> throws a std::system_error on any failure; try_as<T> reports it as an error code. The type T selects the conversion through tag_invoke. These are the same conversions offered by response, so

co_await client.get(url).as<T>();

is shorthand for send-ing the request and calling response::as<T> on the result. The exact failure semantics — in particular what happens on a 4xx or 5xx status — are the subject of the error handling section.

Finishing: build and execute

build() does not send anything. It returns a request — a plain value holding the method, URL, headers, body, and per-request options — that can be stored, passed around, and executed later through client::execute:

burl::request req = client.post("https://example.com/post")
    .header("X-Debug", "1")
    .body("payload")
    .build();

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

execute behaves exactly like send: it yields (error_code, response) with the body unread. Separating construction from execution is useful for building a request in one place and running it in another, for queuing requests, or for retrying the same request after a failure. A built request is independent of the builder and of any particular client; it can be executed by whichever client is convenient.

Next Steps