c_glib Tutorial

Introduction

All Apache Thrift tutorials require that you have:

  1. The Apache Thrift Compiler and Libraries, see Download and Building from Source for more details.
  2. Generated the tutorial.thrift and shared.thrift files:
    thrift -r --gen c_glib tutorial.thrift
  3. Followed all prerequisites listed below.

Client

#include <stdio.h>
#include <glib-object.h>

#include <thrift/c_glib/protocol/thrift_binary_protocol.h>
#include <thrift/c_glib/transport/thrift_buffered_transport.h>
#include <thrift/c_glib/transport/thrift_socket.h>

#include "gen-c_glib/calculator.h"

int main (void)
{
  ThriftSocket *socket;
  ThriftTransport *transport;
  ThriftProtocol *protocol;
  CalculatorIf *client;

  GError *error = NULL;
  InvalidOperation *invalid_operation = NULL;

  Work *work;

  gint32 sum;
  gint32 diff;

  int exit_status = 0;

#if (!GLIB_CHECK_VERSION (2, 36, 0))
  g_type_init ();
#endif

  socket    = g_object_new (THRIFT_TYPE_SOCKET,
                            "hostname",  "localhost",
                            "port",      9090,
                            NULL);
  transport = g_object_new (THRIFT_TYPE_BUFFERED_TRANSPORT,
                            "transport", socket,
                            NULL);
  protocol  = g_object_new (THRIFT_TYPE_BINARY_PROTOCOL,
                            "transport", transport,
                            NULL);

  thrift_transport_open (transport, &error);


  /* In the C (GLib) implementation of Thrift, service methods on the
     server are accessed via a generated client class that implements
     the service interface. In this tutorial, we access a Calculator
     service through an instance of CalculatorClient, which implements
     CalculatorIf. */
  client = g_object_new (TYPE_CALCULATOR_CLIENT,
                         "input_protocol",  protocol,
                         "output_protocol", protocol,
                         NULL);

  /* Each of the client methods requires at least two parameters: A
     pointer to the client-interface implementation (the client
     object), and a handle to a GError structure to receive
     information about any error that occurs.

     On success, client methods return TRUE. A return value of FALSE
     indicates an error occurred and the error parameter has been
     set. */
  if (!error && calculator_if_ping (client, &error)) {
    puts ("ping()");
  }

  /* Service methods that return a value do so by passing the result
     back via an output parameter (here, "sum"). */
  if (!error && calculator_if_add (client, &sum, 1, 1, &error)) {
    printf ("1+1=%d\n", sum);
  }

  /* Thrift structs are implemented as GObjects, with each of the
     struct's members exposed as an object property. */
  work = g_object_new (TYPE_WORK, NULL);

  if (!error) {
    g_object_set (work,
                  "num1", 1,
                  "num2", 0,
                  "op",   OPERATION_DIVIDE,
                  NULL);

    /* Exceptions are passed back from service methods in a manner
       similar to return values. */
    if (calculator_if_calculate (client,
                                 NULL,
                                 1,
                                 work,
                                 &invalid_operation,
                                 &error)) {
      puts ("Whoa? We can divide by zero!");
    }
    else {
      if (invalid_operation) {
        gchar *why;

        /* Like structs, exceptions are implemented as objects with
           properties. */
        g_object_get (invalid_operation, "why", &why, NULL);

        printf ("InvalidOperation: %s\n", why);

        if (why != NULL)
          g_free (why);
        g_object_unref (invalid_operation);
        invalid_operation = NULL;
      }

      g_clear_error (&error);
    }
  }

  if (!error) {
    /* Struct objects can be reused across method invocations. */
    g_object_set (work,
                  "num1", 15,
                  "num2", 10,
                  "op",   OPERATION_SUBTRACT,
                  NULL);

    if (calculator_if_calculate (client,
                                 &diff,
                                 1,
                                 work,
                                 &invalid_operation,
                                 &error)) {
      printf ("15-10=%d\n", diff);
    }
  }

  g_object_unref (work);

  if (!error) {
    SharedStruct *shared_struct;
    gchar *value;

    shared_struct = g_object_new (TYPE_SHARED_STRUCT, NULL);

    /* As defined in the Thrift file, the Calculator service extends
       the SharedService service. Correspondingly, in the generated
       code CalculatorIf inherits from SharedServiceIf, and the parent
       service's methods are accessible through a simple cast. */
    if (shared_service_client_get_struct (SHARED_SERVICE_IF (client),
                                          &shared_struct,
                                          1,
                                          &error)) {
      g_object_get (shared_struct, "value", &value, NULL);
      printf ("Check log: %s\n", value);
      g_free (value);
    }

    g_object_unref (shared_struct);
  }

  if (error) {
    printf ("ERROR: %s\n", error->message);
    g_clear_error (&error);

    exit_status = 1;
  }

  thrift_transport_close (transport, NULL);

  g_object_unref (client);
  g_object_unref (protocol);
  g_object_unref (transport);
  g_object_unref (socket);

  return exit_status;
}

