2.20. Apache Thrift

Apache Thrift is a remote procedure call mechanism for heterogeneous environments. A platform independent interface description language is used to describe the remotely accessible interfaces. The runtime environment supports multiple encodings over multiple transports.

2.20.1. Interface Description Language

Thrift Interface Specification Example

namespace cpp org.example
namespace java org.example

enum AnEnum {
    ONE = 1,
    TWO = 2,
    THREE = 3
}

struct SomeMessage {
    1: bool aBooleanField,
    2: i8 aByteField,
    3: i16 aShortIntField,
    4: i32 aNormalIntField,
    5: i64 aLongIntField,

    10: double aDoubleField,

    20: string aStringField,
    22: binary aBinaryField,

    // Fields can have default values.
    100: AnEnum anEnumFieldWithDefault = AnEnum.THREE,

    // Fields can be optional.
    200: optional i16 anOptionalIntField
}

// Exceptions are structures too.
exception SomeException {
    1: list<string> aStringList,
    2: set<i8> aByteSet,
    3: map<i16, string> aMap
}

service AnInterface {
    void ping (),
    bool aMethod (1: i16 argShort, 2: i64 argLong),
    oneway void aOnewayMethod (1: SomeMessage message)
}

service AnotherInterface extends AnInterface {
    void yetAnotherMethod (1: SomeMessage message) throws (1: SomeException ex)
}
  • Namespaces per language

  • A spectrum of basic types

  • Containers for other types

  • Fields can be optional

  • Explicit field and argument identifiers for versioning

  • Methods can be oneway

  • Methods can throw exceptions

2.20.2. C++ Server Code Basics

C++ Server Implementation

Method Implementation. 

class ExampleHandler : virtual public ExampleIf {
    void printString (const std::string &text) override {

        // Method implementation goes here ...

    }
    ...
}

Server Initialization. 

// Handler is the user defined implementation.
std::shared_ptr<ExampleHandler> handler (new ExampleHandler ());

// Processor is responsible for decoding function arguments and invoking the handler.
std::shared_ptr<ExampleProcessor> processor (new ExampleProcessor (handler));

// Transport provides reading and writing of byte buffers.
std::shared_ptr<TServerTransport> transport (new TServerSocket (SERVER_PORT));

// Buffered transport is a wrapper for another transport object.
std::shared_ptr<TTransportFactory> transport_factory (new TBufferedTransportFactory ());

// Protocol provides reading and writing of individual types on top of transport.
std::shared_ptr<TProtocolFactory> protocol_factory (new TBinaryProtocolFactory ());

// New connections use their own transport and protocol instances hence the factories.
std::shared_ptr<TServer> server (new TSimpleServer (processor, transport, transport_factory, protocol_factory));

server->serve ();
  • Public read and write methods on generated transport types

  • Multiple transports available

    • Plain socket

    • Socket with SSL

    • Socket with HTTP

    • Socket with WebSocket

    • Wrapper for zlib compression

    • File and pipe

    • Memory buffer

  • Multiple protocols available

    • JSON

    • Simple binary encoding

    • Compact binary encoding

    • Wrapper for serving multiple services

  • Multiple servers available

    • Single main thread

    • Thread per connection

    • Thread pool with fixed size

    • Thread pool with fixed size and dedicated dispatcher

The file and memory buffer transports can be used for simple serialization without performing remote calls.

The simple binary encoding protocol stores primitive types in fixed length format with configurable byte ordering. Structure fields are stored with type and identifier also in fixed length format. Containers are similarly straightforward. See https://github.com/apache/thrift/blob/master/doc/specs/thrift-binary-protocol.md for details on the simple binary encoding protocol.

The compact binary encoding protocol stores primitive integer types in variable length format. Structure fields store identifiers as deltas where possible and use variable length format where not. Containers store type and size together for small enough sizes and use variable length format otherwise. See https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md for details on the compact binary encoding protocol.

Example Server Internals

This note looks at the example from https://github.com/d-iii-s/teaching-middleware/tree/master/src/thrift-basic-server in more detail, the code snippets were generated with Thrift 0.14. On the server side, the ExampleHandler class inherits from ExampleIf, a generated abstract class with the printString method that reflects the interface definition:

class ExampleIf {
    public:
        virtual ~ExampleIf () {}
        virtual void printString (const std::string& text) = 0;
};

The ExampleIf class is what the generated ExampleProcessor class calls to deliver method invocations:

