Skip to content

Commit

Permalink
ubus: add new RESTful API
Browse files Browse the repository at this point in the history
Initial uhttpd ubus API was fully based on JSON-RPC. That restricted it
from supporting ubus notifications that don't fit its model.

Notifications require protocol that allows server to send data without
being polled. There are two candidates for that:
1. Server-sent events
2. WebSocket

The later one is overcomplex for this simple task so ideally uhttps ubus
should support text-based server-sent events. It's not possible with
JSON-RPC without violating it. Specification requires server to reply
with Response object. Replying with text/event-stream is not allowed.

All above led to designing new API that:
1. Uses GET and POST requests
2. Makes use of RESTful URLs
3. Uses JSON-RPC in cleaner form and only for calling ubus methods

This new API allows:
1. Listing all ubus objects and their methods using GET <prefix>/list
2. Listing object methods using GET <prefix>/list/<path>
3. Listening to object notifications with GET <prefix>/subscribe/<path>
4. Calling ubus methods using POST <prefix>/call/<path>

JSON-RPC custom protocol was also simplified to:
1. Use "method" member for ubus object method name
   It was possible thanks to using RESTful URLs. Previously "method"
   had to be "list" or "call".
2. Reply with Error object on ubus method call error
   This simplified "result" member format as it doesn't need to contain
   ubus result code anymore.

This patch doesn't break or change the old API. The biggest downside of
the new API is no support for batch requests. It's cost of using RESTful
URLs. It should not matter much as uhttpd supports keep alive.

Example usages:

1. Getting all objects and their methods:
$ curl http://192.168.1.1/ubus/list
{
	"dhcp": {
		"ipv4leases": {

		},
		"ipv6leases": {

		}
	},
	"log": {
		"read": {
			"lines": "number",
			"stream": "boolean",
			"oneshot": "boolean"
		},
		"write": {
			"event": "string"
		}
	}
}

2. Getting object methods:
$ curl http://192.168.1.1/ubus/list/log
{
	"read": {
		"lines": "number",
		"stream": "boolean",
		"oneshot": "boolean"
	},
	"write": {
		"event": "string"
	}
}

3. Subscribing to notifications:
$ curl http://192.168.1.1/ubus/subscribe/foo
event: status
data: {"count":5}

4. Calling ubus object method:
$ curl -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "login",
    "params": {"username": "root", "password": "password" }
}' http://192.168.1.1/ubus/call/session
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": {
		"ubus_rpc_session": "01234567890123456789012345678901",
		(...)
	}
}

$ curl -H 'Authorization: Bearer 01234567890123456789012345678901' -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "write",
    "params": {"event": "Hello world" }
}' http://192.168.1.1/ubus/call/log
{
	"jsonrpc": "2.0",
	"id": 1,
	"result": null
}

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
  • Loading branch information
Rafał Miłecki committed Sep 15, 2020
1 parent fe1888f commit 1172357
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 22 deletions.
8 changes: 7 additions & 1 deletion main.c
Expand Up @@ -159,6 +159,7 @@ static int usage(const char *name)
" -U file Override ubus socket path\n"
" -a Do not authenticate JSON-RPC requests against UBUS session api\n"
" -X Enable CORS HTTP headers on JSON-RPC api\n"
" -e Events subscription reconnection time (retry value)\n"
#endif
" -x string URL prefix for CGI handler, default is '/cgi-bin'\n"
" -y alias[=path] URL alias handle\n"
Expand Down Expand Up @@ -262,7 +263,7 @@ int main(int argc, char **argv)
init_defaults_pre();
signal(SIGPIPE, SIG_IGN);

while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:fh:H:I:i:K:k:L:l:m:N:n:P:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
while ((ch = getopt(argc, argv, "A:aC:c:Dd:E:e:fh:H:I:i:K:k:L:l:m:N:n:P:p:qRr:Ss:T:t:U:u:Xx:y:")) != -1) {
switch(ch) {
#ifdef HAVE_TLS
case 'C':
Expand Down Expand Up @@ -490,11 +491,16 @@ int main(int argc, char **argv)
case 'X':
conf.ubus_cors = 1;
break;

case 'e':
conf.events_retry = atoi(optarg);
break;
#else
case 'a':
case 'u':
case 'U':
case 'X':
case 'e':
fprintf(stderr, "uhttpd: UBUS support not compiled, "
"ignoring -%c\n", ch);
break;
Expand Down

0 comments on commit 1172357

Please sign in to comment.