Google APIs Client Library for C++ A C++ library for client applications to access Google APIs.
Adding a Custom HTTP Transport

This document describes how to write your own HttpTransport implementation. It may be of interest if you already have an HTTP transport that you use within your client application, and would like to use that same implementation when talking to cloud services with the client-library.

Custom HTTP Transports is an advanced use-case. Typically client applications use an implementation already provided with the SDK. These are documented in Making HTTP Requests.

Contents

  1. When to consider a custom HTTP transport
  2. Steps for adding a custom transport
    1. Implement a subclass of HttpRequest
    2. Implement a subclass of HttpTransport
    3. Implement a subclass of HttpTransportFactory
    4. Test for compliance
  3. Troubleshooting a custom transport

When to consider a custom HTTP transport

The Google APIs Client Library for C++ comes with a functional transport implementation using the open source curl transport. If your application is already using another transport then you might wish to have all traffic go through that mechanism rather than split across different implementations. Multiple implementations add more resources to your application and may be more difficult to instrument and monitor if you do such things. You may also have trouble porting curl to the platform that you are on. Or perhaps your have a different type of network layer that curl does not support.

Steps for adding a custom transport

The following steps illustrate the complete process for adding a a custom HttpTransport. It assumes that you already have a library that implements the HTTP protocol and you would like to integrate that implementation to be used natively with the Google APIs Client Library for C++.

The steps are presented bottom up to simplify the documentation. In practice you might want to stub out classes and implement them more top down or through several iterative passes of refinement. What is easiest depends on the implementation details of your transport. In the end, you need to perform all the steps to complete. However, you may aim for different milestones along the way that suite your development style.

The code samples in the remainder of this document assume the following context:

#include "googleapis/client/data/data_reader.h"
#include "googleapis/client/data/data_writer.h"
#include "googleapis/client/transport/http_transport.h"
#include "googleapis/client/transport/http_request.h"
#include "googleapis/client/transport/http_response.h"
#include "googleapis/client/transport/http_types.h"
#include "googleapis/client/util/status.h"
using googleapis_client::DataReader;
using googleapis_client::DataWriter;
using googleapis_client::HttpHeaderMap;
using googleapis_client::HttpRequest;
using googleapis_client::HttpRequestState;
using googleapis_client::HttpResponse;
using googleapis_client::HttpTransport;
using googleapis_client::HttpTransportFactory;
using googleapis_client::HttpTransportLayerConfig;
using googleapis_client::HttpTransportOptions;

Implement a subclass of HttpRequest

The HttpRequest class is responsible for putting the actual HTTP requests on the network and reading the responses back. This is going to be the bulk of your integration work. It might be the only class that knows about the custom HTTP protocol library that you are adapting. Often the HttpTransport class you write later will not even need to know that your HTTP protocol implementation exists.

We recommend that your HttpRequest class be a private class known only to the transport. You usually will not even need a header file for it unless you want to write explicit unit tests against internal details. Your class must minimally provide a constructor and an implementation of the protected pure virtual DoExecute method specified in the base HttpRequest. That is all you need. In fact your specialized class can be internal -- it is only known to your transport class which exposes it as an instance of the abstract base class. There is not reason to add additional methods other than to decompose the required ones.

Implement your HttpRequest subclass's constructor

The constructor must explicitly call the base class constructor with the HttpRequest::HttpMethod and HttpTransport instance. You may add additional parameters into the constructor if you need. The HttpTransport* parameter to your constructor can be typed as your specialized HttpTransport class if it contains additional state variables and/or helper methods that you want to use with your request class. Otherwise leave it as an abstract HttpTransport. The base class only has access to the abstract base HttpTransport class regardless.

If you pass a typed instance, you can either store it as an additional specialized instance variable or you canstatic_cast the transport() attribute from the base class since you know that it must be your transport class (because your constructor only explicitly uses your custom class).

You may want to initialize other private request state that you will be using later as in any other constructor.

