json: Support streaming JSON messages

It turns out we were heavily relying on the fact that after each message from
the client there'd be a flush, and that there would not be anything after the
JSON object we read. This will no longer be the case once we start streaming
things or we are very quick in issuing the JSON-RPC requests.

This just takes one of the error paths (incomplete read) and makes it into a
successful path if we have indeed read a full root element.
This commit is contained in:
Christian Decker 2018-09-25 07:47:23 +02:00 committed by Rusty Russell
parent fc12f65a3d
commit c3f433ec66
2 changed files with 47 additions and 4 deletions

View File

@ -188,23 +188,34 @@ jsmntok_t *json_parse_input(const char *input, int len, bool *valid)
int ret; int ret;
toks = tal_arr(input, jsmntok_t, 10); toks = tal_arr(input, jsmntok_t, 10);
toks[0].type = JSMN_UNDEFINED;
again:
jsmn_init(&parser); jsmn_init(&parser);
again:
ret = jsmn_parse(&parser, input, len, toks, tal_count(toks) - 1); ret = jsmn_parse(&parser, input, len, toks, tal_count(toks) - 1);
switch (ret) { switch (ret) {
case JSMN_ERROR_INVAL: case JSMN_ERROR_INVAL:
*valid = false; *valid = false;
return tal_free(toks); return tal_free(toks);
case JSMN_ERROR_PART:
*valid = true;
return tal_free(toks);
case JSMN_ERROR_NOMEM: case JSMN_ERROR_NOMEM:
tal_resize(&toks, tal_count(toks) * 2); tal_resize(&toks, tal_count(toks) * 2);
goto again; goto again;
} }
/* Check whether we read at least one full root element, i.e., root
* element has its end set. */
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
*valid = true;
return tal_free(toks);
}
/* If we read a partial element at the end of the stream we'll get a
* ret=JSMN_ERROR_PART, but due to the previous check we know we read at
* least one full element, so count tokens that are part of this root
* element. */
ret = json_next(toks) - toks;
/* Cut to length and return. */ /* Cut to length and return. */
*valid = true; *valid = true;
tal_resize(&toks, ret + 1); tal_resize(&toks, ret + 1);

View File

@ -145,6 +145,37 @@ static void test_json_partial(void)
tal_free(ctx); tal_free(ctx);
} }
/* Test that we can segment and parse a stream of json objects correctly. */
static void test_json_stream(void)
{
bool valid;
char *input, *talstr;
jsmntok_t *toks;
/* Multiple full messages in a single buffer (happens when buffer
* boundary coincides with message boundary, or read returned after
* timeout. */
input = "{\"x\":\"x\"}{\"y\":\"y\"}";
talstr = tal_strndup(NULL, input, strlen(input));
toks = json_parse_input(talstr, strlen(talstr), &valid);
assert(toks);
assert(tal_count(toks) == 4);
assert(toks[0].start == 0 && toks[0].end == 9);
assert(valid);
tal_free(talstr);
/* Multiple messages, and the last one is partial, far more likely than
* accidentally getting the boundaries to match. */
input = "{\"x\":\"x\"}{\"y\":\"y\"}{\"z\":\"z";
talstr = tal_strndup(NULL, input, strlen(input));
toks = json_parse_input(talstr, strlen(talstr), &valid);
assert(toks);
assert(tal_count(toks) == 4);
assert(toks[0].start == 0 && toks[0].end == 9);
assert(valid);
tal_free(talstr);
}
int main(void) int main(void)
{ {
setup_locale(); setup_locale();
@ -153,6 +184,7 @@ int main(void)
test_json_filter(); test_json_filter();
test_json_escape(); test_json_escape();
test_json_partial(); test_json_partial();
test_json_stream();
assert(!taken_any()); assert(!taken_any());
take_cleanup(); take_cleanup();
} }