JSON-RPC support for Kore.

The API surface is very limited. Jsonrpc support reads request from HTTP
body and such can't be activated if NOHTTP=1. At the moment there is no
websocket support either (which is a shame). It depends upon the
third-party Yajl library.

Errors can be emitted using jsonrpc_error() and results using
jsonrpc_result(), for the later you'll have to provide a callback which
will write the inner of the result object.

If errors happen during the response write process, no further error
output will be attempted and an HTTP error 500 will be returned.

Read the provided example for getting a better idea of the API.
This commit is contained in:
Raphaël Monrouzeau 2016-07-05 17:27:30 +02:00
parent 2cf83aea3c
commit db02e990ea
10 changed files with 875 additions and 0 deletions

View File

@ -33,6 +33,12 @@ ifneq ("$(NOHTTP)", "")
else
S_SRC+= src/auth.c src/accesslog.c src/http.c \
src/validator.c src/websocket.c
ifneq ("$(JSONRPC)", "")
S_SRC+=src/jsonrpc.c
LDFLAGS+=-lyajl
CFLAGS+=-DKORE_USE_JSONRPC
endif
endif
ifneq ("$(NOTLS)", "")

7
examples/jsonrpc/.gitignore vendored Executable file
View File

@ -0,0 +1,7 @@
*.o
.objs
jsonrpc.so
assets.h
cert
.*.swp
.*.swo

View File

@ -0,0 +1,53 @@
This example demonstrates how you can use the JSON-RPC module in your
application.
Note that the module depends upon the third-party library `yajl` (Yet Another
JSON library) to parse and produce messages.
As for the `yajl_json` example, conf/build.conf shows how to link to the
library.
This example needs kore having been compiled with `JSONRPC` (and so `HTTP`)
activated.
Run:
```
$ kore run
```
Test:
```
$ curl -i -k \
-d '{"jsonrpc":"2.0","method":"echo","params":"Hello world"}' \
https://127.0.0.1:8888/v1
```
The result should echo back the string at `params`: Hello world.
Alternatively, if you have bats installed:
```
$ bats test/integ/jsonrpc.bats
```
Will run a small test suite.
The yajl repo is available @ https://github.com/lloyd/yajl
Message Handling Log
--------------------
The `jsonrpc\_request` keeps a log of messages with levels similar to those of
syslog. Messages are added with jsonrpc_log().
By default messages of the log are added to the data member of the error
responses if at levels EMERG, ERROR, WARNING and NOTICE.
If you dont want log messages to be outputted zero the log_levels flag of the
jsonrpc_request.
Formatting responses
--------------------
By default responses are not prettyfied. To do that set the appropriate flag in
the jsonrpc_request structure.

View File

@ -0,0 +1,19 @@
# jsonrpc build config
# You can switch flavors using: kore flavor [newflavor]
# The cflags below are shared between flavors
cflags=-Wall -Wmissing-declarations -Wshadow
cflags=-Wstrict-prototypes -Wmissing-prototypes
cflags=-Wpointer-arith -Wcast-qual -Wsign-compare
dev {
# These cflags are added to the shared ones when
# you build the "dev" flavor.
cflags=-g
ldflags=-lyajl
}
#prod {
# You can specify additional CFLAGS here which are only
# included if you build with the "prod" flavor.
#}

View File

@ -0,0 +1,14 @@
# Placeholder configuration
bind 127.0.0.1 8888
load ./jsonrpc.so
tls_dhparam dh2048.pem
domain 127.0.0.1 {
certfile cert/server.crt
certkey cert/server.key
static / homepage
static /v1 v1
}

View File

@ -0,0 +1,14 @@
#include <kore/kore.h>
#include <kore/http.h>
int homepage(struct http_request *);
int
homepage(struct http_request *req)
{
static char response_body[] = "JSON-RPC API\n";
http_response_header(req, "content-type", "text/plain");
http_response(req, 200, (void*)response_body, sizeof(response_body) - 1);
return (KORE_RESULT_OK);
}

74
examples/jsonrpc/src/v1.c Normal file
View File

