forked from mirrors/kore
Merge pull request #135 from raphaelmonrouzeau/master
Add conditional JSON-RPC support
This commit is contained in:
commit
6ba8dd439b
9
Makefile
9
Makefile
|
@ -30,6 +30,9 @@ endif
|
|||
|
||||
ifneq ("$(NOHTTP)", "")
|
||||
CFLAGS+=-DKORE_NO_HTTP
|
||||
ifneq ("$(JSONRPC)", "")
|
||||
$(error "JSONRPC support needs HTTP")
|
||||
endif
|
||||
else
|
||||
S_SRC+= src/auth.c src/accesslog.c src/http.c \
|
||||
src/validator.c src/websocket.c
|
||||
|
@ -57,6 +60,12 @@ ifneq ("$(TASKS)", "")
|
|||
CFLAGS+=-DKORE_USE_TASKS
|
||||
endif
|
||||
|
||||
ifneq ("$(JSONRPC)", "")
|
||||
S_SRC+=src/jsonrpc.c
|
||||
LDFLAGS+=-lyajl
|
||||
CFLAGS+=-DKORE_USE_JSONRPC
|
||||
endif
|
||||
|
||||
OSNAME=$(shell uname -s | sed -e 's/[-_].*//g' | tr A-Z a-z)
|
||||
ifeq ("$(OSNAME)", "darwin")
|
||||
CFLAGS+=-I/opt/local/include/ -I/usr/local/opt/openssl/include
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
*.o
|
||||
.objs
|
||||
jsonrpc.so
|
||||
assets.h
|
||||
cert
|
||||
.*.swp
|
||||
.*.swo
|
|
@ -0,0 +1,67 @@
|
|||
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 '{"id":1,"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
|
||||
|
||||
|
||||
JSONRPC Request Lifetime
|
||||
------------------------
|
||||
|
||||
Currently, one HTTP request will (in most cases) provoke one and only one
|
||||
response. Batch mode is not supported yet, neither is websocket.
|
||||
|
||||
As such `jsonrpc\_error` and `jsonrpc\_result` do clean the request after call.
|
||||
|
||||
If however you want to abort the processed request, like by returning
|
||||
`KORE\_RESULT\_ERROR`, after it having been read, you need to clean it by
|
||||
calling `jsonrpc\_destroy\_request`. Other than that you shouldn't think about
|
||||
this function.
|
||||
|
||||
|
||||
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.
|
|
@ -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.
|
||||
#}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#include <kore/kore.h>
|
||||
#include <kore/http.h>
|
||||
|
||||
int homepage(struct http_request *);
|
||||
|
||||
int
|
||||
homepage(struct http_request *req)
|
||||
{
|
||||
static const char response_body[] = "JSON-RPC API\n";
|
||||
|
||||
http_response_header(req, "content-type", "text/plain");
|
||||
http_response(req, 200, response_body, sizeof(response_body) - 1);
|
||||
return (KORE_RESULT_OK);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
#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_string(struct jsonrpc_request *req, void *ctx)
|
||||
{
|
||||
const unsigned char *str = (unsigned char *)ctx;
|
||||
|
||||
return yajl_gen_string(req->gen, str, strlen((const char *)str));
|
||||
}
|
||||
|
||||
static int
|
||||
write_string_array_params(struct jsonrpc_request *req, void *ctx)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
if (!YAJL_GEN_KO(status = yajl_gen_array_open(req->gen))) {
|
||||
for (size_t i = 0; i < req->params->u.array.len; i++) {
|
||||
yajl_val yajl_str = req->params->u.array.values[i];
|
||||
char *str = YAJL_GET_STRING(yajl_str);
|
||||
|
||||
if (YAJL_GEN_KO(status = yajl_gen_string(req->gen,
|
||||
(unsigned char *)str, strlen(str))))
|
||||
break;
|
||||
}
|
||||
if (status == 0)
|
||||
status = yajl_gen_array_close(req->gen);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
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_read_request(http_req, &req)) != 0)
|
||||
return jsonrpc_error(&req, ret, NULL);
|
||||
|
||||
/* Echo command takes and gives back params. */
|
||||
if (strcmp(req.method, "echo") == 0) {
|
||||
if (!YAJL_IS_ARRAY(req.params)) {
|
||||
jsonrpc_log(&req, LOG_ERR,
|
||||
"Echo only accepts positional params");
|
||||
return jsonrpc_error(&req, JSONRPC_INVALID_PARAMS, NULL);
|
||||
}
|
||||
for (size_t i = 0; i < req.params->u.array.len; i++) {
|
||||
yajl_val v = req.params->u.array.values[i];
|
||||
if (!YAJL_IS_STRING(v)) {
|
||||
jsonrpc_log(&req, -3,
|
||||
"Echo only accepts strings");
|
||||
return jsonrpc_error(&req,
|
||||
JSONRPC_INVALID_PARAMS, NULL);
|
||||
}
|
||||
}
|
||||
return jsonrpc_result(&req, write_string_array_params, NULL);
|
||||
}
|
||||
|
||||
/* 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];
|
||||
struct tm *(*gettm)(const time_t *, struct tm *) =
|
||||
localtime_r;
|
||||
|
||||
if (YAJL_IS_OBJECT(req.params)) {
|
||||
const char *path[] = {"local", NULL};
|
||||
yajl_val bf;
|
||||
|
||||
bf = yajl_tree_get(req.params, path, yajl_t_false);
|
||||
if (bf != NULL)
|
||||
gettm = gmtime_r;
|
||||
} else if (req.params != NULL) {
|
||||
jsonrpc_log(&req, LOG_ERR,
|
||||
"Date only accepts named params");
|
||||
return jsonrpc_error(&req, JSONRPC_INVALID_PARAMS, NULL);
|
||||
}
|
||||
|
||||
if ((time_value = time(NULL)) == -1)
|
||||
return jsonrpc_error(&req, -2,
|
||||
"Failed to get date time");
|
||||
|
||||
if (gettm(&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_string, timestamp);
|
||||
}
|
||||
|
||||
return jsonrpc_error(&req, JSONRPC_METHOD_NOT_FOUND, NULL);
|
||||
}
|
|
@ -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 returns nothing" {
|
||||
query='{"method":"foo","id":"foo"}'
|
||||
result=`query "$query"`
|
||||
printrep "$query" "$result"
|
||||
[ "$result" = "" ]
|
||||
}
|
||||
@test "requests with invalid protocol (1) returns nothing" {
|
||||
query='{"jsonrpc":"1.0","method":"foo","id":"foo"}'
|
||||
result=`query "$query"`
|
||||
printrep "$query" "$result"
|
||||
[ "$result" = "" ]
|
||||
}
|
||||
@test "requests with invalid protocol (2) returns nothing" {
|
||||
query='{"jsonrpc":2.0,"method":"foo","id":"foo"}'
|
||||
result=`query "$query"`
|
||||
printrep "$query" "$result"
|
||||
[ "$result" = "" ]
|
||||
}
|
||||
|
||||
@test "requests with no method raise errors" {
|
||||
query='{"jsonrpc":"2.0","id":"foo"}'
|
||||
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,"id":"foo"}'
|
||||
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","id":"foo"}'
|
||||
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 id" {
|
||||
query='{"jsonrpc":"2.0","method":"echo","params":["foobar"],"id":"tau"}'
|
||||
result=`query "$query"`
|
||||
printrep "$query" "$result"
|
||||
grepstr "$result" '"result"[ \t\n]*:[ \t\n]*[[ \t\n]*"foobar"[ \t\n]*]'
|
||||
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]*[[ \t\n]*"foobar"[ \t\n]*]'
|
||||
grepstr "$result" '"id"[ \t\n]*:[ \t\n]*6'
|
||||
}
|
|
@ -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_read_request(struct http_request *, struct jsonrpc_request *);
|
||||
void jsonrpc_destroy_request(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 */
|
|
@ -0,0 +1,478 @@
|
|||
/*
|
||||
* 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 <limits.h>
|
||||
#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_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;
|
||||
}
|
||||
|
||||
void
|
||||
jsonrpc_destroy_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_cleanup(&req->buf);
|
||||
free_log(&req->log);
|
||||
}
|
||||
|
||||
void
|
||||
jsonrpc_log(struct jsonrpc_request *req, int lvl, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *msg;
|
||||
size_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, struct jsonrpc_request *req)
|
||||
{
|
||||
char *body_string;
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Check params. */
|
||||
req->params = yajl_tree_get(req->json, params_path, yajl_t_any);
|
||||
if (!(req->params == NULL || YAJL_IS_ARRAY(req->params)
|
||||
|| YAJL_IS_OBJECT(req->params))) {
|
||||
jsonrpc_log(req, LOG_ERR,
|
||||
"JSON-RPC params MUST be Object or Array");
|
||||
return (JSONRPC_PARSE_ERROR);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
jsonrpc_read_request(struct http_request *http_req, struct jsonrpc_request *req)
|
||||
{
|
||||
int ret;
|
||||
|
||||
init_request(req);
|
||||
req->http = http_req;
|
||||
|
||||
if ((ret = read_json_body(http_req, req)) != 0)
|
||||
return (ret);
|
||||
|
||||
return parse_json_body(req);
|
||||
}
|
||||
|
||||
static int
|
||||
write_id(yajl_gen gen, yajl_val id)
|
||||
{
|
||||
int status;
|
||||
|
||||
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 yajl_gen_null(gen);
|
||||
}
|
||||
|
||||
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 yajl_gen_null(gen);
|
||||
}
|
||||
|
||||
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 = NULL;
|
||||
size_t body_len = 0;
|
||||
int status;
|
||||
|
||||
if (req->id == NULL)
|
||||
goto succeeded;
|
||||
|
||||
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) {
|
||||
size_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);
|
||||
succeeded:
|
||||
http_response(req->http, 200, body, body_len);
|
||||
if (req->gen != NULL)
|
||||
yajl_gen_clear(req->gen);
|
||||
jsonrpc_destroy_request(req);
|
||||
return (KORE_RESULT_OK);
|
||||
failed:
|
||||
http_response(req->http, 500, NULL, 0);
|
||||
jsonrpc_destroy_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 = NULL;
|
||||
size_t body_len = 0;
|
||||
|
||||
if (req->id == NULL)
|
||||
goto succeeded;
|
||||
|
||||
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);
|
||||
succeeded:
|
||||
http_response(req->http, 200, body, body_len);
|
||||
if (req->gen != NULL)
|
||||
yajl_gen_clear(req->gen);
|
||||
jsonrpc_destroy_request(req);
|
||||
return (KORE_RESULT_OK);
|
||||
failed:
|
||||
http_response(req->http, 500, NULL, 0);
|
||||
jsonrpc_destroy_request(req);
|
||||
return (KORE_RESULT_OK);
|
||||
}
|
|
@ -415,6 +415,9 @@ kore_server_start(void)
|
|||
#if defined(KORE_USE_TASKS)
|
||||
kore_log(LOG_NOTICE, "tasks built-in enabled");
|
||||
#endif
|
||||
#if defined(KORE_USE_JSONRPC)
|
||||
kore_log(LOG_NOTICE, "jsonrpc built-in enabled");
|
||||
#endif
|
||||
|
||||
kore_platform_proctitle("kore [parent]");
|
||||
kore_msg_init();
|
||||
|
|
Loading…
Reference in New Issue