Fork me on GitHub
Logo

cpr - C++ Requests

cpr is a modern HTTP library for C++, built for people.

User Guide

This project is maintained by Fabian Sauter and Tim Stack

Version Macros

CPR exposes a couple of preprocessor macros with version information.

/**
 * CPR version as a string.
 **/
#define CPR_VERSION "1.7.0"

/**
 * CPR version split up into parts.
 **/
#define CPR_VERSION_MAJOR 1
#define CPR_VERSION_MINOR 7
#define CPR_VERSION_PATCH 0

/**
 * CPR version as a single hex digit.
 * it can be split up into three parts:
 * 0xAABBCC
 * AA: The current CPR major version number in a hex format.
 * BB: The current CPR minor version number in a hex format.
 * CC: The current CPR patch version number in a hex format.
 *
 * Examples:
 * '0x010702' -> 01.07.02 -> CPR_VERSION: 1.7.2
 * '0xA13722' -> A1.37.22 -> CPR_VERSION: 161.55.34
 **/
#define CPR_VERSION_NUM 0x010702

Response Objects

Response objects are bags of data. Their sole purpose is to give the client information at the end of a request – there’s nothing in the API that uses a Response after it gets back to you. This reasoning drove the decision to make the member fields of the response public and mutable.

A Response has these fields and methods:

long status_code;               // The HTTP status code for the request
std::string text;               // The body of the HTTP response
Header header;                  // A map-like collection of the header fields
Url url;                        // The effective URL of the ultimate request
double elapsed;                 // The total time of the request in seconds
Cookies cookies;                // A map-like collection of cookies returned in the request
Error error;                    // An error object containing the error code and a message
std::string raw_header;         // The raw header string
std::string status_line;        // The status line of the respone
std::string reason;             // The reason for the status code
cpr_off_t uploaded_bytes;       // How many bytes have been send to the server
cpr_off_t downloaded_bytes;     // How many bytes have been received from the server
long redirect_count;            // How many redirects occurred

std::vector<std::string> GetCertInfo(); // Returns a vector of certificate information strings (HTTPS only)

and they’re dead simple to access:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"});
if(r.status_code == 0)
    std::cerr << r.error.message << std::endl;
else if (r.status_code >= 400) {
    std::cerr << "Error [" << r.status_code << "] making request" << std::endl;
} else {
    std::cout << "Request took " << r.elapsed << std::endl;
    std::cout << "Body:" << std::endl << r.text;
}

The Header is essentially a map with an important modification. Its keys are case insensitive as required by RFC 7230:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"});
std::cout << r.header["content-type"] << std::endl;
std::cout << r.header["Content-Type"] << std::endl;
std::cout << r.header["CoNtEnT-tYpE"] << std::endl;

All of these should print the same value, "application/json". Cookies similarly are accessed through a map-like interface, but they’re not case insensitive:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies/set?cookies=yummy"});
std::cout << r.cookies["cookies"] << std::endl; // Prints yummy
std::cout << r.cookies["Cookies"] << std::endl; // Prints nothing

As you can see, the Response object is completely transparent. All of its data fields are accessible at all times, and since its only useful to you insofar as it has information to communicate, you can let it fall out of scope safely when you’re done with it.

Request Headers

Speaking of the Header, you can set custom headers in the request call. The object is exactly the same:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/headers"},
                  cpr::Header{{"accept", "application/json"}});
std::cout << r.text << std::endl;

/*
 * "headers": {
 *   "Accept": "application/json",
 *   "Host": "www.httpbin.org",
 *   "User-Agent": "curl/7.42.0-DEV"
 * }
 */

Furthermore, it is even possible to set several header parameters from different sources. For example, this is helpful when injecting header parameters into the request from different helpers:

template<class ...Ts>
cpr::Response myGet(Ts&& ...ts) {
    return cpr::Get(std::forward<Ts>(ts)...,
           cpr::Header{{"Authorization", "token"}});
}

...

cpr::Response r = cpr::myGet(cpr::Url{"http://www.httpbin.org/headers"},
                  cpr::Header{{"accept", "application/json"}});