Implement your HttpRequest subclass's DoExecute

DoExecute is a synchronous method but it might be called from a multi-threaded context. You do not normally need to worry about thread-safety within the googleapis components since the method is a protected member only called by the base class which handles the synchronization for you. If you are using additional shared state required by your HTTP protocol library implementation then that may require precautions for multi-threading since you may have multiple instances of your custom HttpRequest class executing concurrently.

The general flow for DoExecute is:

  1. Form the actual request.
    • Consider any relevant options() attributes, such timeout_ms.
    • Use the URL() and method() attributes to know what you are invoking.
    • Use the headers() attribute for the request headers.
      • These already include standard headers such as: Host, User-Agent, Content-Type, Content-Length.
    • Use the content_reader() attribute for the request payload.
  2. Attempt to send the request.
    • If you could not send the request, call mutable_state()->set_transport_status() detailing the error then return.
  3. Wait for a response.
    • If your HTTP protocol implementation times-out then call mutable_state()->set_transport_status() with a StatusDeadlineExceeded() error and return.
  4. Initialize the HttpResponse in the response() attribute.
    • Parse the HTTP code and mutable_state()->set_http_code().
    • Parse the response headers and add them into the response.
    • Write the response payload into the response()->body_writer().
  5. Let the base class interact with the HttpTransportErrorHandlerhandle redirects (HTTP 3xx codes) and other HTTP error responses (such as 401 and 503).
That's it. In practice this may be a lot of work depending on the API for the HTTP protocol implementation you are trying to adapt.

The following snippet declares a minimal request class.

class MyCustomRequest : public HttpRequest {
 public:
  MyCustomRequest(
      const HttpRequest::HttpMethod& method, HttpTransport* transport)
      : HttpRequest(method, transport) {
  }
  virtual ~MyCustomRequest() {}

  virtual void DoExecute(HttpResponse* response);

 private:
  DISALLOW_COPY_AND_ASSIGN(MyCustomRequest);
};

If you wish to stub this class out to implement the transport and factory first you can implement DoExecute to fail with a transport error as follows:

void DoExecute(HttpResponse* response) {
  mutable_state()->set_transport_status(
      googleapis_client::StatusUnimplemented("Not yet implemented"));
}

Otherwise, the DoExecute method might look something like the following:

void MyCustomRequest::DoExecute(HttpResponse* response) {
  string log;

  // This is the data that would go in the request that we send using the
  // external HTTP protocol implementation that we are adapting.
  int64 timeout_ms = options().timeout_ms();
  StrAppend(&log, "DoExecute method=", http_method(), " url=", url(),
            " timeout=", timeout_ms, "\n");
  StrAppend(&log, "Request Headers:\n");
  for (HttpHeaderMap::const_iterator it = headers().begin();
       it != headers().end();
       ++it) {
    StrAppend(&log, "  ", it->first, ": ", it->second, "\n");
  }
  StrAppend(&log, "Request Content:\n");
  DataReader* reader = content_reader();
  StrAppend(&log, reader ? reader->RemainderToString() : "<null>", "\n");

  // We'll hardcode a 503 here just for illustrative purposes.
  // Normally this response code, headers, and payload body would come
  // from your proprietary HTTP protocol implementation.
  HttpRequestState* state = response->mutable_request_state();
  state->set_http_code(503);
  response->AddHeader("BogusResponseHeader", "BogusHeaderValue");
  util::Status status = response->body_writer()->Write("Bogus Response");

  if (!status.ok()) {
    state->set_transport_status(status);
  }
}

Implement a subclass of HttpTransport

The HttpTransport class is usually very simple to write because the real work either happens in the base class or is delegated to HttpRequest instances. The main responsibility of your specialized HttpTransport class is to act as a HttpRequest factory.

Transports must have a constructor that takes an explicit const HttpTransportOptions& options. Note that the base HttpTransport class does not have a default constructor at this time. If you really do need a default constructor, you should pass a default options to the base constructor.

