Google APIs Client Library for C++ |
A C++ library for client applications to access Google APIs. |
This document describes how to make low-level HTTP requests using the core components of the library's transport layer. It is also possible to use this API with an HTTP transport implementation provided elsewhere. See Writing a Custom HTTP Transport for more information.
The transport layer Http* components are used for making
HTTP requests. These can be used for any HTTP request to any webserver,
not just REST-style service interactions or the Google Cloud Platform.
These are generic components. See the document
Making Service Requests for a higher
level abstraction when talking to cloud services.
For more information about the HTTP protocol in general, see RFC 2616 or the Wikipedia page on Hypertext Transfer Protocol.
Contents
Typical use case examples
These examples show how to use the core components to issue basic HTTP
GET and POST requests and look at the response.
The examples are extremely simplistic to focus on the things you can do
with the API rather than on what real-world applications look like.
The following snippet applies to all the examples in this section.
#include "googleapis/client/data/data_reader.h" #include "googleapis/client/data/http_request.h" #include "googleapis/client/transport/http_response.h" #include "googleapis/client/transport/http_transport.h" #include "googleapis/util/status.h" using googleapis_client::HttpRequest; using googleapis_client::HttpRequestState; using googleapis_client::HttpResponse; using googleapis_client::HttpTransport; using googleapis_client::HttpTransportLayerConfig; using googleapis_client::HttpTransportOptions;
Sending GET, POST, and other HTTP requests
The following snippet issues an HTTP GET on a given URL.
void IllustrateGet(const char* url, HttpTransport* transport) {
scoped_ptr<HttpRequest> request(transport->NewHttpRequest(HttpRequest::GET));
request->set_url(url);
util::Status status = request->Execute();
if (!status.ok()) cerr << status.error_message();
}
All HTTP requests based solely on a URL (they do not send additional data
in the HTTP payload) will look like the previous GET example.
For example, the following issues an HTTP POST to the URL
without sending additional data.
void IllustratePost(const char* url, HttpTransport* transport) {
scoped_ptr<HttpRequest> request(transport->NewHttpRequest(HttpRequest::POST));
request->set_url(url);
util::Status status = request->Execute();
if (!status.ok()) cerr << status.error_message();
}
Messages that contain an HTTP message payload use the following pattern.
In this example we are issuing an HTTP POST. See
Using Data Reader for more information
on using DataReader.
using googleapis_client::DataReader;
using googleapis_client::NewUnmanagedInMemoryDataReader;
void IllustratePostWithData(const char* url, HttpTransport* transport) {
scoped_ptr<HttpRequest> request(transport->NewHttpRequest(HttpRequest::POST));
request->set_url(url);
DataReader* reader = NewUnmanagedInMemoryDataReader("Hello, World!");
request->set_content_reader(reader);
request->set_content_type("text/plain");
util::Status status = request->Execute();
if (!status.ok()) cerr << status.error_message();
}
Handling responses
There are different ways of handling responses. These are independent of the type of request or how it was issued.
The HttpRequest::Execute method returns a
googleapis::util::Status. The result status is the same as the
response.status() attribute. This is the "overall" status of
the request from the application's perspective. Additionally there is a
response.transport_status attribute which is just the
transport level perspective. The difference is illustrated in the following
two examples.
In the following snippet, only HTTP 2xx responses are
considered successful. It considers anything else a failure.
This includes HTTP
response codes indicating errors (such as HTTP 404)
and network errors (such as unknown host).
if (response->ok()) {
cout << "Success" << endl;
} else {
cout << "Failed with status="
<< response->status().error_message() << endl;
}
The following snippet only considers transport errors as failures.
A response such as an HTTP 404 is considered a success
because we were able to send the request and receive a response back.
Only cases in which there is no HTTP response at all are treated
as failures.
if (response->transport_status().ok()) {
string body;
util::Status status = response->GetBodyString(&body);
cout << "Transport OK. Received HTTP Status Code="
<< response->http_code() << endl;
if (status.ok()) {
cout << "HTTP Body:" << endl << body << endl;
} else {
cout << "Could not read response body" << endl;
}
} else {
cout << "Transport failed: "
<< response->transport_status().error_message() << endl;
}
If you are interested in the specific lifecycle state that the request is
in, you can check it in the response passed to the Execute
method.
if (response->request_state_code() == HttpRequestState::TIMED_OUT) {
cout << "Request timed out" << endl;
}
Making authorized requests
Services on the Google Cloud Platform often use
OAuth 2.0 to protect sensitive
information. These endpoints will return
HTTP 401 Unauthorized unless proper credentials are
provided. See the document
Authentication and Authorization Overview for more information about
obtaining credentials.
Credentials are bound to individual requests. This allows transports to be shared among different users in a server deployment.
#include "googleapis/client/auth/oauth2_authorize.h" using googleapis::client::OAuth2Credential; ... HttpRequest* request = ...; // Any request OAuth2Credential* credential = ...; // A credential request->set_credential(credential); request->Execute();
Making asynchronous requests
The transport layer supports asynchronous requests in addition to the
simpler blocking requests. An asynchronous request is executed similar to
a blocking request except the ExecuteAsync method takes an
additional HttpRequestCallback parameter.
Rather than blocking the
method until the request completes, ExecuteAsync returns
immediately and will asynchronously call the callback when the request has
completed. HttpRequestCallback
is a callback taking a single HttpRequest* argument.
Callbacks are discussed further in the Closures
section of the Foundation Types document.
Exactly when and how the request will be processed is determined by
an Executor object. These are discussed in the
Executors section in the
Foundation Types document. Each transport has an
Executor either explicitly bound or implicitly the global
default. Executors are not owned by the transport so are not necessarily
unique — they can be shared across one or more transports.
Conceptually the ExecuteAsync method only queues
the request before returning. If the request cannot be queued
then the request will complete immediately and the callback is notified.
The executor may actually execute the request right away and even if
concurrent and in another thread, might still finish and invoke the
callback before the ExecuteAsync returns. The
InlineExecutor class executes the request in the same thread
as the caller making it behave as a synchronous call (the benefit is that
it does not require threads) but with the added callback.
The callback can be NULL. If this is the case then you will
not know when the request finishes or if it is successful without polling.
In order to poll, you can check the done() method on the
response, then inspect the response for the results.
The response and transport objects are thread-safe; however most other
SDK objects are not. Check the documentation and provide thread-safe
programming practices when using them from within callbacks that might
be accessing them in different threads.
By convention the caller maintains ownership of the
HttpRequest and the request keeps ownership of the
HttpResponse. The caller can pass ownership to the callback
To facilitate management of the HttpRequest, you
can set the destroy_when_done option before executing it.
This will pass ownership to the ExecuteAsync
(or Execute)
and cause the request object to self-destruct once it is completed and
the callback (if any) has been called. This has no effect on the response
object; its life-cycle is independent.
The following example shows an asynchronous invocation.
HttpRequest* IllustrateGetAsync(
const char* url, HttpTransport* transport,
HttpRequestCallback* callback) {
HttpRequest* request = transport->NewHttpRequest(HttpRequest::GET);
request->mutable_options()->set_destroy_when_done(true);
request->set_url(url);
request->ExecuteAsync(callback);
return request;
}
An example callback method and snippet calling the example are shown below.
Often real-world applications will use request-level timeouts to bound the
time a request can remain outstanding and not need to call
WaitUntilDone if they never need to synchronize back with
the main thread.
static void HandleResponse(const char* which, HttpRequest* request) {
HttpResponse* response = request->response();
cout << "GET response for " << which << ": "
<< (response->ok() ? "OK" : response->status().error_message());
}
...
HttpRequestCallback *callback =
NewCallback(&HandleResponse, "IllustrateGetAsync");
// We set this to destroy when done.
HttpRequest* request(IllustrateGetAsync(url, transport, callback));
request->response()->WaitUntilDone();
Adding and removing custom headers
The Google APIs Client Library for C++ will automatically add the basic headers that you need for the APIs that it provides. However there are times when you may want to add additional headers. The following snippet illustrates how to do this.
request->AddHeader("MyHeader", "MyHeaderValue");
request->AddHeader("AnotherHeader", "AnotherHeaderValue");
const string* check_value = request->FindHeaderValue("SomeHeaderToCheck");
if (check_value) {
cout << "Already have SomeHeaderToCheck with value="
<< *check_value << endl;
}
Creating an HttpTransport instance
Transports are created and configured from an
HttpTransportFactory which in turn are usually
configured from an HttpTransportLayerConfig instance.
Usually the HttpTransportLayerConfig owns some object
instances shared among multiple transport and factory instances so
must remain valid over the lifetime of the factory. If you are only
using one factory, you can pass ownership of the factory to the config
with ResetDefaultTransportFactory and use the config
NewDefaultTransport as a factory.
You can explicitly create a transport from a generic factory instance
using its New() method. This is the preferred way if you are
not using the default.
Setting request options
A Request can be independently configured with various options through its
HttpRequestOptions attribute. The following example changes
the request's timeout.
util::Status IllustrateGetAndPrintWithTimeout(
const char *url, int64 timeout_ms, HttpTransport* transport) {
scoped_ptr<HttpRequest> request(transport->NewHttpRequest(HttpRequest::GET));
request->set_url(url);
request->mutable_options()->set_timeout_ms(timeout_ms);
request->Execute().IgnoreError();
HttpResponse* response = request->response();
if (response->transport_status().ok()) {
string body;
util::Status status = response->GetBodyString(&body);
cout << "Transport OK. Received response with HTTP code="
<< response->http_code() << endl;
if (status.ok()) {
cout << body << endl;
} else {
cout << "Could not get response body" << endl;
}
} else {
cout << "Transport FAILED: "
<< response->transport_status().error_message() << endl;
cout << "timeout="
<< (response->request_state_code()
== HttpRequestState::TIMED_OUT) << endl;
}
return response->status();
}
To change default options for all requests (created by a transport),
change the default request options on that HttpTransport.
The changes are not retroactive. They will only start applying to
new HttpRequest instances created from that point onward.
using googleapis::client::HttpRequestOptions; const int64 kDefaultTimeoutMillis = 123456; HttpRequestOptions* default_options = transport->mutable_default_request_options(); default_options->set_timeout_ms(kDefaultTimeoutMillis);
Provided transport implementation
curl
The SDK comes within an HttpTransport implementation using
the cURL library.
This CurlHttpTransport is implemented in the
header file
googleapis/client/transport/curl_http_transport.h
and the source file
googleapis/client/curl_http_transport.cc
Mock transport
The MockHttpTransport is provided to facilitate unit testing
code that uses the Transport Layer. The mock transport uses the
Google C++ Mocking Framework. The transport gives you complete
access to inject mocks. You can use a mock transport where you inject
an HttpTransport.
Testing is covered more in Testing and Debugging the HTTP Transport Layer
Playback transport
The JsoonPlaybackTransport is an alternative transport for
testing using a fake transport. The playback transport can replay
pre-recorded transcripts of live interactions without talking to the
actual server. This provides a fast and repeatable way to unit test
applications and high level libraries that use cloud services and
web servers.
Testing is covered more in Testing and Debugging the HTTP Transport Layer
Google APIs Client Library for C++