[jsonrpc-glib] server: add handler convenience API



commit b2c27da85ce591dc12c0a64b859344a72a750127
Author: Christian Hergert <chergert redhat com>
Date:   Sat Jun 17 21:46:39 2017 -0700

    server: add handler convenience API
    
    Rather than requiring that all handlers be dispatched via connecting to
    the handle-call signal, this allows a simplier API to register handlers
    and remove them as necessary.
    
    To keep things simple, we just use a sorted GArray of handler data and
    bsearch to locate the proper handler upon dispatch. For jsonrpc, this
    is likely better than a tree of nodes that we parse parameters from
    because methods generally don't have parseable user data like a HTTP url
    might have.

 src/jsonrpc-server.c |  143 +++++++++++++++++++++++++++++++++++++++++++++++++-
 src/jsonrpc-server.h |   18 ++++++-
 tests/test-server.c  |   60 +++++++++++++++++++--
 3 files changed, 213 insertions(+), 8 deletions(-)
---
diff --git a/src/jsonrpc-server.c b/src/jsonrpc-server.c
index f8697e0..a993ac4 100644
--- a/src/jsonrpc-server.c
+++ b/src/jsonrpc-server.c
@@ -18,6 +18,8 @@
 
 #define G_LOG_DOMAIN "jsonrpc-server"
 
+#include <stdlib.h>
+
 #include "jsonrpc-input-stream.h"
 #include "jsonrpc-output-stream.h"
 #include "jsonrpc-server.h"
@@ -35,8 +37,19 @@
 typedef struct
 {
   GHashTable *clients;
+  GArray     *handlers;
+  guint       last_handler_id;
 } JsonrpcServerPrivate;
 