std::cout << r.text << std::endl;

/*
 * "headers": {
 *   "Accept": "application/json",
 *   "Accept-Encoding": "deflate, gzip", 
 *   "Authorization": "token",
 *   "Host": "www.httpbin.org",
 *   "User-Agent": "curl/7.81.0"
 * }
 */

You’ve probably noticed a similarity between Header, Parameters, Payload, and Multipart. They all have constructors of the form:

cpr::Header header = cpr::Header{{"header-key", "header-value"}};
cpr::Parameters parameters = cpr::Parameters{{"parameter-key", "parameter-value"}};
cpr::Payload payload = cpr::Payload{{"payload-key", "payload-value"}};
cpr::Multipart multipart = cpr::Multipart{{"multipart-key", "multipart-value"}};

This isn’t an accident – all of these are map-like objects and their syntax is identical because their semantics depends entirely on the object type. Additionally, it’s practical to have Parameters, Payload, and Multipart be swappable because APIs sometimes don’t strictly differentiate between them.

Session Objects

Under the hood, all calls to the primary API modify an object called a Session before performing the request. This is the only truly stateful piece of the library, and for most applications it isn’t necessary to act on a Session directly, preferring to let the library handle it for you.

However, in cases where it is useful to hold on to state, you can use a Session:

cpr::Url url = cpr::Url{"http://www.httpbin.org/get"};
cpr::Parameters parameters = cpr::Parameters{{"hello", "world"}};
cpr::Session session;
session.SetUrl(url);
session.SetParameters(parameters);

cpr::Response r = session.Get();        // Equivalent to cpr::Get(url, parameters);
std::cout << r.url << std::endl;        // Prints http://www.httpbin.org/get?hello=world

cpr::Parameters new_parameters = cpr::Parameters{{"key", "value"}};
session.SetParameters(new_parameters);

cpr::Response new_r = session.Get();    // Equivalent to cpr::Get(url, new_parameters);
std::cout << new_r.url << std::endl;    // Prints http://www.httpbin.org/get?key=value

Session also allows you to get the full request URL before a request is actually made:

cpr::Url url = cpr::Url{"http://www.httpbin.org/get"};
cpr::Parameters parameters = cpr::Parameters{{"hello", "world"}};
cpr::Session session;
session.SetUrl(url);
session.SetParameters(parameters);

std::string fullRequestUrl = session.GetFullRequestUrl();
std::cout << fullRequestUrl << std::endl;   // Prints http://www.httpbin.org/get?hello=world

Session actually exposes two different interfaces for setting the same option. If you wanted you can do this instead of the above:

cpr::Url url = cpr::Url{"http://www.httpbin.org/get"};
cpr::Parameters parameters = cpr::Parameters{{"hello", "world"}};
cpr::Session session;
session.SetOption(url);
session.SetOption(parameters);
cpr::Response r = session.Get();

This is important so it bears emphasizing: for each configuration option (like Url, Parameters), there’s a corresponding method Set<ObjectName> and a SetOption(<Object>). The second interface is to facilitate the template metaprogramming magic that lets the API expose order-less methods.

The key to all of this is actually the way libcurl is designed. It uses a somewhat policy-based design that relies configuring a single library object (the curl handle). Each option configured into that object changes its behavior in mostly orthogonal ways.

Session leverages that and exposes a more modern interface that’s free of the macro-heavy hulkiness of libcurl. Understanding the policy-based design of libcurl is important for understanding the way the Session object behaves.

Session may also be used in scenarios when you want to benefit from cpr’s API for settings options and getting results back, but you need to perform the http request using curl’s advanced api such as curl_multi_socket_action(). In this case, you need to:

  1. prepare the request using one of PrepareGet, PreparePut, etc… instead of Get, Put, … respectively
  2. perform the request yourself using curl’s API, fetching the curl handle from the session using GetCurlHolder()
  3. once curlis done, give the resulting CURLcode to the session using Complete() and get the Response objet

Keep in mind that Session is stateful, which means that you can’t prepare multiple requests concurrently using the same Session: a request must be completed before you prepare the next one.

