Browse Source
We promote 'struct json_stream' to contain the membuf; we only attach the json_stream to the command when we actually call json_stream_success / json_stream_fail. This means we are closer to 'struct json_stream' being an independent layer; the tests are already modified to use it directly to create JSON. This is also the first step toward re-enabling non-serial command execution. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>trytravis
Rusty Russell
6 years ago
committed by
Christian Decker
12 changed files with 499 additions and 356 deletions
@ -0,0 +1,289 @@ |
|||||
|
#include <ccan/io/io.h> |
||||
|
/* To reach into io_plan: not a public header! */ |
||||
|
#include <ccan/io/backend.h> |
||||
|
#include <common/utils.h> |
||||
|
#include <lightningd/json.h> |
||||
|
#include <lightningd/json_stream.h> |
||||
|
#include <stdarg.h> |
||||
|
#include <stdio.h> |
||||
|
|
||||
|
struct json_stream { |
||||
|
#if DEVELOPER |
||||
|
/* tal_arr of types (JSMN_OBJECT/JSMN_ARRAY) we're enclosed in. */ |
||||
|
jsmntype_t *wrapping; |
||||
|
#endif |
||||
|
/* How far to indent. */ |
||||
|
size_t indent; |
||||
|
|
||||
|
/* True if we haven't yet put an element in current wrapping */ |
||||
|
bool empty; |
||||
|
|
||||
|
/* Who is writing to this buffer now; NULL if nobody is. */ |
||||
|
struct command *writer; |
||||
|
|
||||
|
/* Who is io_writing from this buffer now: NULL if nobody is. */ |
||||
|
struct io_conn *reader; |
||||
|
struct io_plan *(*reader_cb)(struct io_conn *conn, void *arg); |
||||
|
void *reader_arg; |
||||
|
size_t len_read; |
||||
|
|
||||
|
/* Current command's output. */ |
||||
|
MEMBUF(char) outbuf; |
||||
|
}; |
||||
|
|
||||
|
/* Realloc helper for tal membufs */ |
||||
|
static void *membuf_tal_realloc(struct membuf *mb, |
||||
|
void *rawelems, size_t newsize) |
||||
|
{ |
||||
|
char *p = rawelems; |
||||
|
|
||||
|
tal_resize(&p, newsize); |
||||
|
return p; |
||||
|
} |
||||
|
|
||||
|
struct json_stream *new_json_stream(const tal_t *ctx, struct command *writer) |
||||
|
{ |
||||
|
struct json_stream *js = tal(ctx, struct json_stream); |
||||
|
|
||||
|
js->writer = writer; |
||||
|
js->reader = NULL; |
||||
|
membuf_init(&js->outbuf, |
||||
|
tal_arr(js, char, 64), 64, membuf_tal_realloc); |
||||
|
#if DEVELOPER |
||||
|
js->wrapping = tal_arr(js, jsmntype_t, 0); |
||||
|
#endif |
||||
|
js->indent = 0; |
||||
|
js->empty = true; |
||||
|
return js; |
||||
|
} |
||||
|
|
||||
|
bool json_stream_still_writing(const struct json_stream *js) |
||||
|
{ |
||||
|
return js->writer != NULL; |
||||
|
} |
||||
|
|
||||
|
void json_stream_close(struct json_stream *js, struct command *writer) |
||||
|
{ |
||||
|
/* FIXME: We use writer == NULL for malformed: make writer a void *?
|
||||
|
* I used to assert(writer); here. */ |
||||
|
assert(js->writer == writer); |
||||
|
|
||||
|
js->writer = NULL; |
||||
|
} |
||||
|
|
||||
|
/* FIXME: This, or something prettier (io_replan?) belong in ccan/io! */ |
||||
|
static void adjust_io_write(struct io_conn *conn, ptrdiff_t delta) |
||||
|
{ |
||||
|
conn->plan[IO_OUT].arg.u1.cp += delta; |
||||
|
} |
||||
|
|
||||
|
/* Make sure js->outbuf has room for len */ |
||||
|
static void mkroom(struct json_stream *js, size_t len) |
||||
|
{ |
||||
|
ptrdiff_t delta = membuf_prepare_space(&js->outbuf, len); |
||||
|
|
||||
|
/* If io_write is in progress, we shift it to point to new buffer pos */ |
||||
|
if (js->reader) |
||||
|
adjust_io_write(js->reader, delta); |
||||
|
} |
||||
|
|
||||
|
static void js_written_some(struct json_stream *js) |
||||
|
{ |
||||
|
/* Wake the stream reader. FIXME: Could have a flag here to optimize */ |
||||
|
io_wake(js); |
||||
|
} |
||||
|
|
||||
|
void json_stream_append(struct json_stream *js, const char *str) |
||||
|
{ |
||||
|
size_t len = strlen(str); |
||||
|
|
||||
|
mkroom(js, len); |
||||
|
memcpy(membuf_add(&js->outbuf, len), str, len); |
||||
|
js_written_some(js); |
||||
|
} |
||||
|
|
||||
|
static void json_stream_append_vfmt(struct json_stream *js, |
||||
|
const char *fmt, va_list ap) |
||||
|
{ |
||||
|
size_t fmtlen; |
||||
|
va_list ap2; |
||||
|
|
||||
|
/* Make a copy in case we need it below. */ |
||||
|
va_copy(ap2, ap); |
||||
|
|
||||
|
/* Try printing in place first. */ |
||||
|
fmtlen = vsnprintf(membuf_space(&js->outbuf), |
||||
|
membuf_num_space(&js->outbuf), fmt, ap); |
||||
|
|
||||
|
/* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means
|
||||
|
* chopping off the last character. So if fmtlen == |
||||
|
* membuf_num_space(&jcon->outbuf), the result was truncated! */ |
||||
|
if (fmtlen >= membuf_num_space(&js->outbuf)) { |
||||
|
/* Make room for NUL terminator, even though we don't want it */ |
||||
|
mkroom(js, fmtlen + 1); |
||||
|
vsprintf(membuf_space(&js->outbuf), fmt, ap2); |
||||
|
} |
||||
|
membuf_added(&js->outbuf, fmtlen); |
||||
|
js_written_some(js); |
||||
|
va_end(ap2); |
||||
|
} |
||||
|
|
||||
|
void PRINTF_FMT(2,3) |
||||
|
json_stream_append_fmt(struct json_stream *js, const char *fmt, ...) |
||||
|
{ |
||||
|
va_list ap; |
||||
|
|
||||
|
va_start(ap, fmt); |
||||
|
json_stream_append_vfmt(js, fmt, ap); |
||||
|
va_end(ap); |
||||
|
} |
||||
|
|
||||
|
static void check_fieldname(const struct json_stream *js, |
||||
|
const char *fieldname) |
||||
|
{ |
||||
|
#if DEVELOPER |
||||
|
size_t n = tal_count(js->wrapping); |
||||
|
if (n == 0) |
||||
|
/* Can't have a fieldname if not in anything! */ |
||||
|
assert(!fieldname); |
||||
|
else if (js->wrapping[n-1] == JSMN_ARRAY) |
||||
|
/* No fieldnames in arrays. */ |
||||
|
assert(!fieldname); |
||||
|
else |
||||
|
/* Must have fieldnames in objects. */ |
||||
|
assert(fieldname); |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
static void js_append_indent(struct json_stream *js) |
||||
|
{ |
||||
|
static const char indent_buf[] = " "; |
||||
|
size_t len; |
||||
|
|
||||
|
for (size_t i = 0; i < js->indent * 2; i += len) { |
||||
|
len = js->indent * 2; |
||||
|
if (len > sizeof(indent_buf)-1) |
||||
|
len = sizeof(indent_buf)-1; |
||||
|
/* Use tail of indent_buf string. */ |
||||
|
json_stream_append(js, indent_buf + sizeof(indent_buf) - 1 - len); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void json_start_member(struct json_stream *js, const char *fieldname) |
||||
|
{ |
||||
|
/* Prepend comma if required. */ |
||||
|
if (!js->empty) |
||||
|
json_stream_append(js, ", \n"); |
||||
|
else |
||||
|
json_stream_append(js, "\n"); |
||||
|
|
||||
|
js_append_indent(js); |
||||
|
|
||||
|
check_fieldname(js, fieldname); |
||||
|
if (fieldname) |
||||
|
json_stream_append_fmt(js, "\"%s\": ", fieldname); |
||||
|
js->empty = false; |
||||
|
} |
||||
|
|
||||
|
static void js_indent(struct json_stream *js, jsmntype_t type) |
||||
|
{ |
||||
|
#if DEVELOPER |
||||
|
*tal_arr_expand(&js->wrapping) = type; |
||||
|
#endif |
||||
|
js->empty = true; |
||||
|
js->indent++; |
||||
|
} |
||||
|
|
||||
|
static void js_unindent(struct json_stream *js, jsmntype_t type) |
||||
|
{ |
||||
|
assert(js->indent); |
||||
|
#if DEVELOPER |
||||
|
assert(tal_count(js->wrapping) == js->indent); |
||||
|
assert(js->wrapping[js->indent-1] == type); |
||||
|
tal_resize(&js->wrapping, js->indent-1); |
||||
|
#endif |
||||
|
js->empty = false; |
||||
|
js->indent--; |
||||
|
} |
||||
|
|
||||
|
void json_array_start(struct json_stream *js, const char *fieldname) |
||||
|
{ |
||||
|
json_start_member(js, fieldname); |
||||
|
json_stream_append(js, "["); |
||||
|
js_indent(js, JSMN_ARRAY); |
||||
|
} |
||||
|
|
||||
|
void json_array_end(struct json_stream *js) |
||||
|
{ |
||||
|
json_stream_append(js, "\n"); |
||||
|
js_unindent(js, JSMN_ARRAY); |
||||
|
js_append_indent(js); |
||||
|
json_stream_append(js, "]"); |
||||
|
} |
||||
|
|
||||
|
void json_object_start(struct json_stream *js, const char *fieldname) |
||||
|
{ |
||||
|
json_start_member(js, fieldname); |
||||
|
json_stream_append(js, "{"); |
||||
|
js_indent(js, JSMN_OBJECT); |
||||
|
} |
||||
|
|
||||
|
void json_object_end(struct json_stream *js) |
||||
|
{ |
||||
|
json_stream_append(js, "\n"); |
||||
|
js_unindent(js, JSMN_OBJECT); |
||||
|
js_append_indent(js); |
||||
|
json_stream_append(js, "}"); |
||||
|
} |
||||
|
|
||||
|
void PRINTF_FMT(3,4) |
||||
|
json_add_member(struct json_stream *js, const char *fieldname, |
||||
|
const char *fmt, ...) |
||||
|
{ |
||||
|
va_list ap; |
||||
|
|
||||
|
json_start_member(js, fieldname); |
||||
|
va_start(ap, fmt); |
||||
|
json_stream_append_vfmt(js, fmt, ap); |
||||
|
va_end(ap); |
||||
|
} |
||||
|
|
||||
|
/* This is where we read the json_stream and write it to conn */ |
||||
|
static struct io_plan *json_stream_output_write(struct io_conn *conn, |
||||
|
struct json_stream *js) |
||||
|
{ |
||||
|
/* For when we've just done some output */ |
||||
|
membuf_consume(&js->outbuf, js->len_read); |
||||
|
|
||||
|
/* Get how much we can write out from js */ |
||||
|
js->len_read = membuf_num_elems(&js->outbuf); |
||||
|
|
||||
|
/* Nothing in buffer? */ |
||||
|
if (js->len_read == 0) { |
||||
|
/* We're not doing io_write now, unset. */ |
||||
|
js->reader = NULL; |
||||
|
if (!json_stream_still_writing(js)) |
||||
|
return js->reader_cb(conn, js->reader_arg); |
||||
|
return io_out_wait(conn, js, json_stream_output_write, js); |
||||
|
} |
||||
|
|
||||
|
js->reader = conn; |
||||
|
return io_write(conn, |
||||
|
membuf_elems(&js->outbuf), js->len_read, |
||||
|
json_stream_output_write, js); |
||||
|
} |
||||
|
|
||||
|
struct io_plan *json_stream_output_(struct json_stream *js, |
||||
|
struct io_conn *conn, |
||||
|
struct io_plan *(*cb)(struct io_conn *conn, |
||||
|
void *arg), |
||||
|
void *arg) |
||||
|
{ |
||||
|
assert(!js->reader); |
||||
|
|
||||
|
js->reader_cb = cb; |
||||
|
js->reader_arg = arg; |
||||
|
|
||||
|
js->len_read = 0; |
||||
|
return json_stream_output_write(conn, js); |
||||
|
} |
@ -0,0 +1,97 @@ |
|||||
|
/* lightningd/json_stream.h
|
||||
|
* Helpers for outputting JSON results into a membuf. |
||||
|
*/ |
||||
|
#ifndef LIGHTNING_LIGHTNINGD_JSON_STREAM_H |
||||
|
#define LIGHTNING_LIGHTNINGD_JSON_STREAM_H |
||||
|
#include "config.h" |
||||
|
#include <ccan/membuf/membuf.h> |
||||
|
#include <ccan/short_types/short_types.h> |
||||
|
#include <ccan/tal/tal.h> |
||||
|
#include <ccan/typesafe_cb/typesafe_cb.h> |
||||
|
#include <stdbool.h> |
||||
|
#include <stddef.h> |
||||
|
#include <stdint.h> |
||||
|
|
||||
|
struct command; |
||||
|
struct io_conn; |
||||
|
struct json_stream; |
||||
|
|
||||
|
/**
|
||||
|
* new_json_stream - create a new JSON stream. |
||||
|
* @ctx: tal context for allocation. |
||||
|
* @writer: object responsible for writing to this stream. |
||||
|
*/ |
||||
|
struct json_stream *new_json_stream(const tal_t *ctx, struct command *writer); |
||||
|
|
||||
|
/**
|
||||
|
* json_stream_close - finished writing to a JSON stream. |
||||
|
* @js: the json_stream. |
||||
|
* @writer: object responsible for writing to this stream. |
||||
|
*/ |
||||
|
void json_stream_close(struct json_stream *js, struct command *writer); |
||||
|
|
||||
|
/**
|
||||
|
* json_stream_still_writing - is someone currently writing to this stream? |
||||
|
* @js: the json_stream. |
||||
|
* |
||||
|
* Has this json_stream not been closed yet? |
||||
|
*/ |
||||
|
bool json_stream_still_writing(const struct json_stream *js); |
||||
|
|
||||
|
|
||||
|
/* '"fieldname" : [ ' or '[ ' if fieldname is NULL */ |
||||
|
void json_array_start(struct json_stream *js, const char *fieldname); |
||||
|
/* '"fieldname" : { ' or '{ ' if fieldname is NULL */ |
||||
|
void json_object_start(struct json_stream *ks, const char *fieldname); |
||||
|
/* ' ], ' */ |
||||
|
void json_array_end(struct json_stream *js); |
||||
|
/* ' }, ' */ |
||||
|
void json_object_end(struct json_stream *js); |
||||
|
|
||||
|
/**
|
||||
|
* json_stream_append - literally insert this string into the json_stream. |
||||
|
* @js: the json_stream. |
||||
|
* @str: the string. |
||||
|
*/ |
||||
|
void json_stream_append(struct json_stream *js, const char *str); |
||||
|
|
||||
|
/**
|
||||
|
* json_stream_append_fmt - insert formatted string into the json_stream. |
||||
|
* @js: the json_stream. |
||||
|
* @fmt...: the printf-style format |
||||
|
*/ |
||||
|
void PRINTF_FMT(2,3) |
||||
|
json_stream_append_fmt(struct json_stream *js, const char *fmt, ...); |
||||
|
|
||||
|
/**
|
||||
|
* json_add_member - add a generic member. |
||||
|
* @js: the json_stream. |
||||
|
* @fieldname: optional fieldname. |
||||
|
* @fmt...: the printf-style format |
||||
|
*/ |
||||
|
void PRINTF_FMT(3,4) |
||||
|
json_add_member(struct json_stream *js, const char *fieldname, |
||||
|
const char *fmt, ...); |
||||
|
|
||||
|
/**
|
||||
|
* json_stream_output - start writing out a json_stream to this conn. |
||||
|
* @js: the json_stream |
||||
|
* @conn: the io_conn to write out to. |
||||
|
* @cb: the callback to call once it's all written. |
||||
|
* @arg: the argument to @cb |
||||
|
*/ |
||||
|
#define json_stream_output(js, conn, cb, arg) \ |
||||
|
json_stream_output_((js), (conn), \ |
||||
|
typesafe_cb_preargs(struct io_plan *, \ |
||||
|
void *, \ |
||||
|
(cb), (arg), \ |
||||
|
struct io_conn *), \ |
||||
|
(arg)) |
||||
|
|
||||
|
struct io_plan *json_stream_output_(struct json_stream *js, |
||||
|
struct io_conn *conn, |
||||
|
struct io_plan *(*cb)(struct io_conn *conn, |
||||
|
void *arg), |
||||
|
void *arg); |
||||
|
|
||||
|
#endif /* LIGHTNING_LIGHTNINGD_JSON_STREAM_H */ |
Loading…
Reference in new issue