Google APIs Client Library for C++ A C++ library for client applications to access Google APIs.
Making HTTP Requests

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

  1. Typical use case examples
    1. Sending GET, POST, and other HTTP requests
    2. Handling responses
    3. Making authorized requests
    4. Making asynchronous requests
    5. Adding and removing custom headers
    6. Creating an HttpTransport instance
    7. Setting request options
  2. Provided transport implementations
    1. curl
    2. Mock transport
    3. Playback transport

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