mirror of
https://github.com/valitydev/thrift.git
synced 2024-11-07 02:45:22 +00:00
c76a9ecdd2
Client: c_glib Patch: Simon South <ssouth@simonsouth.com> This closes #574
528 lines
18 KiB
C
528 lines
18 KiB
C
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
#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;
|
|
}
|