Google APIs Client Library for C++ | A C++ library for client applications to access Google APIs. |
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
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:
- 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.
- Attempt to send the request.
- If you could not send the request,
call
mutable_state()->set_transport_status()
detailing the error then return.
- If you could not send the request,
call
- Wait for a response.
- If your HTTP protocol implementation times-out then
call
mutable_state()->set_transport_status()
with aStatusDeadlineExceeded()
error and return.
- If your HTTP protocol implementation times-out then
call
- 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()
.
- Parse the HTTP code and
- Let the base class interact with the
HttpTransportErrorHandler
handle redirects (HTTP 3xx codes) and other HTTP error responses (such as 401 and 503).
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.