[gnome-builder] jsonrpc: import JsonrpcServer



commit fe04efc261d9f5e75158ad9fd4ad690df6c4591d
Author: Christian Hergert <chergert redhat com>
Date:   Mon Oct 31 22:43:33 2016 -0700

    jsonrpc: import JsonrpcServer
    
    This will allow us to start writing language server subprocesses to ease
    the moving of language features out of process.

 contrib/jsonrpc-glib/Makefile.am      |    2 +
 contrib/jsonrpc-glib/jsonrpc-client.c |  294 ++++++++++++++++++++++++++++++---
 contrib/jsonrpc-glib/jsonrpc-client.h |   25 +++-
 contrib/jsonrpc-glib/jsonrpc-glib.h   |    1 +
 contrib/jsonrpc-glib/jsonrpc-server.c |  159 ++++++++++++++++++
 contrib/jsonrpc-glib/jsonrpc-server.h |   63 +++++++
 6 files changed, 521 insertions(+), 23 deletions(-)
---
diff --git a/contrib/jsonrpc-glib/Makefile.am b/contrib/jsonrpc-glib/Makefile.am
index 67e931c..38b4ac7 100644
--- a/contrib/jsonrpc-glib/Makefile.am
+++ b/contrib/jsonrpc-glib/Makefile.am
@@ -10,6 +10,8 @@ libjsonrpc_glib_la_SOURCES = \
        jsonrpc-input-stream.h \
        jsonrpc-output-stream.c \
        jsonrpc-output-stream.h \
+       jsonrpc-server.c \
+       jsonrpc-server.h \
        jsonrpc-version.h \
        $(NULL)
 
diff --git a/contrib/jsonrpc-glib/jsonrpc-client.c b/contrib/jsonrpc-glib/jsonrpc-client.c
index 9cee8c0..9cb9dcb 100644
--- a/contrib/jsonrpc-glib/jsonrpc-client.c
+++ b/contrib/jsonrpc-glib/jsonrpc-client.c
@@ -133,6 +133,7 @@ enum {
 };
 
 enum {
+  HANDLE_CALL,
   NOTIFICATION,
   N_SIGNALS
 };
@@ -195,6 +196,29 @@ is_jsonrpc_result (JsonNode *node)
          json_object_has_member (object, "result");
 }
 