@ -0,0 +1,74 @@
#include <time.h>
#include <xlocale.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_tree.h>
#include <kore/kore.h>
#include <kore/http.h>
#include <kore/jsonrpc.h>
int v1(struct http_request *);
static int
write_json_string(struct jsonrpc_request *req, void *ctx)
{
char *str = (char *)ctx;
return yajl_gen_string(req->gen, (unsigned char *)str, strlen(str));
}
int
v1(struct http_request *http_req)
{
struct jsonrpc_request req;
int ret;
/* We only allow POST/PUT methods. */
if (http_req->method != HTTP_METHOD_POST &&
http_req->method != HTTP_METHOD_PUT) {
http_response_header(http_req, "allow", "POST, PUT");
http_response(http_req, HTTP_STATUS_METHOD_NOT_ALLOWED, NULL, 0);
return (KORE_RESULT_OK);
}
/* Read JSON-RPC request. */
if ((ret = jsonrpc_request_read(http_req, 1000 * 64, &req)) != 0)
return jsonrpc_error(&req, ret, NULL);
/* Echo command takes and gives back a single string. */
if (strcmp(req.method, "echo") == 0) {
char *msg = YAJL_GET_STRING(req.params);
if (msg == NULL) {
return jsonrpc_error(&req,
JSONRPC_INVALID_PARAMS, NULL);
}
return jsonrpc_result(&req, write_json_string, msg);
}
/* Date command displays date and time according to parameters. */
if (strcmp(req.method, "date") == 0) {
time_t time_value;
struct tm time_info;
char timestamp[33];
char *args[2] = {NULL, NULL};
if ((time_value = time(NULL)) == -1)
return jsonrpc_error(&req, -2,
"Failed to get date time");
//gmtime_r(time_value, &time_info);
if (localtime_r(&time_value, &time_info) == NULL)
return jsonrpc_error(&req, -3,
"Failed to get date time info");
memset(timestamp, 0, sizeof(timestamp));
if (strftime_l(timestamp, sizeof(timestamp) - 1, "%c",
&time_info, LC_GLOBAL_LOCALE) == 0)
return jsonrpc_error(&req, -4,
"Failed to get printable date time");
return jsonrpc_result(&req, write_json_string,
timestamp);
}
return jsonrpc_error(&req, JSONRPC_METHOD_NOT_FOUND, NULL);
}

View File

@ -0,0 +1,121 @@
#!/usr/bin/env bats
# Simple and non exhaustive test suite using bats:
# https://github.com/sstephenson/bats
PIDFILE=run/jsonrpc.pid
CONFFILE=conf/jsonrpc.conf
# Start and stop have to be tweaked before being used
stop_app() {
if [ -f "$PIDFILE" ]; then
kill -QUIT `cat "$PIDFILE"`
sleep 3
fi
if [ -f "$PIDFILE" ]; then
kill -KILL `cat "$PIDFILE"`
sleep 2
fi
}
start_app() {
stop_app
kore -nrc "$CONFFILE"
}
query_with_content_type() {
curl -q \
-H "Content-Type: $1" \
-X POST \
--raw \
-d "$2" \
-s -S \
--insecure \
"https://127.0.0.1:8888/v1"
}
query() {
query_with_content_type "application/json" "$1"
}
grepstr() {
declare result=$1
shift
printf "%s" "$result" | grep "$@" >/dev/null
}
printrep() {
declare query=$1
declare result=$2
printf "Sent:\n"
printf "%s\n" "$query"
printf "Received:\n"
printf "%s\n" "$result"
}
@test "requests with no protocol raise errors" {
query='{"method":"foo"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
}
@test "requests with invalid protocol (1) raise errors" {
query='{"jsonrpc":"1.0","method":"foo"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
}
@test "requests with invalid protocol (2) raise errors" {
query='{"jsonrpc":2.0,"method":"foo"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
}
@test "requests with no method raise errors" {
query='{"jsonrpc":"2.0"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
}
@test "requests with invalid method raise errors" {
query='{"jsonrpc":"2.0","method":1}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
}
@test "requests with unknown method raise errors" {
query='{"jsonrpc":"2.0","method":"foobar"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
}
@test "error responses give back the string request id" {
query='{"jsonrpc":"2.0","id":"foo"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
grepstr "$result" '"id"[ \t\n]*:[ \t\n]*"foo"'
}
@test "error responses give back the integer request id" {
query='{"jsonrpc":"2.0","id":1}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"error"[ \t\n]*:[ \t\n]*{[ \t\n]*"code"'
grepstr "$result" '"id"[ \t\n]*:[ \t\n]*1'
}
@test "result responses give back the string request" {
query='{"jsonrpc":"2.0","method":"echo","params":"foobar","id":"tau"}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"result"[ \t\n]*:[ \t\n]*"foobar"'
grepstr "$result" '"id"[ \t\n]*:[ \t\n]*"tau"'
}
@test "result responses give back the integer request id" {
query='{"jsonrpc":"2.0","method":"echo","params":"foobar","id":6}'
result=`query "$query"`
printrep "$query" "$result"
grepstr "$result" '"result"[ \t\n]*:[ \t\n]*"foobar"'
grepstr "$result" '"id"[ \t\n]*:[ \t\n]*6'
}

