summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jsonrpc.c255
-rw-r--r--jsonrpc.h18
-rw-r--r--jsonrpc_server.c82
3 files changed, 355 insertions, 0 deletions
diff --git a/jsonrpc.c b/jsonrpc.c
new file mode 100644
index 0000000..e3a2942
--- /dev/null
+++ b/jsonrpc.c
@@ -0,0 +1,255 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <jansson.h>
+#include "jsonrpc.h"
+
+json_t *jsonrpc_error_object(int code, json_t *data)
+{
+ // reference to data is stolen
+ char *message = "";
+
+ switch (code) {
+ case JSONRPC_PARSE_ERROR:
+ message = "Parse Error";
+ break;
+ case JSONRPC_INVALID_REQUEST:
+ message = "Invalid Request";
+ break;
+ case JSONRPC_METHOD_NOT_FOUND:
+ message = "Method not found";
+ break;
+ case JSONRPC_INVALID_PARAMS:
+ message = "Invalid params";
+ break;
+ case JSONRPC_INTERNAL_ERROR:
+ message = "Internal error";
+ break;
+ }
+
+ json_t *json = json_pack("{s:i,s:s}", "code", code, "message", message);
+ if (data) {
+ json_object_set_new(json, "data", data);
+ }
+ return json;
+}
+
+json_t *jsonrpc_error_response(json_t *json_id, json_t *json_error)
+{
+ // json_error reference is stolen
+
+ // json_id could be NULL
+ if (json_id) {
+ json_incref(json_id);
+ } else {
+ json_id = json_null();
+ }
+
+ json_error = json_error ? json_error : json_null();
+
+ json_t *response = json_pack("{s:s,s:o,s:o}",
+ "jsonrpc", "2.0",
+ "id", json_id,
+ "error", json_error);
+ return response;
+}
+
+json_t *jsonrpc_result_response(json_t *json_id, json_t *json_result)
+{
+ // json_result reference is stolen
+
+ // json_id shouldn't be NULL
+ if (json_id) {
+ json_incref(json_id);
+ } else {
+ json_id = json_null();
+ }
+
+ json_result = json_result ? json_result : json_null();
+
+ json_t *response = json_pack("{s:s,s:o,s:o}",
+ "jsonrpc", "2.0",
+ "id", json_id,
+ "result", json_result);
+ return response;
+}
+
+json_t *jsonrpc_validate_request(json_t *json_request, const char **str_method, json_t **json_params, json_t **json_id)
+{
+ size_t flags = 0;
+ json_error_t error;
+ const char *str_version = NULL;
+ int rc;
+ json_t *data = NULL;
+ int valid_id = 0;
+
+ *str_method = NULL;
+ *json_params = NULL;
+ *json_id = NULL;
+
+ rc = json_unpack_ex(json_request, &error, flags, "{s:s,s:s,s?o,s?o}",
+ "jsonrpc", &str_version,
+ "method", str_method,
+ "params", json_params,
+ "id", json_id
+ );
+ if (rc==-1) {
+ data = json_string(error.text);
+ goto invalid;
+ }
+
+ if (0!=strcmp(str_version, "2.0")) {
+ data = json_string("\"jsonrpc\" MUST be exactly \"2.0\"");
+ goto invalid;
+ }
+
+ if (*json_id) {
+ if (!json_is_string(*json_id) && !json_is_number(*json_id) && !json_is_null(*json_id)) {
+ data = json_string("\"id\" MUST contain a String, Number, or NULL value if included");
+ goto invalid;
+ }
+ }
+
+ // Note that we only return json_id in the error response after we have established that it is jsonrpc/2.0 compliant
+ // otherwise we would be returning a non-compliant response ourselves!
+ valid_id = 1;
+
+ if (*json_params) {
+ if (!json_is_array(*json_params) && !json_is_object(*json_params)) {
+ data = json_string("\"params\" MUST be Array or Object if included");
+ goto invalid;
+ }
+ }
+
+ return NULL;
+
+invalid:
+ if (!valid_id)
+ *json_id = NULL;
+ return jsonrpc_error_response(*json_id,
+ jsonrpc_error_object(JSONRPC_INVALID_REQUEST, data));
+}
+
+json_t *jsonrpc_validate_params(json_t *json_params, const char *params_spec)
+{
+ json_t *data = NULL;
+
+ if (strlen(params_spec)==0) { // empty string means no arguments
+ if (!json_params) {
+ // no params field: OK
+ } else if (json_is_array(json_params) && json_array_size(json_params)==0) {
+ // an empty Array: OK
+ } else {
+ data = json_string("method takes no arguments");
+ }
+ } else if (!json_params) { // non-empty string but no params field
+ data = json_string("method takes arguments but params field missing");
+ } else { // non-empty string and have params field
+ size_t flags = JSON_VALIDATE_ONLY;
+ json_error_t error;
+ int rc = json_unpack_ex(json_params, &error, flags, params_spec);
+ if (rc==-1) {
+ data = json_string(error.text);
+ }
+ }
+
+ return data ? jsonrpc_error_object(JSONRPC_INVALID_PARAMS, data) : NULL;
+}
+
+json_t *jsonrpc_handle_request_single(json_t *json_request, struct jsonrpc_method_entry_t method_table[])
+{
+ int rc;
+ json_t *json_response;
+ const char *str_method;
+ json_t *json_params, *json_id;
+ json_t *json_result;
+
+ json_response = jsonrpc_validate_request(json_request, &str_method, &json_params, &json_id);
+ if (json_response)
+ return json_response;
+
+ int is_notification = json_id==NULL;
+
+ struct jsonrpc_method_entry_t *entry;
+ for (entry=method_table; entry->name!=NULL; entry++) {
+ if (0==strcmp(entry->name, str_method))
+ break;
+ }
+ if (entry->name==NULL) {
+ json_response = jsonrpc_error_response(json_id,
+ jsonrpc_error_object(JSONRPC_METHOD_NOT_FOUND, NULL));
+ goto done;
+ }
+
+ if (entry->params_spec) {
+ json_t *error_obj = jsonrpc_validate_params(json_params, entry->params_spec);
+ if (error_obj) {
+ json_response = jsonrpc_error_response(json_id, error_obj);
+ goto done;
+ }
+ }
+
+ json_response = NULL;
+ json_result = NULL;
+ rc = entry->funcptr(json_params, &json_result);
+ if (is_notification) {
+ json_decref(json_result);
+ json_result = NULL;
+ } else {
+ if (rc==0) {
+ json_response = jsonrpc_result_response(json_id, json_result);
+ } else if (rc==JSONRPC_INVALID_PARAMS) {
+ json_response = jsonrpc_error_response(json_id,
+ jsonrpc_error_object(JSONRPC_INVALID_PARAMS, json_result));
+ } else {
+ json_response = jsonrpc_error_response(json_id,
+ jsonrpc_error_object(JSONRPC_INTERNAL_ERROR, json_result));
+ }
+ }
+
+done:
+ if (is_notification && json_response) {
+ json_decref(json_response);
+ json_response = NULL;
+ }
+ return json_response;
+}
+
+char *jsonrpc_handler(const char *input, size_t input_len, struct jsonrpc_method_entry_t method_table[])
+{
+ json_t *json_request, *json_response;
+ json_error_t error;
+ char *output = NULL;
+
+ json_request = json_loadb(input, input_len, 0, &error);
+ if (!json_request) {
+ json_response = jsonrpc_error_response(NULL,
+ jsonrpc_error_object(JSONRPC_PARSE_ERROR, NULL));
+ } else if json_is_array(json_request) {
+ size_t len = json_array_size(json_request);
+ if (len==0) {
+ json_response = jsonrpc_error_response(NULL,
+ jsonrpc_error_object(JSONRPC_INVALID_REQUEST, NULL));
+ } else {
+ int k;
+ json_response = NULL;
+ for (k=0; k < len; k++) {
+ json_t *req = json_array_get(json_request, k);
+ json_t *rep = jsonrpc_handle_request_single(req, method_table);
+ if (rep) {
+ if (!json_response)
+ json_response = json_array();
+ json_array_append_new(json_response, rep);
+ }
+ }
+ }
+ } else {
+ json_response = jsonrpc_handle_request_single(json_request, method_table);
+ }
+
+ if (json_response)
+ output = json_dumps(json_response, JSON_INDENT(2));
+ return output;
+}
diff --git a/jsonrpc.h b/jsonrpc.h
new file mode 100644
index 0000000..27a458c
--- /dev/null
+++ b/jsonrpc.h
@@ -0,0 +1,18 @@
+#include <jansson.h>
+
+#define JSONRPC_PARSE_ERROR -32700
+#define JSONRPC_INVALID_REQUEST -32600
+#define JSONRPC_METHOD_NOT_FOUND -32601
+#define JSONRPC_INVALID_PARAMS -32602
+#define JSONRPC_INTERNAL_ERROR -32603
+
+typedef int (*jsonrpc_method_prototype)(json_t *json_params, json_t **result);
+struct jsonrpc_method_entry_t
+{
+ const char *name;
+ jsonrpc_method_prototype funcptr;
+ const char *params_spec;
+};
+char *jsonrpc_handler(const char *input, size_t input_len, struct jsonrpc_method_entry_t method_table[]);
+
+json_t *jsonrpc_error_object(int code, json_t *data);
diff --git a/jsonrpc_server.c b/jsonrpc_server.c
new file mode 100644
index 0000000..4e775d9
--- /dev/null
+++ b/jsonrpc_server.c
@@ -0,0 +1,82 @@
+#include <czmq.h>
+#include "jsonrpc.h"
+
+static int method_echo(json_t *json_params, json_t **result)
+{
+ json_incref(json_params);
+ *result = json_params;
+ return 0;
+}
+
+static int method_subtract(json_t *json_params, json_t **result)
+{
+ size_t flags = 0;
+ json_error_t error;
+ double x, y;
+ int rc;
+
+ if (json_is_array(json_params)) {
+ rc = json_unpack_ex(json_params, &error, flags, "[FF!]", &x, &y);
+ } else if (json_is_object(json_params)) {
+ rc = json_unpack_ex(json_params, &error, flags, "{s:F,s:F}",
+ "minuend", &x, "subtrahend", &y
+ );
+ } else {
+ assert(0);
+ }
+
+ if (rc==-1) {
+ *result = jsonrpc_error_object(JSONRPC_INVALID_PARAMS, json_string(error.text));
+ return JSONRPC_INVALID_PARAMS;
+ }
+
+ *result = json_real(x - y);
+ return 0;
+}
+
+static int method_sum(json_t *json_params, json_t **result)
+{
+ double total = 0;
+ size_t len = json_array_size(json_params);
+ int k;
+ for (k=0; k < len; k++) {
+ double value = json_number_value(json_array_get(json_params, k));
+ total += value;
+ }
+ *result = json_real(total);
+ return 0;
+}
+
+static struct jsonrpc_method_entry_t method_table[] = {
+ { "echo", method_echo, "o" },
+ { "subtract", method_subtract, "o" },
+ { "sum", method_sum, "[]" },
+ { NULL },
+};
+
+int main()
+{
+ zctx_t *ctx = zctx_new();
+ void *sock = zsocket_new(ctx, ZMQ_REP);
+ int port = zsocket_bind(sock, "tcp://127.0.0.1:*");
+ printf("bound to port %d\n", port);
+
+ while (1) {
+ zmsg_t *msg = zmsg_recv(sock);
+ zframe_t *frame = zmsg_first(msg);
+
+ char *output = jsonrpc_handler((char *)zframe_data(frame), zframe_size(frame), method_table);
+ if (output) {
+ zstr_send(sock, output);
+ free(output);
+ } else {
+ zstr_send(sock, "");
+ }
+
+ zmsg_destroy(&msg);
+ }
+
+ zctx_destroy(&ctx);
+
+ return 0;
+}