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