[jsonrpc-glib] server: add handler convenience API
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [jsonrpc-glib] server: add handler convenience API
- Date: Sun, 18 Jun 2017 04:46:55 +0000 (UTC)
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]