cpr::Url url = cpr::Url{"http://www.httpbin.org/get"};
cpr::Session session;
session.SetOption(url);
session.PrepareGet();

// Here, curl_easy_perform would typically be replaced
// by a more complex scheme using curl_multi API
CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle);

cpr::Response response = session.Complete(curl_result);

Large Responses

In case you expect a large string as a response, you should reserve space for it beforehand to prevent it from being moved and resized too often. For example, we expect the server to return roughly 4 million characters in this response with a chunk size of 256 characters. Without reserving space for 4 million characters beforehand, the string have to be moved roughly five times in case it grows exponentially. In reality, this usually happens less, since we usually receive larger chunks from the server.

So to get around this one could do the following:

cpr::Response r = cpr::Get(cpr::Url{"http://xxx/file"},
                  cpr::ReserveSize{1024 * 1024 * 4});   // Reserve space for at least 4 million characters

Asynchronous Requests

Making an asynchronous request uses a similar but separate interface:

AsyncResponse fr = cpr::GetAsync(cpr::Url{"http://www.httpbin.org/get"});
// Sometime later...
cpr::Response r = fr.get(); // This blocks until the request is complete
std::cout << r.text << std::endl;

The call is otherwise identical except instead of Get, it’s GetAsync. Similarly for POST requests, you would call PostAsync. The return value of an asynchronous call is actually a std::future<Response>:

AsyncResponse fr = cpr::GetAsync(cpr::Url{"http://www.httpbin.org/get"});
fr.wait() // This waits until the request is complete
cpr::Response r = fr.get(); // Since the request is complete, this returns immediately
std::cout << r.text << std::endl;

You can even put a bunch of requests into a std container and get them all later:

std::vector<std::future<cpr::Response>> container{};
cpr::Url url = cpr::Url{"http://www.httpbin.org/get"};
for (int i = 0; i < 10; ++i) {
    container.emplace_back(cpr::GetAsync(url, cpr::Parameters{{"i", std::to_string(i)}}));
}
// Sometime later
for (std::future<cpr::Response>& fr: container) {
    cpr::Response r = fr.get();
    std::cout << r.text << std::endl;
}

An important note to make here is that arguments passed to an asynchronous call are copied. Under the hood, an asychronous call through the library’s API is done with std::async. By default, for memory safety, all arguments are copied (or moved if temporary) because there’s no syntax level guarantee that the arguments will live beyond the scope of the request.

It’s possible to force std::async out of this default so that the arguments are passed by reference as opposed to value. Currently, however, cpr::<method>Async has no support for forced pass by reference, though this is planned for a future release.

Asynchronous Callbacks

C++ Requests also supports a callback interface for asynchronous requests. Using the callback interface, you pass in a functor (lambda, function pointer, etc.) as the first argument, and then pass in the rest of the options you normally would in a blocking request. The functor needs to have a single parameter, a Response object – this response is populated when the request completes and the function body executes.

Here’s a simple example:

auto future_text = cpr::GetCallback([](cpr::Response r) {
        return r.text;
    }, cpr::Url{"http://www.httpbin.org/get"});
// Sometime later
if (future_text.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
    std::cout << future_text.get() << std::endl;
}

There are a couple of key features to point out here:

  1. The return value is a std::string. This isn’t hardcoded – the callback is free to return any value it pleases! When it’s time to get that value, a check for if the request is complete is made, and a simple .get() call on the future grabs the correct value. This flexibility makes the callback interface delightfully simple, generic, and effective!
  2. The lambda capture is empty, but absolutely doesn’t need to be. Anything that can be captured inside a lambda normally can be captured in a lambda passed into the callback interface. This additional vector of flexibility makes it highly preferable to use lambdas, though any functor with a Response parameter will compile and work.

Additionally, you can enforce immutability of the Response simply with a const Response& parameter instead of Response.

Setting a Timeout

It’s possible to set a timeout for your request if you have strict timing requirements:

#include <assert.h>

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"},
                  cpr::Timeout{1000}); // Let's hope we aren't using Time Warner Cable
assert(r.elapsed <= 1); // Less than one second should have elapsed