Server

#include <glib-object.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>

#include <thrift/c_glib/thrift.h>
#include <thrift/c_glib/protocol/thrift_binary_protocol_factory.h>
#include <thrift/c_glib/protocol/thrift_protocol_factory.h>
#include <thrift/c_glib/server/thrift_server.h>
#include <thrift/c_glib/server/thrift_simple_server.h>
#include <thrift/c_glib/transport/thrift_buffered_transport_factory.h>
#include <thrift/c_glib/transport/thrift_server_socket.h>
#include <thrift/c_glib/transport/thrift_server_transport.h>

#include "gen-c_glib/calculator.h"

G_BEGIN_DECLS

/* In the C (GLib) implementation of Thrift, the actual work done by a
   server---that is, the code that runs when a client invokes a
   service method---is defined in a separate "handler" class that
   implements the service interface. Here we define the
   TutorialCalculatorHandler class, which implements the CalculatorIf
   interface and provides the behavior expected by tutorial clients.
   (Typically this code would be placed in its own module but for
   clarity this tutorial is presented entirely in a single file.)

   For each service the Thrift compiler generates an abstract base
   class from which handler implementations should inherit. In our
   case TutorialCalculatorHandler inherits from CalculatorHandler,
   defined in gen-c_glib/calculator.h.

   If you're new to GObject, try not to be intimidated by the quantity
   of code here---much of it is boilerplate and can mostly be
   copied-and-pasted from existing work. For more information refer to
   the GObject Reference Manual, available online at
   https://developer.gnome.org/gobject/. */

#define TYPE_TUTORIAL_CALCULATOR_HANDLER \
  (tutorial_calculator_handler_get_type ())

#define TUTORIAL_CALCULATOR_HANDLER(obj)                                \
  (G_TYPE_CHECK_INSTANCE_CAST ((obj),                                   \
                               TYPE_TUTORIAL_CALCULATOR_HANDLER,        \
                               TutorialCalculatorHandler))
#define TUTORIAL_CALCULATOR_HANDLER_CLASS(c)                    \
  (G_TYPE_CHECK_CLASS_CAST ((c),                                \
                            TYPE_TUTORIAL_CALCULATOR_HANDLER,   \
                            TutorialCalculatorHandlerClass))
#define IS_TUTORIAL_CALCULATOR_HANDLER(obj)                             \
  (G_TYPE_CHECK_INSTANCE_TYPE ((obj),                                   \
                               TYPE_TUTORIAL_CALCULATOR_HANDLER))
#define IS_TUTORIAL_CALCULATOR_HANDLER_CLASS(c)                 \
  (G_TYPE_CHECK_CLASS_TYPE ((c),                                \
                            TYPE_TUTORIAL_CALCULATOR_HANDLER))
#define TUTORIAL_CALCULATOR_HANDLER_GET_CLASS(obj)              \
  (G_TYPE_INSTANCE_GET_CLASS ((obj),                            \
                              TYPE_TUTORIAL_CALCULATOR_HANDLER, \
                              TutorialCalculatorHandlerClass))

struct _TutorialCalculatorHandler {
  CalculatorHandler parent_instance;

  /* private */
  GHashTable *log;
};
typedef struct _TutorialCalculatorHandler TutorialCalculatorHandler;

struct _TutorialCalculatorHandlerClass {
  CalculatorHandlerClass parent_class;
};
typedef struct _TutorialCalculatorHandlerClass TutorialCalculatorHandlerClass;

GType tutorial_calculator_handler_get_type (void);

G_END_DECLS

/* ---------------------------------------------------------------- */

/* The implementation of TutorialCalculatorHandler follows. */

G_DEFINE_TYPE (TutorialCalculatorHandler,
               tutorial_calculator_handler,
               TYPE_CALCULATOR_HANDLER)

