mirror of
https://github.com/jorisvink/kore
synced 2025-03-09 12:39:01 -04:00
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:
parent
2cf83aea3c
commit
db02e990ea
6
Makefile
6
Makefile
@ -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
7
examples/jsonrpc/.gitignore
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
*.o
|
||||
.objs
|
||||
jsonrpc.so
|
||||
assets.h
|
||||
cert
|
||||
.*.swp
|
||||
.*.swo
|
53
examples/jsonrpc/README.md
Normal file
53
examples/jsonrpc/README.md
Normal 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.
|
19
examples/jsonrpc/conf/build.conf
Normal file
19
examples/jsonrpc/conf/build.conf
Normal 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.
|
||||
#}
|
14
examples/jsonrpc/conf/jsonrpc.conf
Normal file
14
examples/jsonrpc/conf/jsonrpc.conf
Normal 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
|
||||
}
|
14
examples/jsonrpc/src/home.c
Normal file
14
examples/jsonrpc/src/home.c
Normal 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
74
examples/jsonrpc/src/v1.c
Normal 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);
|
||||
}
|
121
examples/jsonrpc/test/integ/jsonrpc.bats
Normal file
121
examples/jsonrpc/test/integ/jsonrpc.bats
Normal 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
87
includes/jsonrpc.h
Normal 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
480
src/jsonrpc.c
Normal 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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user