Setting the Timeout option sets the maximum allowed time the transfer operation can take. Since C++ Requests is built on top of libcurl, it’s important to know what setting this Timeout does to the request. You can find more information about the specific libcurl option here.

Setting Callbacks

You can optionally set callbacks for a request. Currently there is support for read, header, write, progress, and debug callbacks.

ReadCallback

This callback function will be called every time libcurl is ready for data to be sent to the server, and provides for streaming uploads.

The callback signature looks like this.

  bool readCallback(char* buffer, size_t & length, intptr_t userdata);

Provide the callback with the ReadCallback options object. Only one read callback may be set. When called, length is the length of buffer. buffer should be filled with data, and length updated to how much was filled. Return true on success, or false to cancel the transfer.

HeaderCallback

This callback function gets called by libcurl once for each non-data line received from the server. This includes empty lines and the HTTP status line. \r\n endings are preserved.

The callback signature looks like this.

  bool headerCallback(std::string data, intptr_t userdata);

Provide the callback with the HeaderCallback options object. Only one header callback may be set. When a header callback is set, the Response object’s header member will not be filled. Return true on success, or false to cancel the transfer.

WriteCallback

This callback function gets called by libcurl as soon as there is data received that needs to be saved, and provides for streaming downloads. You could buffer data in your own way, or write every chunk immediately out to some other stream or file.

The callback signature looks like this.

  bool writeCallback(std::string data, intptr_t userdata);

Provide the callback with the WriteCallback options object. Only one write callback may be set. When a write callback is set, the Response object’s text member will not be filled. Return true on success, or false to cancel the transfer.

ProgressCallback

While data is being transferred it will be called very frequently, and during slow periods like when nothing is being transferred it can slow down to about one call per second. The callback gets told how much data libcurl will transfer and has transferred, in number of bytes.

The callback signature looks like this.

  bool progressCallback(cpr_off_t downloadTotal, cpr_off_t downloadNow, cpr_off_t uploadTotal, cpr_off_t uploadNow, intptr_t userdata);

The values are in bytes. Return true to continue the transfer, and false to cancel it.

Here is an example of using the callback.

int main(int argc, char** argv) {
    cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"},
                      cpr::ProgressCallback([&](cpr_off_t downloadTotal, cpr_off_t downloadNow, cpr_off_t uploadTotal, cpr_off_t uploadNow, intptr_t userdata) -> bool
    {
        std::cout << "Downloaded " << downloadNow << " / " << downloadTotal << " bytes." << std::endl;
        return true;
    }));
    return 0;
}

DebugCallback

This is called by libcurl for verbose debugging information, including all data transferred.

The callback signature looks like this.

  enum class DebugCallback::InfoType {
    TEXT = 0,
    HEADER_IN = 1,
    HEADER_OUT = 2,
    DATA_IN = 3,
    DATA_OUT = 4,
    SSL_DATA_IN = 5,
    SSL_DATA_OUT = 6,
  };
  void debugCallback(DebugCallback::InfoType type, std::string data, intptr_t userdata);

type represents the type of the content, whereas data contains the content itself. Debug messages have type TEXT.

Using Proxies

Proxies, like Parameters, are map-like objects. It’s easy to set one:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"},
                  cpr::Proxies{{"http", "http://www.fakeproxy.com"}});
std::cout << r.url << std::endl; // Prints http://www.httpbin.org/get, not the proxy url

It doesn’t look immediately useful to have Proxies behave like a map, but when used with a Session it’s more obvious:

cpr::Session session;
session.SetProxies({{"http", "http://www.fakeproxy.com"},
                    {"https", "http://www.anotherproxy.com"}})
session.SetUrl("http://www.httpbin.org/get");
{
    cpr::Response r = session.Get();
    std::cout << r.url << std::endl; // Prints http://www.httpbin.org/get after going
                                     // through http://www.fakeproxy.com
}
session.SetUrl("https://www.httpbin.org/get");
{
    cpr::Response r = session.Get();
    std::cout << r.url << std::endl; // Prints https://www.httpbin.org/get after going
                                     // through http://www.anotherproxy.com
}

