* Major rewrite of event streams

* If you used old event callbacks API, you need to switch to the streams API
    * See clixon_stream.[ch]
  * Old streams API which needs to be removed include:
    * clicon_log_register_callback()
    * subscription_add() --> stream_register()
    * backend_notify() and backend_notify_xml() - use stream_notify() instead
* Example uses "NETCONF" stream instead of "ROUTING"
* Added timeout option -t for clixon_netconf - quit after max time.
This commit is contained in:
Olof hagsand 2018-09-30 14:51:30 +02:00
parent d7fbe75c9e
commit 98f3cd0e32
31 changed files with 597 additions and 635 deletions

View file

@ -13,10 +13,15 @@ Define nginx config file: /etc/nginx/sites-available/default
```
server {
...
location / {
root /usr/share/nginx/html/restconf;
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
location /restconf {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
location /stream { # for restconf notifications
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
```

View file

@ -36,6 +36,12 @@
#ifndef _RESTCONF_LIB_H_
#define _RESTCONF_LIB_H_
/*
* Constants
*/
#define RESTCONF_API "restconf"
#define RESTCONF_STREAM "stream"
/*
* Prototypes (also in clixon_restconf.h)
*/

View file

@ -89,12 +89,6 @@
*/
#define RESTCONF_WELL_KNOWN "/.well-known/host-meta"
#define RESTCONF_API "restconf"
#define RESTCONF_API_ROOT "/restconf"
#define RESTCONF_STREAM_ROOT "/stream"
#define RESTCONF_LOGFILE "/www-data/clixon_restconf.log"
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
@ -531,9 +525,11 @@ main(int argc,
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
/* Create handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
_CLICON_HANDLE = h; /* for termination handling */
while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
switch (c) {
@ -634,6 +630,8 @@ main(int argc,
yang_spec_append(h, CLIXON_DATADIR, "ietf-yang-library", NULL)< 0)
goto done;
if (stream_register(h, "NETCONF", "default NETCONF event stream") < 0)
goto done;
/* Call start function in all plugins before we go interactive
Pass all args after the standard options to plugin_start
*/
@ -667,9 +665,9 @@ main(int argc,
clicon_debug(1, "------------");
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
clicon_debug(1, "path: %s", path);
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0)
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, r); /* This is the function */
else if (strncmp(path, RESTCONF_STREAM_ROOT, strlen(RESTCONF_STREAM_ROOT)) == 0) {
else if (strncmp(path, "/" RESTCONF_STREAM, strlen("/" RESTCONF_STREAM)) == 0) {
api_stream(h, r);
}
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {

View file

@ -64,6 +64,32 @@
#include <fcgi_stdio.h> /* Need to be after clixon_xml-h due to attribute format */
static int
restconf_stream(clicon_handle h,
FCGX_Request *r,
event_stream_t *es)
{
int retval = -1;
clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n");
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Connection: keep-alive\r\n");
FCGX_FPrintF(r->out, "X-Accel-Buffering: no\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "Here is output\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
sync();
sleep(1);
FCGX_FPrintF(r->out, "Here is output 2\r\n");
retval = 0;
// done:
return retval;
}
/* restconf */
#include "restconf_lib.h"
#include "restconf_stream.h"
@ -73,13 +99,122 @@
*/
int
api_stream(clicon_handle h,
FCGX_Request *r)
FCGX_Request *r)
{
int retval = -1;
char *path;
char *query;
char *method;
char **pvec = NULL;
int pn;
cvec *qvec = NULL;
cvec *dvec = NULL;
cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL;
char *data;
int authenticated = 0;
char *media_accept;
char *media_content_type;
int pretty;
int parse_xml = 0; /* By default expect and parse JSON */
int use_xml = 0; /* By default use JSON */
cbuf *cbret = NULL;
cxobj *xret = NULL;
cxobj *xerr;
event_stream_t *es;
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("REQUEST_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* get xml/json in put and output */
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (media_accept && strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp);
if (media_content_type &&
strcmp(media_content_type, "application/yang-data+xml")==0)
parse_xml++;
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
if (pn != 3){
notfound(r);
goto ok;
}
if (strlen(pvec[0]) != 0){
retval = notfound(r);
goto done;
}
if (strcmp(pvec[1], RESTCONF_STREAM)){
retval = notfound(r);
goto done;
}
test(r, 1);
if ((method = pvec[2]) == NULL){
retval = notfound(r);
goto done;
}
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = readdata(r)) == NULL)
goto done;
data = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((authenticated = clixon_plugin_auth(h, r)) < 0)
goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
/* If set but no user, we set a dummy user */
if (authenticated){
if (clicon_username_get(h) == NULL)
clicon_username_set(h, "none");
}
else{
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
if (api_return_err(h, r, xerr, pretty, use_xml) < 0)
goto done;
goto ok;
}
goto ok;
}
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if ((es = stream_find(h, method)) == NULL){
retval = notfound(r);
goto done;
}
if (restconf_stream(h, r, es) < 0)
goto done;
ok:
retval = 0;
// done:
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pvec)
free(pvec);
if (dvec)
cvec_free(dvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (xret)
xml_free(xret);
return retval;
}