+
+/*
+ * Check to see if this looks like a proper method call for an RPC.
+ */
+static gboolean
+is_jsonrpc_call (JsonNode *node)
+{
+  JsonNode *id = NULL;
+  JsonNode *params = NULL;
+  const gchar *method = NULL;
+  gboolean success;
+
+  g_assert (JSON_NODE_HOLDS_OBJECT (node));
+
+  success = JCON_EXTRACT (node,
+    "id", JCONE_NODE (id),
+    "method", JCONE_STRING (method),
+    "params", JCONE_NODE (params)
+  );
+
+  return success && id != NULL && method != NULL && params != NULL;
+}
+
 /*
  * Try to unwrap the error and possibly set @id to the extracted RPC
  * request id.
@@ -397,6 +421,45 @@ jsonrpc_client_class_init (JsonrpcClientClass *klass)
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
 
+  /**
+   * JsonrpcClient::handle-call:
+   * @self: A #JsonrpcClient
+   * @method: the method name
+   * @id: The "id" field of the JSONRPC message
+   * @params: The "params" field of the JSONRPC message
+   *
+   * This signal is emitted when an RPC has been received from
+   * the peer we are connected to. Return %TRUE if you have handled
+   * this message, even asynchronously. If no handler has returned
+   * %TRUE an error will be synthesized.
+   *
+   * If you handle the message, you are responsible for replying to
+   * the peer in a timely manner using jsonrpc_client_reply() or
+   * jsonrpc_client_reply_async().
+   */
+  signals [HANDLE_CALL] =
+    g_signal_new ("handle-call",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (JsonrpcClientClass, handle_call),
+                  g_signal_accumulator_true_handled, NULL, NULL,
+                  G_TYPE_BOOLEAN,
+                  3,
+                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+                  JSON_TYPE_NODE,
+                  JSON_TYPE_NODE);
+
+  /**
+   * JsonrpcClient::notification:
+   * @self: A #JsonrpcClient
+   * @method: the method name of the notification
+   * @params: params for the notification
+   *
+   * This signal is emitted when a notification has been received
+   * from a peer. Unlike #JsonrpcClient::handle-call, this does
+   * not have an "id" parameter because notifications do not have
+   * ids. They do not round trip.
+   */
   signals [NOTIFICATION] =
     g_signal_new ("notification",
                   G_TYPE_FROM_CLASS (klass),
@@ -582,6 +645,48 @@ jsonrpc_client_call_read_cb (GObject      *object,
     }
 
   /*
+   * If this is a method call, emit the handle-call signal.
+   */
+  if (is_jsonrpc_call (node))
+    {
+      JsonNode *id_node = NULL;
+      JsonNode *params = NULL;
+      const gchar *method = NULL;
+      gboolean ret = FALSE;
+      gboolean success;
+
+      success = JCON_EXTRACT (node,
+        "id", JCONE_NODE (id_node),
+        "method", JCONE_STRING (method),
+        "params", JCONE_NODE (params)
+      );
+
+      g_assert (success);
+
+      g_signal_emit (self, signals [HANDLE_CALL], 0, method, id_node, params, &ret);
+
+      if (ret == FALSE)
+        {
+          g_autoptr(JsonNode) reply = NULL;
+
+          reply = JCON_NEW (
+            "jsonrpc", "2.0",
+            "id", JCON_NODE (id_node),
+            "error", "{",
+              "code", JCON_INT (-32601),
+              "message", "The method does not exist or is not available",
+            "}"
+          );
+
+          jsonrpc_output_stream_write_message_async (priv->output_stream,
+                                                     g_steal_pointer (&reply),
+                                                     NULL, NULL, NULL);
+        }
+
+      return;
+    }
+
+  /*
    * If we got an error destined for one of our inflight invocations, then
    * we need to dispatch it now.
    */
@@ -769,27 +874,8 @@ jsonrpc_client_call_async (JsonrpcClient       *self,
                                              jsonrpc_client_call_write_cb,
                                              g_steal_pointer (&task));
 
-  /*
-   * If this is our very first message, then we need to start our
-   * async read loop. This will allow us to receive notifications
-   * out-of-band and intermixed with RPC calls.
-   */
-
   if (priv->is_first_call)
-    {
-      priv->is_first_call = FALSE;
-
-      /*
-       * Because we take a reference here in our read loop, it is important
-       * that the user calls jsonrpc_client_close() or
-       * jsonrpc_client_close_async() so that we can cancel the operation and
-       * allow it to cleanup any outstanding references.
-       */
-      jsonrpc_input_stream_read_message_async (priv->input_stream,
-                                               priv->read_loop_cancellable,
-                                               jsonrpc_client_call_read_cb,
-                                               g_object_ref (self));
-    }
+    jsonrpc_client_start_listening (self);
 }
 
 /**
@@ -1091,3 +1177,171 @@ jsonrpc_client_close_finish (JsonrpcClient  *self,
 
   return g_task_propagate_boolean (G_TASK (result), error);
 }
+
+/**
+ * jsonrpc_client_reply:
+ * @id: (transfer full) (not nullable): the id of the message to reply
+ * result: (transfer full) (nullable): the return value or %NULL
+ *
+ * Synchronous variant of jsonrpc_client_reply_async().
+ */
+gboolean
+jsonrpc_client_reply (JsonrpcClient  *self,
+                      JsonNode       *id,
+                      JsonNode       *result,
+                      GCancellable   *cancellable,
+                      GError        **error)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(JsonNode) message = NULL;
+  gboolean ret;
+
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (id != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  if (!jsonrpc_client_check_ready (self, error))
+    return FALSE;
+
+  if (result == NULL)
+    result = json_node_new (JSON_NODE_NULL);
+
+  message = JCON_NEW (
+    "jsonrpc", "2.0",
+    "id", JCON_NODE (id),
+    "result", JCON_NODE (result)
+  );
+
+  ret = jsonrpc_output_stream_write_message (priv->output_stream, message, cancellable, error);
+
+  json_node_unref (id);
+  json_node_unref (result);
+
+  return ret;
+}
+
+static void
+jsonrpc_client_reply_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  JsonrpcOutputStream *stream = (JsonrpcOutputStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!jsonrpc_output_stream_write_message_finish (stream, result, &error))
+    g_task_return_error (task, g_steal_pointer (&task));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * jsonrcp_client_reply_async:
+ * @id: (transfer full) (not nullable): the id of the message to reply
+ * result: (transfer full) (nullable): the return value or %NULL
+ *
+ * This function will reply to a method call identified by @id with the
+ * result provided. If @result is %NULL, then a null JSON node is returned
+ * for the "result" field of the JSONRPC message.
+ *
+ * JSONRPC allows either peer to call methods on each other, so this
+ * method is provided allowing #JsonrpcClient to be used for either
+ * side of communications.
+ *
+ * If no signal handler has handled #JsonrpcClient::handle-call then
+ * an error will be synthesized to the peer.
+ *
+ * Call jsonrpc_client_reply_finish() to complete the operation. Note
+ * that since the peer does not reply to replies, completion of this
+ * asynchronous message does not indicate that the peer has received
+ * the message.
+ */
+void
+jsonrpc_client_reply_async (JsonrpcClient       *self,
+                            JsonNode            *id,
+                            JsonNode            *result,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(JsonNode) message = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (JSONRPC_IS_CLIENT (self));
+  g_return_if_fail (id != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, jsonrpc_client_reply_async);
+
+  if (!jsonrpc_client_check_ready (self, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (result == NULL)
+    result = json_node_new (JSON_NODE_NULL);
+
+  message = JCON_NEW (
+    "jsonrpc", "2.0",
+    "id", JCON_NODE (id),
+    "result", JCON_NODE (result)
+  );
+
+  jsonrpc_output_stream_write_message_async (priv->output_stream,
+                                             message,
+                                             cancellable,
+                                             jsonrpc_client_reply_cb,
+                                             g_steal_pointer (&task));
+
+  json_node_unref (id);
+  json_node_unref (result);
+}
+
+gboolean
+jsonrpc_client_reply_finish (JsonrpcClient  *self,
+                             GAsyncResult   *result,
+                             GError        **error)
+{
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+jsonrpc_client_start_listening (JsonrpcClient *self)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+
+  g_return_if_fail (JSONRPC_IS_CLIENT (self));
+
+  /*
+   * If this is our very first message, then we need to start our
+   * async read loop. This will allow us to receive notifications
+   * out-of-band and intermixed with RPC calls.
+   */
+
+  if (priv->is_first_call)
+    {
+      priv->is_first_call = FALSE;
+
+      /*
+       * Because we take a reference here in our read loop, it is important
+       * that the user calls jsonrpc_client_close() or
+       * jsonrpc_client_close_async() so that we can cancel the operation and
+       * allow it to cleanup any outstanding references.
+       */
+      jsonrpc_input_stream_read_message_async (priv->input_stream,
+                                               priv->read_loop_cancellable,
+                                               jsonrpc_client_call_read_cb,
+                                               g_object_ref (self));
+    }
+}
diff --git a/contrib/jsonrpc-glib/jsonrpc-client.h b/contrib/jsonrpc-glib/jsonrpc-client.h
index 3ec0fe9..1e58e20 100644
--- a/contrib/jsonrpc-glib/jsonrpc-client.h
+++ b/contrib/jsonrpc-glib/jsonrpc-client.h
@@ -33,9 +33,13 @@ struct _JsonrpcClientClass
 {
   GObjectClass parent_class;
 
-  void (*notification) (JsonrpcClient *self,
-                        const gchar   *method_name,
-                        JsonNode      *params);
+  void     (*notification) (JsonrpcClient *self,
+                            const gchar   *method_name,
+                            JsonNode      *params);
+  gboolean (*handle_call)  (JsonrpcClient *self,
+                            const gchar   *method,
+                            JsonNode      *id,
+                            JsonNode      *params);
 
   gpointer _reserved1;
   gpointer _reserved2;
@@ -89,6 +93,21 @@ void           jsonrpc_client_notification_async  (JsonrpcClient        *self,
 gboolean       jsonrpc_client_notification_finish (JsonrpcClient        *self,
                                                    GAsyncResult         *result,
                                                    GError              **error);
+gboolean       jsonrpc_client_reply               (JsonrpcClient        *self,
+                                                   JsonNode             *id,
+                                                   JsonNode             *result,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+void           jsonrpc_client_reply_async         (JsonrpcClient        *self,
+                                                   JsonNode             *id,
+                                                   JsonNode             *result,
+                                                   GCancellable         *cancellable,
+                                                   GAsyncReadyCallback   callback,
+                                                   gpointer              user_data);
+gboolean       jsonrpc_client_reply_finish        (JsonrpcClient        *self,
+                                                   GAsyncResult         *result,
+                                                   GError              **error);
+void           jsonrpc_client_start_listening     (JsonrpcClient        *self);
 
 G_END_DECLS
 
diff --git a/contrib/jsonrpc-glib/jsonrpc-glib.h b/contrib/jsonrpc-glib/jsonrpc-glib.h
index 4a23404..d837f16 100644
--- a/contrib/jsonrpc-glib/jsonrpc-glib.h
+++ b/contrib/jsonrpc-glib/jsonrpc-glib.h
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
 # include "jsonrpc-client.h"
 # include "jsonrpc-input-stream.h"
 # include "jsonrpc-output-stream.h"
+# include "jsonrpc-server.h"
 # include "jsonrpc-version.h"
 # include "jcon.h"
 #undef JSONRPC_GLIB_INSIDE
diff --git a/contrib/jsonrpc-glib/jsonrpc-server.c b/contrib/jsonrpc-glib/jsonrpc-server.c
new file mode 100644
index 0000000..f809e5a
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-server.c
@@ -0,0 +1,159 @@
+/* jsonrpc-server.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "jsonrpc-server"
+
+#include "jsonrpc-input-stream.h"
+#include "jsonrpc-output-stream.h"
+#include "jsonrpc-server.h"
+
+typedef struct
+{
+  GHashTable *clients;
+} JsonrpcServerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (JsonrpcServer, jsonrpc_server, G_TYPE_OBJECT)
+
+enum {
+  HANDLE_CALL,
+  NOTIFICATION,
+  N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+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_OBJECT_CLASS (jsonrpc_server_parent_class)->finalize (object);
+}
+
+static void
+jsonrpc_server_class_init (JsonrpcServerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = jsonrpc_server_finalize;
+
+  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_TYPE_BOOLEAN,
+                  4,
+                  JSONRPC_TYPE_CLIENT,
+                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+                  JSON_TYPE_NODE,
+                  JSON_TYPE_NODE);
+
+  signals [NOTIFICATION] =
+    g_signal_new ("notification",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (JsonrpcServerClass, notification),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  3,
+                  JSONRPC_TYPE_CLIENT,
+                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+                  JSON_TYPE_NODE);
+}
+
+static void
+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);
+}
+
+JsonrpcServer *
+jsonrpc_server_new (void)
+{
+  return g_object_new (JSONRPC_TYPE_SERVER, NULL);
+}
+
+static gboolean
+jsonrpc_server_client_handle_call (JsonrpcServer *self,
+                                   const gchar   *method,
+                                   JsonNode      *id,
+                                   JsonNode      *params,
+                                   JsonrpcClient *client)
+{
+  gboolean ret;
+
+  g_assert (JSONRPC_IS_SERVER (self));
+  g_assert (method != NULL);
+  g_assert (id != NULL);
+  g_assert (params != NULL);
+  g_assert (JSONRPC_IS_CLIENT (client));
+
+  g_signal_emit (self, signals [HANDLE_CALL], 0, client, method, id, params, &ret);
+
+  return ret;
+}
+
+static void
+jsonrpc_server_client_notification (JsonrpcServer *self,
+                                    const gchar   *method,
+                                    JsonNode      *params,
+                                    JsonrpcClient *client)
+{
+  g_assert (JSONRPC_IS_SERVER (self));
+  g_assert (method != NULL);
+  g_assert (params != NULL);
+  g_assert (JSONRPC_IS_CLIENT (client));
+
+  g_signal_emit (self, signals [NOTIFICATION], 0, client, method, params);
+}
+
+void
+jsonrpc_server_accept_io_stream (JsonrpcServer *self,
+                                 GIOStream     *io_stream)
+{
+  JsonrpcServerPrivate *priv = jsonrpc_server_get_instance_private (self);
+  JsonrpcClient *client;
+
+  g_return_if_fail (JSONRPC_IS_SERVER (self));
+  g_return_if_fail (G_IS_IO_STREAM (io_stream));
+
+  client = jsonrpc_client_new (io_stream);
+
+  g_signal_connect_object (client,
+                           "handle-call",
+                           G_CALLBACK (jsonrpc_server_client_handle_call),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (client,
+                           "notification",
+                           G_CALLBACK (jsonrpc_server_client_notification),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_hash_table_insert (priv->clients, client, NULL);
+
+  jsonrpc_client_start_listening (client);
+}
diff --git a/contrib/jsonrpc-glib/jsonrpc-server.h b/contrib/jsonrpc-glib/jsonrpc-server.h
new file mode 100644
index 0000000..82542da
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-server.h
@@ -0,0 +1,63 @@
+/* jsonrpc-server.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JSONRPC_SERVER_H
+#define JSONRPC_SERVER_H
+
+#include <json-glib/json-glib.h>
+#include <gio/gio.h>
+
+#include "jsonrpc-client.h"
+
+G_BEGIN_DECLS
+
+#define JSONRPC_TYPE_SERVER (jsonrpc_server_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (JsonrpcServer, jsonrpc_server, JSONRPC, SERVER, GObject)
+
+struct _JsonrpcServerClass
+{
+  GObjectClass parent_class;
+
+  gboolean (*handle_call)  (JsonrpcServer *self,
+                            JsonrpcClient *client,
+                            const gchar   *method,
+                            JsonNode      *id,
+                            JsonNode      *params);
+  void     (*notification) (JsonrpcServer *self,
+                            JsonrpcClient *client,
+                            const gchar   *method,
+                            JsonNode      *params);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+JsonrpcServer *jsonrpc_server_new              (void);
+void           jsonrpc_server_accept_io_stream (JsonrpcServer *self,
+                                                GIOStream     *stream);
+
+G_END_DECLS
+
+#endif /* JSONRPC_SERVER_H */


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