Setting Proxies on a Session lets you intelligently route requests using different protocols through different proxies without having to respecify anything but the request Url.

Sometimes a proxy requires authentication, and now that we are used to map-like objects - you know the drill. Proxy username/password pairs must be URL encoded, so we need to take care of that.

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"},
                  cpr::Proxies{{"http", "http://www.fake_auth_proxy.com"}},
                  cpr::ProxyAuthentication{{"http", EncodedAuthentiction{"user", "pass"}}});
std::cout << r.text << std::endl;
/*
 * {
 *   "args": {},
 *   "headers": {
 *     ..
 *   },
 *   "url": "http://httpbin.org/get"
 * }
 */

Sending Cookies

Earlier you saw how to grab a cookie from the request:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies/set?cookies=yummy"});
std::cout << r.cookies["cookies"] << std::endl; // Prints yummy
std::cout << r.cookies["Cookies"] << std::endl; // Prints nothing

You can send back cookies using the same object:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies/set?cookies=yummy"});
cpr::Response another_r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies"}, r.cookies);
std::cout << another_r.text << std::endl;

/*
 * {
 *   "cookies": {
 *     "cookie": "yummy"
 *   }
 * }
 */

This is especially useful because Cookies often go from server to client and back to the server. Setting new Cookies should not look surprising at all:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies"},
                  cpr::Cookies{{"ice cream", "is delicious"}});
std::cout << r.text << std::endl;

/*
 * {
 *   "cookies": {
 *     "ice%20cream": "is%20delicious"
 *   }
 * }
 */

By default Cookies and their values will be URL-encoded. Although this is recommend, it is not mandatory for Cookies to be URL-encoded.

[...]
To maximize compatibility with user agents, servers that wish to
store arbitrary data in a cookie-value SHOULD encode that data, for
example, using Base64 [RFC4648].
[...]

Source: RFC6265

URL-encoding for Cookies can be disabled by setting encode = false in the Cookie constructor.

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies"},
                  cpr::Cookies{{"ice cream", "is delicious"}}, false);
std::cout << r.text << std::endl;

/*
 * {
 *   "cookies": {
 *     "ice cream": "is delicious"
 *   }
 * }
 */

PUT and PATCH Requests

PUT and PATCH requests work identically to POST requests, with the only modification being that the specified HTTP method is "PUT" or "PATCH" instead of "POST". Use this when the semantics of the API you’re calling implements special behavior for these requests:

#include <assert.h>

// We can't POST to the "/put" endpoint so the status code is rightly 405
assert(cpr::Post(cpr::Url{"http://www.httpbin.org/put"},
                 cpr::Payload{{"key", "value"}}).status_code == 405);

// On the other hand, this works just fine
cpr::Response r = cpr::Put(cpr::Url{"http://www.httpbin.org/put"},
                  cpr::Payload{{"key", "value"}});
std::cout << r.text << std::endl;

/*
 * {
 *   "args": {},
 *   "data": "",
 *   "files": {},
 *   "form": {
 *     "key": "value"
 *   },
 *   "headers": {
 *     ..
 *     "Content-Type": "application/x-www-form-urlencoded",
 *     ..
 *   },
 *   "json": null,
 *   "url": "https://httpbin.org/put"
 * }
 */

Most often, PUTs are used to update an existing object with a new object. Of course, there’s no guarantee that any particular API uses PUT semantics this way, so use it only when it makes sense to. Here’s a sample PATCH request, it’s essentially identical:

#include <assert.h>

// We can't POST or PUT to the "/patch" endpoint so the status code is rightly 405
assert(cpr::Post(cpr::Url{"http://www.httpbin.org/patch"},
                 cpr::Payload{{"key", "value"}}).status_code == 405);
assert(cpr::Put(cpr::Url{"http://www.httpbin.org/patch"},
                cpr::Payload{{"key", "value"}}).status_code == 405);

// On the other hand, this works just fine[libcurl](http://curl.haxx.se/libcurl/)
cpr::Response r = cpr::Patch(cpr::Url{"http://www.httpbin.org/patch"},
                    cpr::Payload{{"key", "value"}});
std::cout << r.text << std::endl;