87
includes/jsonrpc.h Normal file
View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2016 Raphaël Monrouzeau <raphael.monrouzeau@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#if !defined(KORE_NO_HTTP)
#ifndef __H_JSONRPC_H
#define __H_JSONRPC_H
#if defined(__cplusplus)
extern "C" {
#endif
/* JSON RPC request handling log entry. */
struct jsonrpc_log
{
char *msg;
struct jsonrpc_log *next, *prev;
int lvl;
};
/* JSON RPC request. */
struct jsonrpc_request
{
struct jsonrpc_log log;
struct kore_buf buf;
struct http_request *http;
yajl_gen gen;
yajl_val json;
yajl_val id;
char *method;
yajl_val params;
unsigned int flags;
int log_levels;
};
#define YAJL_GEN_CONST_STRING(CTX, STR) \
yajl_gen_string((CTX), (unsigned char *)(STR), sizeof (STR) - 1)
#define YAJL_GEN_CONST_NUMBER(CTX, STR) \
yajl_gen_number((CTX), (unsigned char *)(STR), sizeof (STR) - 1)
#define YAJL_GEN_KO(OPERATION) \
(OPERATION) != yajl_gen_status_ok
enum jsonrpc_error_code
{
#define JSONRPC_PARSE_ERROR_MSG "Parse error"
JSONRPC_PARSE_ERROR = -32700,
#define JSONRPC_INVALID_REQUEST_MSG "Invalid Request"
JSONRPC_INVALID_REQUEST = -32600,
#define JSONRPC_METHOD_NOT_FOUND_MSG "Method not found"
JSONRPC_METHOD_NOT_FOUND = -32601,
#define JSONRPC_INVALID_PARAMS_MSG "Invalid params"
JSONRPC_INVALID_PARAMS = -32602,
#define JSONRPC_INTERNAL_ERROR_MSG "Internal error"
JSONRPC_INTERNAL_ERROR = -32603,
#define JSONRPC_SERVER_ERROR_MSG "Server error"
JSONRPC_SERVER_ERROR = -32000,
#define JSONRPC_LIMIT_REACHED_MSG "Limit reached"
JSONRPC_LIMIT_REACHED = -31997
};
void jsonrpc_log(struct jsonrpc_request *, int, const char *, ...);
int jsonrpc_request_read(struct http_request *, ssize_t,
struct jsonrpc_request *);
int jsonrpc_error(struct jsonrpc_request *, int, const char *);
int jsonrpc_result(struct jsonrpc_request *,
int (*)(struct jsonrpc_request *, void *), void *);
#if defined(__cplusplus)
}
#endif
#endif /* !__H_JSONRPC_H */
#endif /* ! KORE_NO_HTTP */

480
src/jsonrpc.c Normal file
View File

