[gnome-builder] jsonrpc: import JsonrpcServer
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] jsonrpc: import JsonrpcServer
- Date: Tue, 1 Nov 2016 05:43:51 +0000 (UTC)
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]