/*
 * {
 *   "args": {},
 *   "data": "",
 *   "files": {},
 *   "form": {
 *     "key": "value"
 *   },
 *   "headers": {
 *     ..
 *     "Content-Type": "application/x-www-form-urlencoded",
 *     ..
 *   },
 *   "json": null,
 *   "url": "https://httpbin.org/patch"
 * }
 */

As with PUT, PATCH only works if the method is supported by the API you’re sending the request to.

Download File

CPR specifically provides an interface for downloading files.

Download To File

Download to file is simple:

    std::ofstream of("1.jpg", std::ios::binary);
    cpr::Response r = cpr::Download(of, cpr::Url{"http://www.httpbin.org/1.jpg"});
    std::cout << "http status code = " << r.status_code << std::endl << std::endl;

Download With Callback

When downloading a small file, you might want to allocate enough memory to hold the data you read before starting the download. This is where ‘GetDownloadFileLength()’ comes in.

struct File
{
    void*  file_buf;   // file data will be save to
    int64_t read_len;  // file bytes
};
bool write_data(std::string data, intptr_t userdata)
{
    File* pf = reinterpret_cast<File*>(userdata);
    memcpy(pf->file_buf + pf->read_len, data.data(), data.size());
    pf->read_len += data.size();
    return true; // Return `true` on success, or `false` to **cancel** the transfer.
}
void download_to_mem(File &f)
{
    cpr::Session session;
    session.SetUrl(cpr::Url{"http://www.httpbin.org/1.jpg"});
    f.read_len = session.GetDownloadFileLength();
    f.file_buf = malloc(f.read_len);
    cpr::Result r = session.Download(cpr::WriteCallback{write_data, reinterpret_cast<File*>(&f)});
}
int main()
{
    File f{nullptr, 0};
    download_to_mem(f);
    // do something
    free(f.file_buf); // free file data buf
    return 0;
}

Other Request Methods

C++ Requests also supports DELETE, HEAD, and OPTIONS methods in the expected forms:

// Regular, blocking modes
cpr::Response delete_response = cpr::Delete(cpr::Url{"http://www.httpbin.org/delete"});
cpr::Response head_response = cpr::Head(cpr::Url{"http://www.httpbin.org/get"});
cpr::Response options_response = cpr::OPTIONS(cpr::Url{"http://www.httpbin.org/get"});

// Asynchronous, future mode
AsyncResponse async_delete_response = cpr::DeleteAsync(cpr::Url{"http://www.httpbin.org/delete"});
AsyncResponse async_head_response = cpr::HeadAsync(cpr::Url{"http://www.httpbin.org/get"});
AsyncResponse async_options_response = cpr::OptionsAsync(cpr::Url{"http://www.httpbin.org/get"});

// Asynchronous, callback mode
auto cb_delete_response = cpr::DeleteCallback([](cpr::Response r) {
        return r.text;
    }, cpr::Url{"http://www.httpbin.org/delete"});
auto cb_head_response = cpr::HeadCallback([](cpr::Response r) {
        return r.status_code;
    }, cpr::Url{"http://www.httpbin.org/get"});
auto cb_options_response = cpr::OptionsCallback([](cpr::Response r) {
        return r.status_code;
    }, cpr::Url{"http://www.httpbin.org/get"});

Currently, "PATCH" is not an implemented HTTP method. It soon will be, and its mechanics will be identically to the example above. Stay tuned!

HTTPS Options

CPR verifies SSL certificates for HTTPS requests, just like a web browser. By default, SSL verification is enabled.

cpr::Response r = cpr::Get(cpr::Url{"https://www.httpbin.org/get"});

The underlying implementation automatically switches to SSL/TLS if libcurl provides the appropriate support.

You can also further customize the behavior of the SSL/TLS protocol by passing more configuration items to the request.

SSL/TLS Version

The SSL/TLS protocol has evolved in many different versions for security and performance reasons.

Starting with version 7.39, libcurl uses TLS v1.0 by default.

If you have security and performance concerns, you can force a newer version of the protocol.