@ -0,0 +1,480 @@
/*
* Copyright (c) 2016 Raphaël Monrouzeau <raphael.monrouzeau@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdbool.h>
#include <yajl/yajl_tree.h>
#include <yajl/yajl_gen.h>
#include "kore.h"
#include "http.h"
#include "jsonrpc.h"
static void
init_log(struct jsonrpc_log *log)
{
log->msg = NULL;
log->next = log;
log->prev = log;
}
static void
append_log(struct jsonrpc_log *prev, int lvl, char *msg)
{
struct jsonrpc_log *new = kore_malloc(sizeof(struct jsonrpc_log));
new->lvl = lvl;
new->msg = msg;
new->prev = prev;
new->next = prev->next;
prev->next->prev = new;
prev->next = new;
}
static void
free_log(struct jsonrpc_log *root)
{
for (struct jsonrpc_log *it = root->next; it != root; it = it->next) {
kore_mem_free(it);
}
}
static void
init_request(struct jsonrpc_request *req)
{
init_log(&req->log);
kore_buf_init(&req->buf, 256);
req->gen = NULL;
req->http = NULL;
req->json = NULL;
req->id = NULL;
req->method = NULL;
req->params = NULL;
req->log_levels = (1 << LOG_EMERG) | (1 << LOG_ERR) | (1 << LOG_WARNING)
| (1 << LOG_NOTICE);
req->flags = 0;
}
static void
free_request(struct jsonrpc_request *req)
{
if (req->gen != NULL) {
yajl_gen_free(req->gen);
req->gen = NULL;
}
if (req->json != NULL) {
yajl_tree_free(req->json);
req->json = NULL;
}
kore_buf_destroy(&req->buf);
free_log(&req->log);
}
void
jsonrpc_log(struct jsonrpc_request *req, int lvl, const char *fmt, ...)
{
va_list ap;
char *msg;
u_int32_t start = req->buf.offset;
va_start(ap, fmt);
kore_buf_appendv(&req->buf, fmt, ap);
va_end(ap);
msg = kore_buf_stringify(&req->buf, NULL) + start;
append_log(&req->log, lvl, msg);
}
static int
read_json_body(struct http_request *http_req, ssize_t body_max_len,
struct jsonrpc_request *req)
{
char *body_string;
u_int32_t body_start = req->buf.offset;
ssize_t body_len = 0, chunk_len;
u_int8_t chunk_buffer[BUFSIZ];
char error_buffer[1024];
for (;;) {
chunk_len = http_body_read(http_req, chunk_buffer,
sizeof(chunk_buffer));
if (chunk_len == -1) {
jsonrpc_log(req, LOG_CRIT,
"Failed to read request body");
return (JSONRPC_SERVER_ERROR);
}
if (chunk_len == 0)
break;
if (body_len > SSIZE_MAX - chunk_len) {
jsonrpc_log(req, LOG_CRIT,
"Request body bigger than the platform accepts");
return (JSONRPC_SERVER_ERROR);
}
body_len += chunk_len;
if (body_len > body_max_len) {
jsonrpc_log(req, LOG_ERR,
"Request overreached configured body size limit");
return (JSONRPC_LIMIT_REACHED);
}
kore_buf_append(&req->buf, chunk_buffer, chunk_len);
}
/* Grab our body data as a NUL-terminated string. */
body_string = kore_buf_stringify(&req->buf, NULL) + body_start;
/* Parse the body via yajl now. */
*error_buffer = 0;
req->json = yajl_tree_parse(body_string, error_buffer,
sizeof(error_buffer));
if (req->json == NULL) {
if (strlen(error_buffer)) {
jsonrpc_log(req, LOG_ERR, "Invalid json: %s",
error_buffer);
} else {
jsonrpc_log(req, LOG_ERR, "Invalid json");
}
return (JSONRPC_PARSE_ERROR);
}
return (0);
}
static int
parse_json_body(struct jsonrpc_request *req)
{
static const char *proto_path[] = { "jsonrpc", NULL };
static const char *id_path[] = { "id", NULL };
static const char *method_path[] = { "method", NULL };
static const char *params_path[] = { "params", NULL };
/* Check protocol first. */
yajl_val proto = yajl_tree_get(req->json, proto_path, yajl_t_string);
if (proto == NULL) {
jsonrpc_log(req, LOG_ERR,
"JSON-RPC protocol MUST be indicated and \"2.0\"");
return (JSONRPC_PARSE_ERROR);
}
char *proto_string = YAJL_GET_STRING(proto);
if (proto_string == NULL) {
jsonrpc_log(req, LOG_ERR,
"JSON-RPC protocol MUST be indicated and \"2.0\"");
return (JSONRPC_PARSE_ERROR);
}
if (strcmp("2.0", proto_string) != 0) {
jsonrpc_log(req, LOG_ERR,
"JSON-RPC protocol MUST be indicated and \"2.0\"");
return (JSONRPC_PARSE_ERROR);
}
/* Check id. */
if ((req->id = yajl_tree_get(req->json, id_path, yajl_t_any)) != NULL) {
if (YAJL_IS_NUMBER(req->id)) {
if (!YAJL_IS_INTEGER(req->id)) {
jsonrpc_log(req, LOG_ERR,
"JSON-RPC id SHOULD NOT contain fractional"
" parts");
return (JSONRPC_PARSE_ERROR);
}
} else if (!YAJL_IS_STRING(req->id)) {
jsonrpc_log(req, LOG_ERR,
"JSON-RPC id MUST contain a String or Number");
return (JSONRPC_PARSE_ERROR);
}
}
/* Check method. */
if ((req->method = YAJL_GET_STRING(yajl_tree_get(req->json, method_path,
yajl_t_string))) == NULL) {
jsonrpc_log(req, LOG_ERR,
"JSON-RPC method MUST exist and be a String");
return (JSONRPC_PARSE_ERROR);
}
/* Retrieve params. */
req->params = yajl_tree_get(req->json, params_path, yajl_t_any);
return (0);
}
int
jsonrpc_request_read(struct http_request *http_req, ssize_t max_body_len,
struct jsonrpc_request *req)
{
int ret;
init_request(req);
req->http = http_req;
if ((ret = read_json_body(http_req, max_body_len, req)) != 0)
return (ret);
return parse_json_body(req);
}
static int
write_id(yajl_gen gen, yajl_val id)
{
int status;
if (id == NULL)
return (yajl_gen_status_ok);
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(gen, "id")))
return (status);
if (YAJL_IS_NULL(id))
return yajl_gen_null(gen);
if (YAJL_IS_NUMBER(id)) {
if (YAJL_IS_INTEGER(id))
return yajl_gen_integer(gen, YAJL_GET_INTEGER(id));
return (-2);
}
if (YAJL_IS_STRING(id)) {
char *id_str = YAJL_GET_STRING(id);
return yajl_gen_string(gen, (unsigned char *)id_str,
strlen(id_str));
}
return (-1);
}
static int
open_response(yajl_gen genctx, yajl_val id)
{
int status;
if (YAJL_GEN_KO(status = yajl_gen_map_open(genctx)))
goto failed;
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(genctx, "jsonrpc")))
goto failed;
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(genctx, "2.0")))
goto failed;
status = write_id(genctx, id);
failed:
return (status);
}
static int
close_response(yajl_gen genctx)
{
int status;
if (YAJL_GEN_KO(status = yajl_gen_map_close(genctx)))
goto failed;
status = yajl_gen_map_close(genctx);
failed:
return (status);
}
static int
write_log(struct jsonrpc_request *req)
{
bool wrote_smth = false;
int status = 0;
for (struct jsonrpc_log *log = req->log.next; log != &req->log;
log = log->next) {
if (((1 << log->lvl) & req->log_levels) == 0)
continue;
if (!wrote_smth) {
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen,
"data")))
goto failed;
if (YAJL_GEN_KO(status = yajl_gen_array_open(req->gen)))
goto failed;
yajl_gen_config(req->gen, yajl_gen_validate_utf8, 1);
wrote_smth = true;
}
if (YAJL_GEN_KO(status = yajl_gen_array_open(req->gen)))
goto failed;
if (YAJL_GEN_KO(status = yajl_gen_integer(req->gen, log->lvl)))
goto failed;
if (YAJL_GEN_KO(status = yajl_gen_string(req->gen,
(unsigned char *)log->msg, strlen(log->msg))))
goto failed;
if (YAJL_GEN_KO(status = yajl_gen_array_close(req->gen)))
goto failed;
}
if (wrote_smth) {
yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
status = yajl_gen_array_close(req->gen);
}
failed:
return (status);
}
static int
write_error(struct jsonrpc_request *req, int code, const char *message)
{
int status;
yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
if (YAJL_GEN_KO(status = open_response(req->gen, req->id)))
goto failed;
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "error")))
goto failed;
if (YAJL_GEN_KO(status = yajl_gen_map_open(req->gen)))
goto failed;
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "code")))
goto failed;
if (YAJL_GEN_KO(status = yajl_gen_integer(req->gen, code)))
goto failed;
if (YAJL_GEN_KO(status = YAJL_GEN_CONST_STRING(req->gen, "message")))
goto failed;
yajl_gen_config(req->gen, yajl_gen_validate_utf8, 1);
if (YAJL_GEN_KO(status = yajl_gen_string(req->gen,
(const unsigned char *)message, strlen(message))))
goto failed;
yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
if (YAJL_GEN_KO(status = write_log(req)))
goto failed;
status = close_response(req->gen);
failed:
return (status);
}
static const char *
known_msg(int code)
{
switch (code) {
case JSONRPC_PARSE_ERROR:
return (JSONRPC_PARSE_ERROR_MSG);
case JSONRPC_INVALID_REQUEST:
return (JSONRPC_INVALID_REQUEST_MSG);
case JSONRPC_METHOD_NOT_FOUND:
return (JSONRPC_METHOD_NOT_FOUND_MSG);
case JSONRPC_INVALID_PARAMS:
return (JSONRPC_INVALID_PARAMS_MSG);
case JSONRPC_INTERNAL_ERROR:
return (JSONRPC_INTERNAL_ERROR_MSG);
case JSONRPC_SERVER_ERROR:
return (JSONRPC_SERVER_ERROR_MSG);
case JSONRPC_LIMIT_REACHED:
return (JSONRPC_LIMIT_REACHED_MSG);
default:
return (NULL);
}
}
int
jsonrpc_error(struct jsonrpc_request *req, int code, const char *msg)
{
char *msg_fallback;
const unsigned char *body;
size_t body_len;
int status;
if ((req->gen = yajl_gen_alloc(NULL)) == NULL) {
kore_log(LOG_ERR, "jsonrpc_error: Failed to allocate yajl gen");
goto failed;
}
yajl_gen_config(req->gen, yajl_gen_beautify,
req->flags & yajl_gen_beautify);
if (msg == NULL)
msg = known_msg(code);
if (msg == NULL) {
u_int32_t start = req->buf.offset;
kore_buf_appendf(&req->buf, "%d", code);
msg_fallback = kore_buf_stringify(&req->buf, NULL) + start;
}
if (YAJL_GEN_KO(status = write_error(req, code,
msg ? msg : msg_fallback))) {
kore_log(LOG_ERR, "jsonrpc_error: Failed to yajl gen text [%d]",
status);
goto failed;
}
http_response_header(req->http, "content-type", "application/json");
yajl_gen_get_buf(req->gen, &body, &body_len);
if (body_len > UINT32_MAX) {
kore_log(LOG_ERR, "jsonrpc_error: Body length overflow");
goto failed;
}
http_response(req->http, 200, body, body_len);
yajl_gen_clear(req->gen);
free_request(req);
return (KORE_RESULT_OK);
failed:
http_response(req->http, 500, NULL, 0);
free_request(req);
return (KORE_RESULT_OK);
}
int
jsonrpc_result(struct jsonrpc_request *req,
int (*write_result)(struct jsonrpc_request *, void *), void *ctx)
{
const unsigned char *body;
size_t body_len;
if ((req->gen = yajl_gen_alloc(NULL)) == NULL) {
kore_log(LOG_ERR, "jsonrpc_result: Failed to allocate yajl gen");
goto failed;
}
yajl_gen_config(req->gen, yajl_gen_beautify,
req->flags & yajl_gen_beautify);
yajl_gen_config(req->gen, yajl_gen_validate_utf8, 0);
if (YAJL_GEN_KO(open_response(req->gen, req->id)))
goto failed;
if (YAJL_GEN_KO(YAJL_GEN_CONST_STRING(req->gen, "result")))
goto failed;
if (YAJL_GEN_KO(write_result(req, ctx)))
goto failed;
if (YAJL_GEN_KO(yajl_gen_map_close(req->gen)))
goto failed;
http_response_header(req->http, "content-type", "application/json");
yajl_gen_get_buf(req->gen, &body, &body_len);
if (body_len > UINT32_MAX) {
kore_log(LOG_ERR, "jsonrpc_result: Body length overflow");
goto failed;
}
http_response(req->http, 200, body, body_len);
yajl_gen_clear(req->gen);
free_request(req);
return (KORE_RESULT_OK);
failed:
http_response(req->http, 500, NULL, 0);
free_request(req);
return (KORE_RESULT_OK);
}