void ExampleProcessor::process_printString (int32_t seqid, TProtocol* iprot, TProtocol* oprot, void* callContext) {

    ...

    Example_printString_args args;
    args.read (iprot);
    iprot->readMessageEnd ();
    uint32_t bytes = iprot->getTransport ()->readEnd ();

    ...

    Example_printString_result result;
    try {
        iface_->printString (args.text);
    } catch (const std::exception& e) {

        ...

        TApplicationException x (e.what ());
        oprot->writeMessageBegin ("printString", T_EXCEPTION, seqid);
        x.write (oprot);
        oprot->writeMessageEnd ();
        oprot->getTransport ()->writeEnd ();
        oprot->getTransport ()->flush ();
        return;
    }

    ...

    oprot->writeMessageBegin ("printString", T_REPLY, seqid);
    result.write (oprot);
    oprot->writeMessageEnd ();
    bytes = oprot->getTransport ()->writeEnd ();
    oprot->getTransport ()->flush ();

    ...
}


uint32_t Example_printString_args::read (TProtocol* iprot) {

    ...

    xfer += iprot->readStructBegin(fname);

    bool isset_text = false;

    while (true) {
        xfer += iprot->readFieldBegin (fname, ftype, fid);
        if (ftype == T_STOP) {
            break;
        }
        switch (fid) {
            case 1:
                if (ftype == T_STRING) {
                    xfer += iprot->readString (this->text);
                    isset_text = true;
                } else {
                    xfer += iprot->skip (ftype);
                }
                break;
            default:
                xfer += iprot->skip(ftype);
                break;
        }
        xfer += iprot->readFieldEnd ();
    }

    xfer += iprot->readStructEnd ();

    if (!isset_text) throw TProtocolException (INVALID_DATA);

    return xfer;
}

2.20.3. C++ Client Code Basics

C++ Client Implementation

Client Initialization. 

// Transport provides reading and writing of byte buffers.
std::shared_ptr<TTransport> socket (new TSocket (SERVER_ADDR, SERVER_PORT));

// Buffered transport is a wrapper for another transport object.
std::shared_ptr<TTransport> transport (new TBufferedTransport (socket));

// Protocol provides reading and writing of individual types on top of transport.
std::shared_ptr<TProtocol> protocol (new TBinaryProtocol (transport));

Method Call. 

std::shared_ptr<ExampleClient> client (new ExampleClient (protocol));
client->printString ("Hello from Thrift in C++ !");

Example Client Internals

This note looks at the example from https://github.com/d-iii-s/teaching-middleware/tree/master/src/thrift-basic-server in more detail, the code snippets were generated with Thrift 0.14. On the client side, the ExampleClient class is a generated stub class with the printString method responsible for the remote method invocation:

class ExampleClient : virtual public ExampleIf {
    public:
        void printString (const std::string& text);
        void send_printString (const std::string& text);
        void recv_printString ();

    ...
};


void ExampleClient::send_printString (const std::string& text) {

    int32_t cseqid = 0;
    oprot_->writeMessageBegin ("printString", T_CALL, cseqid);

    Example_printString_pargs args;
    args.text = &text;
    args.write (oprot_);

    oprot_->writeMessageEnd ();
    oprot_->getTransport ()->writeEnd ();
    oprot_->getTransport ()->flush ();
}


void ExampleClient::recv_printString () {

    ...

    iprot_->readMessageBegin (fname, mtype, rseqid);

    if (mtype == T_EXCEPTION) {
        TApplicationException x;
        x.read (iprot_);
        iprot_->readMessageEnd ();
        iprot_->getTransport ()->readEnd ();
        throw x;
    }
    if (mtype != T_REPLY) {
        iprot_->skip (T_STRUCT);
        iprot_->readMessageEnd ();
        iprot_->getTransport ()->readEnd ();
    }
    if (fname.compare ("printString") != 0) {
        iprot_->skip (T_STRUCT);
        iprot_->readMessageEnd ();
        iprot_->getTransport ()->readEnd ();
    }

    Example_printString_presult result;
    result.read (iprot_);
    iprot_->readMessageEnd ();
    iprot_->getTransport ()->readEnd ();

    return;
}


int32_t Example_printString_args::write (TProtocol* oprot) const {

    ...

    xfer += oprot->writeStructBegin ("Example_printString_args");

    xfer += oprot->writeFieldBegin ("text", T_STRING, 1);
    xfer += oprot->writeString (this->text);
    xfer += oprot->writeFieldEnd ();

    xfer += oprot->writeFieldStop ();
    xfer += oprot->writeStructEnd ();

    return xfer;
}

2.20.4. References

  1. The Apache Thrift Project Home Page. https://thrift.apache.org