cpr::SslOptions sslOpts = cpr::Ssl(ssl:TLSv1_2{});
cpr::Response r = cpr::Get(cpr::Url{"https://www.httpbin.org/get"}, sslOpts);

Or a lower but insecure version of the protocol for compatibility reasons.

ALPN and NPN

Some older HTTPS services do not support ALPN and NPN negotiation, which may result in connections not being established properly. Compatibility issues can be resolved by disabling ALPN/NPN support for the request.

cpr::SslOptions sslOpts = cpr::Ssl(ssl::ALPN{false}, ssl::NPN{false});
cpr::Response r = cpr::Get(cpr::Url{"https://www.httpbin.org/get"}, sslOpts);

Application-Layer Protocol Negotiation (ALPN) is a Transport Layer Security (TLS) extension that allows the application layer to negotiate which protocol should be performed over a secure connection in a manner that avoids additional round trips and which is independent of the application-layer protocols. It is needed by secure HTTP/2 connections, which improves the compression of web pages and reduces their latency compared to HTTP/1.x. The ALPN and HTTP/2 standards emerged from development work done by Google on the now withdrawn SPDY protocol.

NPN, or Next Protocol Negotiation, allows a TLS connection to negotiate which application-level protocol will be running across it.

Verify SSL/TLS Certificate and Status

By default, the Libcurl library attempts to verify the SSL/TLS protocol server-side certificate and its status.

If you wish to connect to a server that uses a self-signed certificate, you can turn off the corresponding check with the VerifyHost, VerifyPeer and VerifyStatus options.

Pinned Public Key

When negotiating a TLS or SSL connection, the server sends a certificate indicating its identity. A public key is extracted from this certificate and if it does not exactly match the public key provided to this option, libcurl (and therefore CPR) will abort the connection before sending or receiving any data.

You can specify the public key using the PinnedPublicKey option. The string can be the file name of your pinned public key. The file format expected is “PEM” or “DER”. The string can also be any number of base64 encoded sha256 hashes preceded by “sha256//” and separated by “;”

cpr::SslOptions sslOpts = cpr::Ssl(ssl::PinnedPublicKey{"pubkey.pem"});
/* OR
cpr::SslOptions sslOpts = cpr::Ssl(ssl::PinnedPublicKey{"sha256//J0dKy1gw45muM4o/vm/tskFQ2BWudtp9XLxaW7OtowQ="});
*/
cpr::Response r = cpr::Get(cpr::Url{"https://www.httpbin.org/get"}, sslOpts);

If you do not have the server’s public key file you can extract it from the server’s certificate:

# retrieve the server's certificate if you don't already have it
#
# be sure to examine the certificate to see if it is what you expected
#
# Windows-specific:
# - Use NUL instead of /dev/null.
# - OpenSSL may wait for input instead of disconnecting. Hit enter.
# - If you don't have sed, then just copy the certificate into a file:
#   Lines from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----.
#
openssl s_client -servername www.httpbin.org -connect www.httpbin.org:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > www.httpbin.org.pem

# extract public key in pem format from certificate
openssl x509 -in www.httpbin.org.pem -pubkey -noout > www.httpbin.org.pubkey.pem

# convert public key from pem to der
openssl asn1parse -noout -inform pem -in www.httpbin.org.pubkey.pem -out www.httpbin.org.pubkey.der

# sha256 hash and base64 encode der to string for use
openssl dgst -sha256 -binary www.httpbin.org.pubkey.der | openssl base64

SSL Client Certificate

Some HTTPS services require client certificates to be given at the time of connection for authentication and authorization.

You can specify filenames for client certificates and private keys using the CertFile and KeyFile options. When using libcurl 7.71.0 or newer, you can also pass a private key using the KeyBlob option.

Private key as a key path:

cpr::SslOptions sslOpts = cpr::Ssl(ssl::CertFile{"cert.pem"}, ssl::KeyFile{"key.pem"});
cpr::Response r = cpr::Get(cpr::Url{"https://www.httpbin.org/get"}, sslOpts);

Private key as a blob (libcurl 7.71.0 or newer):