/* Each of a handler's methods accepts at least two parameters: A
   pointer to the service-interface implementation (the handler object
   itself) and a handle to a GError structure to receive information
   about any error that occurs.

   On success, a handler method returns TRUE. A return value of FALSE
   indicates an error occurred and the error parameter has been
   set. (Methods should not return FALSE without first setting the
   error parameter.) */
static gboolean
tutorial_calculator_handler_ping (CalculatorIf  *iface,
                                  GError       **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  puts ("ping()");

  return TRUE;
}

/* Service-method parameters are passed through as parameters to the
   handler method.

   If the service method returns a value an output parameter, _return,
   is additionally passed to the handler method. This parameter should
   be set appropriately before the method returns, whenever it
   succeeds.

   The return value from this method happens to be of a base type,
   i32, but note if a method returns a complex type such as a map or
   list *_return will point to a pre-allocated data structure that
   does not need to be re-allocated and should not be destroyed. */
static gboolean
tutorial_calculator_handler_add (CalculatorIf  *iface,
                                 gint32        *_return,
                                 const gint32   num1,
                                 const gint32   num2,
                                 GError       **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  printf ("add(%d,%d)\n", num1, num2);
  *_return = num1 + num2;

  return TRUE;
}

/* Any handler method can return a ThriftApplicationException to the
   client by setting its error parameter appropriately and returning
   FALSE. See the ThriftApplicationExceptionError enumeration defined
   in thrift_application_exception.h for a list of recognized
   exception types (GError codes).

   If a service method can also throw a custom exception (that is, one
   defined in the .thrift file) an additional output parameter will be
   provided (here, "ouch") to hold an instance of the exception, when
   necessary. Note there will be a separate parameter added for each
   type of exception the method can throw.

   Unlike return values, exception objects are never pre-created; this
   is always the responsibility of the handler method. */
static gboolean
tutorial_calculator_handler_calculate (CalculatorIf      *iface,
                                       gint32            *_return,
                                       const gint32       logid,
                                       const Work        *w,
                                       InvalidOperation **ouch,
                                       GError           **error)
{
  TutorialCalculatorHandler *self;

  gint *log_key;
  gchar log_value[12];
  SharedStruct *log_struct;

  gint num1;
  gint num2;
  Operation op;
  gboolean result = TRUE;

  THRIFT_UNUSED_VAR (error);

  g_return_val_if_fail (IS_TUTORIAL_CALCULATOR_HANDLER (iface),
                        FALSE);
  self = TUTORIAL_CALCULATOR_HANDLER (iface);

  /* Remember: Exception objects are never pre-created */
  g_assert (*ouch == NULL);

  /* Fetch the contents of our Work parameter.

     Note that integer properties of thirty-two bits or fewer in width
     are _always_ of type gint, regardless of the range of values they
     hold. A common error is trying to retrieve, say, a structure
     member defined in the .thrift file as type i16 into a variable of
     type gint16, which will clobber variables adjacent on the
     stack. Remember: If you're retrieving an integer property the
     receiving variable must be of either type gint or gint64, as
     appropriate. */
  g_object_get ((Work *)w,
                "num1", &num1,
                "num2", &num2,
                "op",   &op,
                NULL);

  printf ("calculate(%d,{%d,%d,%d})\n", logid, op, num1, num2);

  switch (op) {
  case OPERATION_ADD:
    *_return = num1 + num2;
    break;

  case OPERATION_SUBTRACT:
    *_return = num1 - num2;
    break;

  case OPERATION_MULTIPLY:
    *_return = num1 * num2;
    break;

  case OPERATION_DIVIDE:
    if (num2 == 0) {
      /* For each custom exception type a subclass of ThriftStruct is
         generated by the Thrift compiler. Throw an exception by
         setting the corresponding output parameter to a new instance
         of its type and returning FALSE. */
      *ouch = g_object_new (TYPE_INVALID_OPERATION,
                            "whatOp", op,
                            "why",  g_strdup ("Cannot divide by 0"),
                            NULL);
      result = FALSE;

      /* Note the call to g_strdup above: All the memory used by a
         ThriftStruct's properties belongs to the object itself and
         will be freed on destruction. Removing this call to g_strdup
         will lead to a segmentation fault as the object tries to
         release memory allocated statically to the program. */
    }
    else {
      *_return = num1 / num2;
    }
    break;

  default:
    *ouch = g_object_new (TYPE_INVALID_OPERATION,
                          "whatOp", op,
                          "why",  g_strdup ("Invalid Operation"),
                          NULL);
    result = FALSE;
  }

  /* On success, log a record of the result to our hash table */
  if (result) {
    log_key = g_malloc (sizeof *log_key);
    *log_key = logid;

    snprintf (log_value, sizeof log_value, "%d", *_return);

    log_struct = g_object_new (TYPE_SHARED_STRUCT,
                               "key",   *log_key,
                               "value",  g_strdup (log_value),
                               NULL);
    g_hash_table_replace (self->log, log_key, log_struct);
  }

  return result;
}