+typedef struct
+{
+  const gchar          *method;
+  JsonrpcServerHandler  handler;
+  gpointer              handler_data;
+  GDestroyNotify        handler_data_destroy;
+  guint                 handler_id;
+} JsonrpcServerHandlerData;
+
 G_DEFINE_TYPE_WITH_PRIVATE (JsonrpcServer, jsonrpc_server, G_TYPE_OBJECT)
 
 enum {
@@ -48,12 +61,58 @@ enum {
 static guint signals [N_SIGNALS];
 
 static void
+jsonrpc_server_clear_handler_data (JsonrpcServerHandlerData *data)
+{
+  if (data->handler_data_destroy)
+    data->handler_data_destroy (data->handler_data);
+}
+
+static gint
+locate_handler_by_method (const void *key,
+                          const void *element)
+{
+  const gchar *method = key;
+  const JsonrpcServerHandlerData *data = element;
+
+  return g_strcmp0 (method, data->method);
+}
+
+static gboolean
+jsonrpc_server_real_handle_call (JsonrpcServer *self,
+                                 JsonrpcClient *client,
+                                 const gchar   *method,
+                                 GVariant      *id,
+                                 GVariant      *params)
+{
+  JsonrpcServerPrivate *priv = jsonrpc_server_get_instance_private (self);
+  JsonrpcServerHandlerData *data;
+
+  g_assert (JSONRPC_IS_SERVER (self));
+  g_assert (JSONRPC_IS_CLIENT (client));
+  g_assert (method != NULL);
+  g_assert (id != NULL);
+
+  data = bsearch (method, (gpointer)priv->handlers->data,
+                  priv->handlers->len, sizeof (JsonrpcServerHandlerData),
+                  locate_handler_by_method);
+
+  if (data != NULL)
+    {
+      data->handler (self, client, method, id, params, data->handler_data);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
 jsonrpc_server_finalize (GObject *object)
 {
   JsonrpcServer *self = (JsonrpcServer *)object;
   JsonrpcServerPrivate *priv = jsonrpc_server_get_instance_private (self);
 
   g_clear_pointer (&priv->clients, g_hash_table_unref);
+  g_clear_pointer (&priv->handlers, g_array_unref);
 
   G_OBJECT_CLASS (jsonrpc_server_parent_class)->finalize (object);
 }
@@ -65,12 +124,14 @@ jsonrpc_server_class_init (JsonrpcServerClass *klass)
 
   object_class->finalize = jsonrpc_server_finalize;
 
+  klass->handle_call = jsonrpc_server_real_handle_call;
+
   signals [HANDLE_CALL] =
     g_signal_new ("handle-call",
                   G_TYPE_FROM_CLASS (klass),
                   G_SIGNAL_RUN_LAST,
                   G_STRUCT_OFFSET (JsonrpcServerClass, handle_call),
-                  NULL, NULL, NULL,
+                  g_signal_accumulator_true_handled, NULL, NULL,
                   G_TYPE_BOOLEAN,
                   4,
                   JSONRPC_TYPE_CLIENT,
@@ -97,6 +158,9 @@ jsonrpc_server_init (JsonrpcServer *self)
   JsonrpcServerPrivate *priv = jsonrpc_server_get_instance_private (self);
 
   priv->clients = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+
+  priv->handlers = g_array_new (FALSE, FALSE, sizeof (JsonrpcServerHandlerData));
+  g_array_set_clear_func (priv->handlers, (GDestroyNotify)jsonrpc_server_clear_handler_data);
 }
 
 JsonrpcServer *
@@ -167,3 +231,80 @@ jsonrpc_server_accept_io_stream (JsonrpcServer *self,
 
   jsonrpc_client_start_listening (client);
 }
+
+static gint
+sort_by_method (gconstpointer a,
+                gconstpointer b)
+{
+  const JsonrpcServerHandlerData *data_a = a;
+  const JsonrpcServerHandlerData *data_b = b;
+
+  return g_strcmp0 (data_a->method, data_b->method);
+}
+
+/**
+ * jsonrpc_server_add_handler:
+ * @self: A #JsonrpcServer
+ * @method: A method to handle
+ * @handler: (closure handler_data) (destroy handler_data_destroy): A handler to
+ *   execute when an incoming method matches @methods
+ * @handler_data: user data for @handler
+ * @handler_data_destroy: a destroy callback for @handler_data
+ *
+ * Adds a new handler that will be dispatched when a matching @method arrives.
+ *
+ * Returns: A handler id that can be used to remove the handler with
+ *   jsonrpc_server_remove_handler().
+ */
+guint
+jsonrpc_server_add_handler (JsonrpcServer        *self,
+                            const gchar          *method,
+                            JsonrpcServerHandler  handler,
+                            gpointer              handler_data,
+                            GDestroyNotify        handler_data_destroy)
+{
+  JsonrpcServerPrivate *priv = jsonrpc_server_get_instance_private (self);
+  JsonrpcServerHandlerData data;
+
+  g_return_val_if_fail (JSONRPC_IS_SERVER (self), 0);
+  g_return_val_if_fail (handler != NULL, 0);
+
+  data.method = g_intern_string (method);
+  data.handler = handler;
+  data.handler_data = handler_data;
+  data.handler_data_destroy = handler_data_destroy;
+  data.handler_id = ++priv->last_handler_id;
+
+  g_array_append_val (priv->handlers, data);
+  g_array_sort (priv->handlers, sort_by_method);
+
+  return data.handler_id;
+}
+
+/**
+ * jsonrpc_server_remove_handler:
+ * @self: a #JsonrpcServer
+ * @handler_id: a handler returned from jsonrpc_server_add_handler()
+ *
+ * Removes a handler that was previously registered with jsonrpc_server_add_handler().
+ */
+void
+jsonrpc_server_remove_handler (JsonrpcServer *self,
+                               guint          handler_id)
+{
+  JsonrpcServerPrivate *priv = jsonrpc_server_get_instance_private (self);
+
+  g_return_if_fail (JSONRPC_IS_SERVER (self));
+  g_return_if_fail (handler_id != 0);
+
+  for (guint i = 0; i < priv->handlers->len; i++)
+    {
+      const JsonrpcServerHandlerData *data = &g_array_index (priv->handlers, JsonrpcServerHandlerData, i);
+
+      if (data->handler_id == handler_id)
+        {
+          g_array_remove_index (priv->handlers, i);
+          break;
+        }
+    }
+}
diff --git a/src/jsonrpc-server.h b/src/jsonrpc-server.h
index d5ba8a0..e18a3e9 100644
--- a/src/jsonrpc-server.h
+++ b/src/jsonrpc-server.h
@@ -53,9 +53,23 @@ struct _JsonrpcServerClass
   gpointer _reserved8;
 };
 
+typedef void (*JsonrpcServerHandler) (JsonrpcServer *self,
+                                      JsonrpcClient *client,
+                                      const gchar   *method,
+                                      GVariant      *id,
+                                      GVariant      *params,
+                                      gpointer       user_data);
+
 JsonrpcServer *jsonrpc_server_new              (void);
-void           jsonrpc_server_accept_io_stream (JsonrpcServer *self,
-                                                GIOStream     *stream);
+void           jsonrpc_server_accept_io_stream (JsonrpcServer        *self,
+                                                GIOStream            *stream);
+guint          jsonrpc_server_add_handler      (JsonrpcServer        *self,
+                                                const gchar          *method,
+                                                JsonrpcServerHandler  handler,
+                                                gpointer              handler_data,
+                                                GDestroyNotify        handler_data_destroy);
+void           jsonrpc_server_remove_handler   (JsonrpcServer        *self,
+                                                guint                 handler_id);
 
 G_END_DECLS
 
diff --git a/tests/test-server.c b/tests/test-server.c
index 92711c8..a4bfc3d 100644
--- a/tests/test-server.c
+++ b/tests/test-server.c
@@ -55,7 +55,7 @@ handle_call (JsonrpcServer *server,
 {
   const gchar *rootPath = NULL;
   g_autoptr(GError) error = NULL;
-  g_auto(GVariantDict) dict = { 0 };
+  g_auto(GVariantDict) dict = {{{0}}};
   gboolean r;
 
   g_assert (id != NULL);
@@ -82,6 +82,30 @@ handle_call (JsonrpcServer *server,
 }
 
 static void
+do_something_handler (JsonrpcServer *server,
+                      JsonrpcClient *client,
+                      const gchar   *method,
+                      GVariant      *id,
+                      GVariant      *params,
+                      gpointer       user_data)
+{
+  g_autoptr(GError) error = NULL;
+  gint *count = user_data;
+  gboolean r;
+
+  g_assert_cmpstr (method, ==, "do/something");
+
+  (*count)++;
+
+  g_assert (g_variant_is_of_type (params, G_VARIANT_TYPE_STRING));
+  g_assert_cmpstr (g_variant_get_string (params, NULL), ==, "do/something/message");
+
+  r = jsonrpc_client_reply (client, id, g_variant_new_boolean (TRUE), NULL, &error);
+  g_assert_no_error (error);
+  g_assert (r);
+}
+
+static void
 test_basic (gboolean use_gvariant)
 {
   g_autoptr(JsonrpcServer) server = NULL;
@@ -96,6 +120,8 @@ test_basic (gboolean use_gvariant)
   g_autoptr(GVariant) return_value = NULL;
   g_autoptr(GError) error = NULL;
   GVariantDict dict;
+  guint handler_id;
+  gulong handle_call_id;
   gint pair_a[2];
   gint pair_b[2];
   gint count = 0;
@@ -125,10 +151,10 @@ test_basic (gboolean use_gvariant)
   server = jsonrpc_server_new ();
   jsonrpc_server_accept_io_stream (server, stream_b);
 
-  g_signal_connect (server,
-                    "handle-call",
-                    G_CALLBACK (handle_call),
-                    NULL);
+  handle_call_id = g_signal_connect (server,
+                                     "handle-call",
+                                     G_CALLBACK (handle_call),
+                                     NULL);
 
   g_signal_connect (server,
                     "notification",
@@ -154,6 +180,30 @@ test_basic (gboolean use_gvariant)
   g_assert (return_value != NULL);
 
   g_assert_cmpint (count, ==, 1);
+
+  g_signal_handler_disconnect (server, handle_call_id);
+
+  handler_id = jsonrpc_server_add_handler (server,
+                                           "do/something",
+                                           do_something_handler,
+                                           &count,
+                                           NULL);
+
+  message = g_variant_new_string ("do/something/message");
+
+  r = jsonrpc_client_call (client,
+                           "do/something",
+                           g_steal_pointer (&message),
+                           NULL,
+                           &return_value,
+                           &error);
+  g_assert_no_error (error);
+  g_assert_cmpint (r, ==, TRUE);
+  g_assert (return_value != NULL);
+
+  g_assert_cmpint (count, ==, 2);
+
+  jsonrpc_server_remove_handler (server, handler_id);
 }
 
 static void


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]