cpr::SslOptions sslOpts = cpr::Ssl(ssl::CertFile{"cert.pem"}, ssl::KeyBlob{"-----BEGIN RSA PRIVATE KEY-----[...]"});
cpr::Response r = cpr::Get(cpr::Url{"https://www.httpbin.org/get"}, sslOpts);

The default certificate and private key files are in PEM format, and DER format files can also be imported via DerCert and DerKey if desired.

Certificate Authority (CA) Bundle

By default, libcurl uses the operating system’s root certificate chain to authenticate peer certificate.

If you need to verify a self-signed certificate, you can use the CaInfo to specify the CA certificate bundle file, or CaPath to specify the directory where multiple CA certificate files are located. If libcurl is built against OpenSSL, the certificate directory must be prepared using the openssl c_rehash utility.

Retrieving Certificate Information

After a successful request it is possible to retrieve certificate information. The return value is of type std::vector<std::string>. It contains one entry per certificate. An example can be found bellow:

Url url = "https://github.com";
Response response = cpr::Get(url);
std::vector<std::string> certInfo = response.GetCertInfo();
for (const std::string& s : certInfo)
{
    std::cout << "Info: " << s << '\n';
}

The output could look like:

[...]
Info: Subject:C = XX, L = Default City, O = Default Company Ltd
[...]

Interface

It is also possible to specify the outgoing interface used by libcurl. By default the TCP stack decides which interface to use for this request. You can change this behavior by passing the cpr::Interface option to your request. Passing an empty string corresponds to passing a nullptr to CURLOPT_INTERFACE.
Further details: https://curl.se/libcurl/c/CURLOPT_INTERFACE.html

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"},
                  cpr::Interface{""}); // Let the TCP stack decide (same as default)
cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"},
                  cpr::Interface{"eth0"}); // eth0 will be used as outgoing interface

Redirects

For configuring the behavior once a redirect occurs, the cpr::Redirect class exists. It houses three attributes

In the following example we will follow up to 42 redirects and in case we encounter a 301 or 302 redirect, we will post again.

cpr::Response r = cpr::Post(cpr::Url{"http://www.httpbin.org/get"},
                  cpr::Payload{{"key", "value"}},
                  cpr::Redirect{42L, true, false, PostRedirectFlags::POST_301 | PostRedirectFlags::POST_302});

HTTP Protocol Version

To change the HTTP protocol version, the cpr::HttpVersion class exists. It takes a cpr::HttpVersionCode, which changes the underlying HTTP protocol version used. Possible values are:

cpr::Response r = cpr::Get(cpr::Url{"http://google.de"},
                  cpr::HttpVersion{cpr::HttpVersionCode::VERSION_2_0});

Range Requests

HTTP range requests can be used to receive only a part of a HTTP message. This allows specific access to required areas of large files or to pause downloads and resume them later.

To make a simple HTTP range request, the range options need to be set as follows:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/headers"},
                           cpr::Range{1, 5});
std::cout << r.text << std::endl;
/*
 * {
 *   "headers": {
 *     "Range": "bytes=1-5", 
 *     ...
 *   }
 * }
 */

To leave parts of the range empty, -1 can be specified as the boundary index when creating the partial range:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/headers"},
                           cpr::Range{-1, 5});
std::cout << r.text << std::endl;
/*
 * {
 *   "headers": {
 *     "Range": "bytes=-5", 
 *     ...
 *   }
 * }
 */

Moreover, multiple ranges can be specified in a single request with cpr::MultiRange:

cpr::Response r = cpr::Get(cpr::Url{"http://www.httpbin.org/headers"}, 
                           cpr::MultiRange{cpr::Range{1, 3}, cpr::Range{5, 6}});
std::cout << r.text << std::endl;
/*
 * {
 *   "headers": {
 *     "Range": "bytes=1-3, 5-6", 
 *     ...
 *   }
 * }
 */

As always, there is of course also the possibility to set the range of a session object manually:

cpr::Session session;
session.SetOption(cpr::Range{1, 3});                    // Alternative: SetRange()
session.SetOption(cpr::MultiRange{cpr::Range{1, 3}, 
                                  cpr::Range{5, 6}});   // Alternative: SetMultiRange()