New configuration option: CLICON_RESTCONF_PRETTY

Changed RESTCONF GET to return object referenced. ie, GET /restconf/data/X returns X.
This commit is contained in:
Olof hagsand 2018-01-07 18:01:42 +01:00
parent cefaf4717f
commit 363bd5d19d
7 changed files with 169 additions and 102 deletions

View file

@ -10,6 +10,9 @@
* Configuration files (non-XML) prior to 3.3.3. As enabled with `configure --with-config-compat`. The template clicon.conf.cpp files are also removed.
* Clixon XML C-lib prior to 3.4.0. As enabled with `configure --with-xml-compat`
* new configuration option: CLICON_RESTCONF_PRETTY
* Changed RESTCONF GET to return object referenced. ie, GET /restconf/data/X returns X.
### Corrected Bugs
* Corrected "No yang spec" printed on tty on leafref CLI usage

View file

@ -88,7 +88,6 @@
* @param[out] commands vector of function pointers to callback functions
* @param[out] helptxt vector of pointers to helptexts
* @see cli_expand_var_generate This is where arg is generated
* XXX: helptexts?
*/
int
expand_dbvar(void *h,

View file

@ -9,6 +9,8 @@ There is currently (2017) a [RFC 8040: RESTCONF Protocol](https://tools.ietf.org
including:
- query parameters (section 4.9)
- notifications (sec 6)
- GET /restconf/ (sec 3.3)
- GET /restconf/yang-library-version (sec 3.3.3)
- only rudimentary error reporting exists (sec 7)
### Installation using Nginx

View file

@ -140,56 +140,22 @@ api_data_options(clicon_handle h,
return 0;
}
/*! Generic GET (both HEAD and GET)
/*! Return error on get/head request
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] xerr XML error message from backend
*/
static int
api_data_get_gen(clicon_handle h,
api_data_get_err(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int head)
cxobj *xerr)
{
int retval = -1;
cbuf *path = NULL;
cbuf *cbx = NULL;
cxobj **vec = NULL;
yang_spec *yspec;
cxobj *xret = NULL;
cxobj *xerr;
cbuf *cbj = NULL;
cxobj *xtag;
cbuf *cbj = NULL;;
int code;
const char *reason_phrase;
char *media_accept;
int use_xml = 0; /* By default use JSON */
clicon_debug(1, "%s", __FUNCTION__);
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL)
goto done;
cprintf(path, "/");
if (api_path2xpath_cvv(yspec, pcvec, pi, path) < 0){
notfound(r);
goto done;
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (clicon_rpc_get(h, cbuf_get(path), &xret) < 0){
notfound(r);
goto done;
}
#if 0 /* DEBUG */
{
cbuf *cb = cbuf_new();
xml2json_cbuf(cb, xret, 1);
clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
#endif
if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){
if ((cbj = cbuf_new()) == NULL)
goto done;
if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){
@ -214,8 +180,99 @@ api_data_get_gen(clicon_handle h,
FCGX_FPrintF(r->out, " %s", cbuf_get(cbj));
FCGX_FPrintF(r->out, " }\r\n");
FCGX_FPrintF(r->out, "}\r\n");
retval = 0;
done:
if (cbj)
cbuf_free(cbj);
return retval;
}
/*! Generic GET (both HEAD and GET)
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] head If 1 is HEAD, otherwise GET
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang-data+xml
* Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
* Netconf: <get-config>, <get>
*/
static int
api_data_get2(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int head)
{
int retval = -1;
cbuf *cbpath = NULL;
char *path;
cbuf *cbx = NULL;
yang_spec *yspec;
cxobj *xret = NULL;
cxobj *xerr;
char *media_accept;
int use_xml = 0; /* By default use JSON */
cxobj **xvec = NULL;
size_t xlen;
int pretty;
int i;
cxobj *x;
clicon_debug(1, "%s", __FUNCTION__);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
yspec = clicon_dbspec_yang(h);
if ((cbpath = cbuf_new()) == NULL)
goto done;
cprintf(cbpath, "/");
clicon_debug(1, "%s pi:%d", __FUNCTION__, pi);
/* We know "data" is element pi-1 */
if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){
notfound(r);
goto done;
}
path = cbuf_get(cbpath);
clicon_debug(1, "%s path:%s", __FUNCTION__, path);
if (clicon_rpc_get(h, path, &xret) < 0){
notfound(r);
goto done;
}
/* We get return via netconf which is complete tree from root
* We need to cut that tree to only the object.
*/
#if 1 /* DEBUG */
{
cbuf *cb = cbuf_new();
clicon_xml2cbuf(cb, xret, 0, 0);
clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
#endif
/* Check if error return */
if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){
if (api_data_get_err(h, r, xerr) < 0)
goto done;
goto ok;
}
/* Normal return, no error */
if ((cbx = cbuf_new()) == NULL)
goto done;
FCGX_SetExitStatus(200, r->out); /* OK */
@ -223,18 +280,40 @@ api_data_get_gen(clicon_handle h,
FCGX_FPrintF(r->out, "\r\n");
if (head)
goto ok;
clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret));
clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret));
if (path==NULL || strcmp(path,"/")==0){ /* Special case: data root */
if (use_xml){
if (clicon_xml2cbuf(cbx, xret, 0, 1) < 0) /* Dont print top object? */
if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */
goto done;
}
else{
vec = xml_childvec_get(xret);
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
if (xml2json_cbuf(cbx, xret, pretty) < 0)
goto done;
}
}
else{
if (xpath_vec(xret, path, &xvec, &xlen) < 0)
goto done;
clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, xlen);
for (i=0; i<xlen; i++){
x = xvec[i];
#if 1 /* DEBUG */
{
cbuf *cb = cbuf_new();
clicon_xml2cbuf(cb, x, 0, 0);
clicon_debug(1, "%s x:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
#endif
if (use_xml){
if (clicon_xml2cbuf(cbx, x, 0, pretty) < 0) /* Dont print top object? */
goto done;
}
else{
if (xml2json_cbuf(cbx, x, pretty) < 0)
goto done;
}
}
}
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
@ -244,12 +323,12 @@ api_data_get_gen(clicon_handle h,
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (cbj)
cbuf_free(cbj);
if (path)
cbuf_free(path);
if (cbpath)
cbuf_free(cbpath);
if (xret)
xml_free(xret);
if (xvec)
free(xvec);
return retval;
}
@ -271,7 +350,7 @@ api_data_head(clicon_handle h,
int pi,
cvec *qvec)
{
return api_data_get_gen(h, r, pcvec, pi, qvec, 1);
return api_data_get2(h, r, pcvec, pi, qvec, 1);
}
/*! REST GET method
@ -304,7 +383,7 @@ api_data_get(clicon_handle h,
int pi,
cvec *qvec)
{
return api_data_get_gen(h, r, pcvec, pi, qvec, 0);
return api_data_get2(h, r, pcvec, pi, qvec, 0);
}
/*! REST POST method

View file

@ -549,6 +549,7 @@ match_base_child(cxobj *x0,
char **keyval = NULL;
char **keyvec = NULL;
int i;
int yorder;
*x0cp = NULL; /* return value */
switch (yc->ys_keyword){
@ -591,52 +592,22 @@ match_base_child(cxobj *x0,
break;
}
/* Get match */
{
int yorder;
/* XXX: No we cant do this. on uppermost layer it can look like this:
* config
* ximport-----ymod = ietf
* interfaces--ymod = example
* Which means yang order can be different for different children in the
* same childvec.
*/
if (xml_child_sort==0)
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
else{
#if 1
if (xml_child_nr(x0)==0 || xml_spec(xml_child_i(x0,0))!=NULL){
yorder = yang_order(yc);
*x0cp = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
}
else{
#if 1 /* This is just a warning, but a catcher for when xml tree is not
populated with yang spec. If you see this, a previous inovation of,
populated with yang spec. If you see this, a previous invacation of,
for example xml_spec_populate() may be missing
*/
clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__);
#endif
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
}
#else
cxobj *xx;
*x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval);
if (xml_child_nr(x0) && xml_spec(xml_child_i(x0,0))!=NULL){
xx = xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
if (xx!=*x0cp){
clicon_log(LOG_WARNING, "%s mismatch", __FUNCTION__);
fprintf(stderr, "mismatch\n");
xml_search(x0, xml_name(x1c), yorder, yc->ys_keyword, keynr, keyvec, keyval);
assert(0);
}
}
else
clicon_log(LOG_WARNING, "%s No yspec", __FUNCTION__);
#endif
}
}
ok:
retval = 0;

View file

@ -11,7 +11,7 @@ module clixon-config {
description
"Clixon configuration file
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON
@ -130,6 +130,19 @@ module clixon-config {
"FastCGI unix socket. Should be specified in webserver
Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock";
}
leaf CLICON_RESTCONF_PRETTY {
type boolean;
default true;
description
"Restconf return value pretty print.
Restconf clients may add HTTP header:
Accept: application/yang-data+json, or
Accept: application/yang-data+xml
to get return value in XML or JSON.
RFC 8040 examples print XML and JSON in pretty-printed form.
Setting this value to false makes restconf return not pretty-printed
which may be desirable for performance or tests";
}
leaf CLICON_CLI_DIR {
type string;
description