/* A one-way method has the same signature as an equivalent, regular
   method that returns no value. */
static gboolean
tutorial_calculator_handler_zip (CalculatorIf  *iface,
                                 GError       **error)
{
  THRIFT_UNUSED_VAR (iface);
  THRIFT_UNUSED_VAR (error);

  puts ("zip()");

  return TRUE;
}

/* As specified in the .thrift file (tutorial.thrift), the Calculator
   service extends the SharedService service. Correspondingly, in the
   generated code the Calculator interface, CalculatorIf, extends the
   SharedService interface, SharedServiceIf, and subclasses of
   CalculatorHandler should implement its methods as well.

   Here we provide an implementation for the getStruct method from the
   parent service. */
static gboolean
tutorial_calculator_handler_get_struct (SharedServiceIf  *iface,
                                        SharedStruct    **_return,
                                        const gint32      key32,
                                        GError          **error)
{
  gint key = (gint)key32;
  TutorialCalculatorHandler *self;
  SharedStruct *log_struct;
  gint log_key;
  gchar *log_value;

  THRIFT_UNUSED_VAR (error);

  g_return_val_if_fail (IS_TUTORIAL_CALCULATOR_HANDLER (iface),
                        FALSE);
  self = TUTORIAL_CALCULATOR_HANDLER (iface);

  /* Remember: Complex return types are always pre-created and need
     only be populated */
  g_assert (*_return != NULL);

  printf ("getStruct(%d)\n", key);

  /* If the key exists in our log, return the corresponding logged
     data (or an empty SharedStruct structure if it does not).

     Incidentally, note we _must_ here copy the values from the hash
     table into the return structure. All memory used by the return
     structure belongs to the structure itself and will be freed once
     a response is sent to the client. If we merely freed *_return and
     set it to point to our hash-table entry, that would mean memory
     would be released (effectively, data erased) out of the hash
     table! */
  log_struct = g_hash_table_lookup (self->log, &key);
  if (log_struct != NULL) {
    g_object_get (log_struct,
                  "key",   &log_key,
                  "value", &log_value,
                  NULL);
    g_object_set (*_return,
                  "key",   log_key,
                  "value", g_strdup (log_value),
                  NULL);
  }

  return TRUE;
}

/* TutorialCalculatorHandler's instance finalizer (destructor) */
static void
tutorial_calculator_handler_finalize (GObject *object)
{
  TutorialCalculatorHandler *self =
    TUTORIAL_CALCULATOR_HANDLER (object);

  /* Free our calculation-log hash table */
  g_hash_table_unref (self->log);
  self->log = NULL;

  /* Chain up to the parent class */
  G_OBJECT_CLASS (tutorial_calculator_handler_parent_class)->
    finalize (object);
}

/* TutorialCalculatorHandler's instance initializer (constructor) */
static void
tutorial_calculator_handler_init (TutorialCalculatorHandler *self)
{
  /* Create our calculation-log hash table */
  self->log = g_hash_table_new_full (g_int_hash,
                                     g_int_equal,
                                     g_free,
                                     g_object_unref);
}

/* TutorialCalculatorHandler's class initializer */
static void
tutorial_calculator_handler_class_init (TutorialCalculatorHandlerClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  SharedServiceHandlerClass *shared_service_handler_class =
    SHARED_SERVICE_HANDLER_CLASS (klass);
  CalculatorHandlerClass *calculator_handler_class =
    CALCULATOR_HANDLER_CLASS (klass);

  /* Register our destructor */
  gobject_class->finalize = tutorial_calculator_handler_finalize;

  /* Register our implementations of CalculatorHandler's methods */
  calculator_handler_class->ping =
    tutorial_calculator_handler_ping;
  calculator_handler_class->add =
    tutorial_calculator_handler_add;
  calculator_handler_class->calculate =
    tutorial_calculator_handler_calculate;
  calculator_handler_class->zip =
    tutorial_calculator_handler_zip;

  /* Register our implementation of SharedServiceHandler's method */
  shared_service_handler_class->get_struct =
    tutorial_calculator_handler_get_struct;
}

