Curl for People (crp) library introduction - advanced usage

1. Response Objects

Response objects are packets. Their sole purpose is to provide client-side information at the end of the request - after the API Response is returned to you, no API uses it. This reasoning drove the decision to make the response's member fields public and mutable.
A Response has the following fields:

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

And access is very simple:

auto r = cpr::Get(cpr::Url{"http://www.httpbin.org/get"});
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;
}

This Header is essentially a map with important modifications. Its keys are case-insensitive, as required by RFC 7230:

auto 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". Similarly, cookies are also accessible through a map-like interface, but they are not case-sensitive:

auto 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, this Response object is completely transparent. All of its data fields are accessible at any time, and since it's only useful when there is information to communicate, it's safe to let it go out of scope when you're done with it.

2. Request Headers

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

auto 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"
 * }
 */

You may have noticed the similarity between Header, Parameters, Payload, and Multipart. They all have constructors of the form:

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

This is no accident - all of these are map-like objects, and their syntax is the same, since their semantics depend entirely on the object type. Also, it's useful that Parameters, Payload and Multipart are interchangeable because sometimes APIs don't strictly distinguish them.

3. Session Objects

Under the hood, all calls to the main API modify an object named a Session before executing the request. This is the only truly stateful part of the library, and for most applications there's no need for the Session to act directly, preferring to let the library handle it for you.
However, if holding state is useful, you can use Session:

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

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

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

auto 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 actually exposes two different interfaces to set the same options. If you want, you can do this instead of the above:

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

This is important, so it emphasizes: For each configuration option (like Url, Parameters), there are corresponding methods Set and aSetOption(). The second interface is to facilitate template metaprogramming magic, letting the API expose unordered methods.
The key to all of this is actually the way libcurl is designed. It uses a policy-based design that relies on configuring a single library object (curl handle). Every option configured into this object changes its behavior in a mostly orthogonal way.
Session takes advantage of it and exposes a more modern interface, freeing libcurl from a huge waste. Understanding the policy-based design of libcurl is important to understand how the Session object behaves.

4. Asynchronous Requests

Making asynchronous requests uses a similar but separate interface:

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

The call is the same except that instead of the other Get, it's GetAsync. Similar to POST requests, you can call PostAsync. The return value of an asynchronous call is actually a std::future<Response>:

auto fr = cpr::GetAsync(cpr::Url{"http://www.httpbin.org/get"});
fr.wait() // This waits until the request is complete
auto 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 use them later:

auto container = std::vector<std::future<cpr::Response>>{};
auto 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 (auto& fr: container) {
    auto r = fr.get();
    std::cout << r.text << std::endl;
}

An important note to make here is that the parameters passed to the async call are copied. Under the hood, std::async is done via asynchronous calls to the library's API. By default, all parameters are copied (or when temporarily moved) for memory safety, as there is no syntax-level guarantee that parameters are out of the requested scope.
It is possible to force std::async to use this default so that arguments are passed arguments instead of values. However, cpr::Async does not currently support forced delivery, although this is planned for a future release.

5. Asynchronous Callbacks

C++ requests also support a callback interface for asynchronous requests. Use the callback interface, passing a functor (lambda, function pointer, etc.) as the first argument, followed by other options that are usually chosen in blocking requests. The functor takes one parameter, a Response object - this response is populated when the request completes and the function body executes.
Here is 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 few key features to point out here:
1. The return value is a std::string . This is not hardcoded - the callback is free to return any value! When you need to get the value, check if the request is complete, then simply .get() the future value to get the correct value. This flexibility makes the callback interface very simple, versatile and efficient!
2. The lambda capture is empty, but definitely not needed . Anything that can be captured in a lambda can usually be captured in a lambda passed into the callback interface. This extra vector of flexibility makes it ideal for working with lambda expressions, although any functor with a Response parameter will compile and work.
Also, you can enforce an immutable Response with a const Response& parameter instead of simply enforcing the Response.

6. Setting Timeout

If you have strict time requirements, you can set a timeout for your request:

#include <assert.h>

auto 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

Set the Timeout option to set the maximum time the transfer operation can take. Since C++ requests are built on top of libcurl, it's important to understand what Timeout does to the request. You can find more information about specific libcurl options here.

7. Using Proxies

Proxies, like Parameters are map-like objects. Setting one up is easy:

auto 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 as useful as Proxies as a map, but it's more obvious when using Session:

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

Setting Proxies a Session allows you to intelligently route requests by using different proxies for different protocols without specifying anything but the request Url.

8. Sending Cookies (SendingCookies)

Earlier you saw how to get the cookie from the request:

auto 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 use the same object to send back cookies:

auto r = cpr::Get(cpr::Url{"http://www.httpbin.org/cookies/set?cookies=yummy"});
auto 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 usually travel from server to client and back to the server. Setting new cookies shouldn't come as a surprise:

auto 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"
 *   }
 * }
 */

Note how the cookie is encoded using the url-encoding mode as required by RFC 2965. Aside from this quirk, using cookies is fairly simple and just works.

9. PUT and PATCH Requests

PUT and PATCH requests work the same way as POST requests, the only modification is that the specified HTTP method is "PUT" or "PATCH" not "POST". Use it when the semantics of the API you are calling implements the special behavior of 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
auto 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 of the time, PUT is used to update an existing object with a new one. Of course, this doesn't guarantee that any particular API uses PUT semantics this way, so only use it if it makes sense. Here's an example PATCH request, which is basically the same:

#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
auto 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"
 * }
 */

Like PUT, PATCH only works if the API you are sending the request from supports the method.
Other Request Methods
C++ Request also supports DELETE, HEAD and OPTIONS methods in the expected form:

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

// Asynchronous, future mode
auto async_delete_response = cpr::DeleteAsync(cpr::Url{"http://www.httpbin.org/delete"});
auto async_head_response = cpr::HeadAsync(cpr::Url{"http://www.httpbin.org/get"});
auto 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 will soon be established, and its mechanism will be consistent with the above example.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325439979&siteId=291194637
Recommended