The constructor should set the ID attribute with the name of the transport class by default. If your HTTP protocol implementation library is not thread-safe then you might want to force the HttpTransportOptions::executor to be an InlineExecutor, though leaving this to higher level callers that pass the options gives you (or your consumers) flexibility for testing.

Transport classes must implement the pure virtual NewHttpRequest method from the base HttpTransport. This method is a request factory. Typically it just instantiates your custom HttpRequest class forwarding the HttpMethod and adding itself (this) as the transport instance.

The following snippet defines a custom transport class with a default constructor, though we recommend against them. This snippet is the complete implementation since the interesting work is delegated to the custom request class.

class MyCustomTransport : public HttpTransport {
 public:
  // Transports do not typically have a default constructor.
  // But if you must have one, defined it as follows
  MyCustomTransport()
      : HttpTransport(HttpTransportOptions()) {
    set_id(kTransportIdentifier);
  }

  explicit MyCustomTransport(const HttpTransportOptions& options)
      : HttpTransport(options) {
    set_id(kTransportIdentifier);
  }

  virtual ~MyCustomTransport() {
  }

  // Ususally this is implemented in a private source file
  // because the MyCustomRequest class would not be exposed anywhere.
  virtual HttpRequest* NewHttpRequest(const HttpRequest::HttpMethod& method) {
    return new MyCustomRequest(method, this);
  }

  static const StringPiece kTransportIdentifier;

 private:
  DISALLOW_COPY_AND_ASSIGN(MyCustomTransport);
};

const StringPiece MyCustomTransport::kTransportIdentifier("MyCustomTransport");

Your transport may have additional configurable attributes with it. These can either be individual instance variables, or you can create an options class. At present we recommend against subclassing the HttpRequestOptions. Instead add a second options attribute with the specialized options you expose.

Implement a subclass of HttpTransportFactory

We strongly recommend you add a custom HttpTransportFactory class as well, though it is not strictly required. In fact, we believe it is more important to expose a factory than to expose the custom transport class.

Factories do not typically have a default constructor, rather they have a constructor explicitly taking a HttpTransportLayerConfig instance. The main purpose of the config is to provide the HttpTransportOptions to use for the HttpTransport instances it takes. The config is used to own the data that the options reference so that you only need to track a single instance rather than all the individual attributes that you might set. Your constructor just passes this parameter through to the base class.

Your custom transport factory subclass must implement the protected pure virtual DoAlloc method specified by the base HttpTransportFactory. This creates a new instance of your specialized HttpTransport configures it with any custom attributes introduced by your factory. The DoAlloc method is called from within the base class; the base class finishes configuring the new HttpTransport instance with the standard factory attributes.

The following snippet shows a typical factory implementation. If your transport class has additional attributes then you might want to provide attributes on the factory to control the values they are created with. Your DoAlloc method should use these to configure the transport it returns with the custom attributes that you are adding. Leave the standard attributes to the base class.

class MyCustomTransportFactory : public HttpTransportFactory {
 public:
  explicit MyCustomTransportFactory(HttpTransportLayerConfig* config)
      : HttpTransportFactory(config) {
  }
  virtual ~MyCustomTransportFactory() {}

 protected:
  HttpTransport* DoAlloc(const HttpTransportOptions& options) {
    return new MyCustomTransport(options);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(MyCustomTransportFactory);
};

Troubleshooting a custom transport

You can use the HttpScribe in the base HttpTransport class to generate a transcript of messages flowing through your transport.

Additionally you may want to add VLOG logging messages from the Google glog logging library for C++ that is already utilized within the Google APIs Client Library for C++. See How To Use Google Logging Library for more information about logging.

The core Google APIs Client Library for C++ is defines using only the abstract classes that you have customized here in this document. You can use your transport with the samples or use one of the provided transports, such as CurlHttpTransport with your application to help isolate problems to your library or elsewhere in your application or network.