/* ---------------------------------------------------------------- */

/* That ends the implementation of TutorialCalculatorHandler.
   Everything below is fairly generic code that sets up a minimal
   Thrift server for tutorial clients. */


/* Our server object, declared globally so it is accessible within the
   SIGINT signal handler */
ThriftServer *server = NULL;

/* A flag that indicates whether the server was interrupted with
   SIGINT (i.e. Ctrl-C) so we can tell whether its termination was
   abnormal */
gboolean sigint_received = FALSE;

/* Handle SIGINT ("Ctrl-C") signals by gracefully stopping the
   server */
static void
sigint_handler (int signal_number)
{
  THRIFT_UNUSED_VAR (signal_number);

  /* Take note we were called */
  sigint_received = TRUE;

  /* Shut down the server gracefully */
  if (server != NULL)
    thrift_server_stop (server);
}

int main (void)
{
  TutorialCalculatorHandler *handler;
  CalculatorProcessor *processor;

  ThriftServerTransport *server_transport;
  ThriftTransportFactory *transport_factory;
  ThriftProtocolFactory *protocol_factory;

  struct sigaction sigint_action;

  GError *error = NULL;
  int exit_status = 0;

#if (!GLIB_CHECK_VERSION (2, 36, 0))
  g_type_init ();
#endif

  /* Create an instance of our handler, which provides the service's
     methods' implementation */
  handler =
    g_object_new (TYPE_TUTORIAL_CALCULATOR_HANDLER,
                  NULL);

  /* Create an instance of the service's processor, automatically
     generated by the Thrift compiler, which parses incoming messages
     and dispatches them to the appropriate method in the handler */
  processor =
    g_object_new (TYPE_CALCULATOR_PROCESSOR,
                  "handler", handler,
                  NULL);

  /* Create our server socket, which binds to the specified port and
     listens for client connections */
  server_transport =
    g_object_new (THRIFT_TYPE_SERVER_SOCKET,
                  "port", 9090,
                  NULL);

  /* Create our transport factory, used by the server to wrap "raw"
     incoming connections from the client (in this case with a
     ThriftBufferedTransport to improve performance) */
  transport_factory =
    g_object_new (THRIFT_TYPE_BUFFERED_TRANSPORT_FACTORY,
                  NULL);

  /* Create our protocol factory, which determines which wire protocol
     the server will use (in this case, Thrift's binary protocol) */
  protocol_factory =
    g_object_new (THRIFT_TYPE_BINARY_PROTOCOL_FACTORY,
                  NULL);

  /* Create the server itself */
  server =
    g_object_new (THRIFT_TYPE_SIMPLE_SERVER,
                  "processor",                processor,
                  "server_transport",         server_transport,
                  "input_transport_factory",  transport_factory,
                  "output_transport_factory", transport_factory,
                  "input_protocol_factory",   protocol_factory,
                  "output_protocol_factory",  protocol_factory,
                  NULL);

  /* Install our SIGINT handler, which handles Ctrl-C being pressed by
     stopping the server gracefully (not strictly necessary, but a
     nice touch) */
  memset (&sigint_action, 0, sizeof (sigint_action));
  sigint_action.sa_handler = sigint_handler;
  sigint_action.sa_flags = SA_RESETHAND;
  sigaction (SIGINT, &sigint_action, NULL);

  /* Start the server, which will run until its stop method is invoked
     (from within the SIGINT handler, in this case) */
  puts ("Starting the server...");
  thrift_server_serve (server, &error);

  /* If the server stopped for any reason other than having been
     interrupted by the user, report the error */
  if (!sigint_received) {
    g_message ("thrift_server_serve: %s",
               error != NULL ? error->message : "(null)");
    g_clear_error (&error);
  }

  puts ("done.");

  g_object_unref (server);
  g_object_unref (transport_factory);
  g_object_unref (protocol_factory);
  g_object_unref (server_transport);

  g_object_unref (processor);
  g_object_unref (handler);

  return exit_status;
}