RESTCONF notification for native mode
This commit is contained in:
parent
7a842846db
commit
1336a4ccfb
14 changed files with 282 additions and 533 deletions
|
|
@ -15,6 +15,9 @@ Expected: June 2024
|
|||
|
||||
### Features
|
||||
|
||||
* RESTCONF notification for native mode
|
||||
* Previously only for FCGI
|
||||
* Limitations, ie only HTTP/1, regular subscription + stop-time
|
||||
* Optimization of yang schema mount: share yang-specs if equal
|
||||
* Changed datastore modstate to be last in file, as prior to 7.0
|
||||
* New: Event priority. Backend socket has higher prio
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ LEX = @LEX@
|
|||
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
|
||||
|
||||
ifeq ($(LINKAGE),dynamic)
|
||||
CPPFLAGS += -fPIC
|
||||
endif
|
||||
|
|
@ -100,7 +99,6 @@ APPL = clixon_restconf
|
|||
|
||||
# Common source - not accessible from plugin - independent of restconf package (fcgi|native)
|
||||
APPSRC =
|
||||
APPSRC += restconf_api.c # maybe empty
|
||||
APPSRC += restconf_err.c
|
||||
APPSRC += restconf_methods.c
|
||||
APPSRC += restconf_methods_post.c
|
||||
|
|
|
|||
|
|
@ -4,17 +4,25 @@
|
|||
* [Nginx](#nginx)
|
||||
* [Streams](#streams)
|
||||
* [Nchan Streams](#nchan)
|
||||
* [Debugging](#debugging)
|
||||
* [Debugging](#debugging)
|
||||
|
||||
There are two installation instructions: for native and nginx.
|
||||
|
||||
## Native
|
||||
|
||||
Native with http1 and http2 is the main variant, with most regression testing.
|
||||
|
||||
Configure clixon with native restconf:
|
||||
```
|
||||
./configure --with-restconf=native
|
||||
```
|
||||
|
||||
You can disable http1 and http2:
|
||||
```
|
||||
--disable-http1 Disable native http/1.1 (ie http/2 only)
|
||||
--disable-nghttp2 Disable native http/2 using libnghttp2 (ie http/1 only)
|
||||
```
|
||||
|
||||
Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
|
||||
```
|
||||
sudo usermod -a -G clicon www-data
|
||||
|
|
@ -164,6 +172,8 @@ See (stream tests)[../test/test_streams.sh] for more examples.
|
|||
|
||||
## Nchan
|
||||
|
||||
This is not supported
|
||||
|
||||
As an alternative streams implementation, Nginx/Nchan can be used.
|
||||
Nginx uses pub/sub channels and can be configured in a variety of
|
||||
ways. The following uses a simple variant with one generic subscription
|
||||
|
|
@ -247,3 +257,16 @@ You can set debug level of the backend via restconf:
|
|||
```
|
||||
curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-lib:input":{"level":1}}' http://localhost/restconf/operations/clixon-lib:debug
|
||||
```
|
||||
|
||||
## Code structure
|
||||
|
||||
Due to the native and fcgi variants, and also native http1/http2, the
|
||||
source file structure is complex.
|
||||
|
||||
There are the following blocks of files:
|
||||
|
||||
* COMMON: Common code, such as HTTP methods processing, error and common lib functions
|
||||
* FCGI: Top-level main, stream and low-level lib (as defined by restconf_api.h)
|
||||
* NATIVE-COMMON: Top-level main, stream and low-level lib
|
||||
* NATIVE-HTTP1: Native for HTTP/1 only
|
||||
* NATIVE-HTTP2: Native for Libnghttp2 only
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2009-2019 Olof Hagsand
|
||||
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
* Generic restconf API functions
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <syslog.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/stat.h> /* chmod */
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clixon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
/* restconf */
|
||||
#include "restconf_lib.h"
|
||||
#include "restconf_api.h"
|
||||
|
||||
/* empty */
|
||||
|
|
@ -67,9 +67,7 @@
|
|||
#include "clixon_http1_parse.h"
|
||||
#include "restconf_http1.h"
|
||||
#include "clixon_http_data.h"
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
#include "restconf_stream.h"
|
||||
#endif
|
||||
|
||||
/* Size of xml read buffer */
|
||||
#define BUFLEN 1024
|
||||
|
|
@ -453,13 +451,11 @@ restconf_http1_path_root(clixon_handle h,
|
|||
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||
goto done;
|
||||
}
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
else if (api_path_is_stream(h)){
|
||||
restconf_socket *rs = rc->rc_socket;
|
||||
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
sd->sd_code = 404; /* catch all without body/media */
|
||||
fail:
|
||||
|
|
|
|||
|
|
@ -76,9 +76,7 @@
|
|||
#ifdef HAVE_HTTP1
|
||||
#include "restconf_http1.h"
|
||||
#endif
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
#include "restconf_stream.h"
|
||||
#endif
|
||||
|
||||
/* Forward */
|
||||
static int restconf_idle_cb(int fd, void *arg);
|
||||
|
|
@ -384,7 +382,8 @@ restconf_connection_sanity(clixon_handle h,
|
|||
}
|
||||
|
||||
/* Write buf to socket
|
||||
* see also this function in restcont_api_openssl.c
|
||||
*
|
||||
* see also this function in restconf_api_openssl.c
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] buf Buffer to write
|
||||
* @param[in] buflen Length of buffer
|
||||
|
|
@ -394,7 +393,7 @@ restconf_connection_sanity(clixon_handle h,
|
|||
* @retval 0 OK, but socket write returned error, caller should close rc
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
int
|
||||
native_buf_write(clixon_handle h,
|
||||
char *buf,
|
||||
size_t buflen,
|
||||
|
|
@ -1081,11 +1080,9 @@ restconf_connection_close1(restconf_conn *rc)
|
|||
if (restconf_callhome_timer(rsock, 1) < 0)
|
||||
goto done;
|
||||
}
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
if (rc->rc_event_stream){
|
||||
stream_close(rc->rc_h, rc);
|
||||
}
|
||||
#endif
|
||||
retval = 0;
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
|
||||
|
|
|
|||
|
|
@ -121,9 +121,7 @@ typedef struct restconf_conn {
|
|||
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
|
||||
struct timeval rc_t; /* Timestamp of last read/write activity, used by callhome
|
||||
idle-timeout algorithm */
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
int rc_event_stream; /* Event notification stream socket (maybe in sd?) */
|
||||
#endif
|
||||
} restconf_conn;
|
||||
|
||||
/* Restconf per socket handle
|
||||
|
|
@ -182,6 +180,7 @@ int ssl_x509_name_oneline(SSL *ssl, char **oneline);
|
|||
|
||||
int restconf_close_ssl_socket(restconf_conn *rc, const char *callfn, int sslerr0);
|
||||
int restconf_connection_sanity(clixon_handle h, restconf_conn *rc, restconf_stream_data *sd);
|
||||
int native_buf_write(clixon_handle h, char *buf, size_t buflen, restconf_conn *rc, const char *callfn);
|
||||
restconf_native_handle *restconf_native_handle_get(clixon_handle h);
|
||||
int restconf_connection(int s, void *arg);
|
||||
int restconf_ssl_accept_client(clixon_handle h, int s, restconf_socket *rsock, restconf_conn **rcp);
|
||||
|
|
@ -190,6 +189,8 @@ int restconf_callhome_timer(restconf_socket *rsock, int status);
|
|||
int restconf_socket_extract(clixon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock,
|
||||
char **namespace, char **address, char **addrtype, uint16_t *port);
|
||||
|
||||
|
||||
|
||||
#endif /* _RESTCONF_NATIVE_H_ */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -85,9 +85,7 @@
|
|||
#include "restconf_err.h"
|
||||
#include "restconf_root.h"
|
||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
#include "restconf_stream.h"
|
||||
#endif
|
||||
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
|
||||
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
|
||||
#include "clixon_http_data.h"
|
||||
|
|
@ -341,13 +339,11 @@ restconf_nghttp2_path(restconf_stream_data *sd)
|
|||
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||
goto done;
|
||||
}
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
else if (api_path_is_stream(h)){
|
||||
restconf_socket *rs = rc->rc_socket;
|
||||
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -497,9 +493,7 @@ http2_exec(restconf_conn *rc,
|
|||
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
||||
|| api_path_is_restconf(rc->rc_h)
|
||||
|| api_path_is_data(rc->rc_h)
|
||||
#ifdef RESTCONF_NATIVE_STREAM
|
||||
|| api_path_is_stream(rc->rc_h)
|
||||
#endif
|
||||
) {
|
||||
clixon_debug(CLIXON_DBG_RESTCONF, "path found");
|
||||
if (restconf_nghttp2_path(sd) < 0)
|
||||
|
|
|
|||
|
|
@ -179,6 +179,8 @@ restconf_subscription(clixon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
/* Setting up stream */
|
||||
if (restconf_reply_header(req, "Server", "clixon") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_header(req, "Server", "clixon") < 0)
|
||||
goto done;
|
||||
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
|
||||
|
|
@ -187,10 +189,9 @@ restconf_subscription(clixon_handle h,
|
|||
goto done;
|
||||
if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
|
||||
goto done;
|
||||
#ifndef RESTCONF_NATIVE_STREAM
|
||||
/* Must be there for FCGI caching */
|
||||
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
|
||||
goto done;
|
||||
#endif
|
||||
if (restconf_reply_send(req, 201, NULL, 0) < 0)
|
||||
goto done;
|
||||
*sp = s;
|
||||
|
|
@ -204,3 +205,120 @@ restconf_subscription(clixon_handle h,
|
|||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Process a stream request
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||
* @param[in] timeout Stream timeout
|
||||
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
api_stream(clixon_handle h,
|
||||
void *req,
|
||||
cvec *qvec,
|
||||
int timeout,
|
||||
int *finish)
|
||||
{
|
||||
int retval = -1;
|
||||
char *path = NULL;
|
||||
char **pvec = NULL;
|
||||
int pn;
|
||||
cvec *pcvec = NULL; /* for rest api */
|
||||
cxobj *xerr = NULL;
|
||||
char *streampath;
|
||||
int pretty;
|
||||
int besock = -1;
|
||||
restconf_media media_reply = YANG_DATA_XML;
|
||||
char *media_str = NULL;
|
||||
char *stream_name;
|
||||
int ret;
|
||||
|
||||
clixon_debug(CLIXON_DBG_STREAM, "");
|
||||
if (req == NULL){
|
||||
clixon_err(OE_RESTCONF, EINVAL, "req is NULL");
|
||||
goto done;
|
||||
}
|
||||
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
||||
if ((path = restconf_uripath(h)) == NULL)
|
||||
goto done;
|
||||
pretty = restconf_pretty_get(h);
|
||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||
goto done;
|
||||
/* Sanity check of path. Should be /stream/<name> */
|
||||
if (pn != 3){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Get media for output (proactive negotiation) RFC7231 by using
|
||||
*/
|
||||
media_str = restconf_param_get(h, "HTTP_ACCEPT");
|
||||
if (media_str == NULL){
|
||||
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Accept only text_event-stream or */
|
||||
if (strcmp(media_str, "*/*") != 0 &&
|
||||
strcmp(media_str, "text/event-stream") != 0){
|
||||
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (strlen(pvec[0]) != 0){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (strcmp(pvec[1], streampath)){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if ((stream_name = pvec[2]) == NULL){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
|
||||
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
|
||||
goto done;
|
||||
/* If present, check credentials. See "plugin_credentials" in plugin
|
||||
* See RFC 8040 section 2.5
|
||||
*/
|
||||
if ((ret = restconf_authentication_cb(h, req, pretty, media_reply)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_reply, &besock) < 0)
|
||||
goto done;
|
||||
if (besock != -1){
|
||||
if (stream_sockets_setup(h, req, timeout, besock, finish) < 0)
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (pcvec)
|
||||
cvec_free(pcvec);
|
||||
if (path)
|
||||
free(path);
|
||||
return retval;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@
|
|||
*/
|
||||
int api_path_is_stream(clixon_handle h);
|
||||
int restconf_subscription(clixon_handle h, void *req, char *name, cvec *qvec, int pretty, restconf_media media_out, int *sp);
|
||||
int stream_child_free(clixon_handle h, int pid);
|
||||
int stream_child_freeall(clixon_handle h);
|
||||
int stream_close(clixon_handle h, void *req);
|
||||
int api_stream(clixon_handle h, void *req, cvec *qvec, int timeout, int *finish);
|
||||
int stream_sockets_setup(clixon_handle h, void *req, int timeout, int besock, int *finish);
|
||||
int stream_close(clixon_handle h, void *req); // only native
|
||||
int stream_child_free(clixon_handle h, int pid); // only fcgi
|
||||
int stream_child_freeall(clixon_handle h); // only fcgi
|
||||
|
||||
#endif /* _RESTCONF_STREAM_H_ */
|
||||
|
|
|
|||
|
|
@ -104,8 +104,9 @@
|
|||
*/
|
||||
/* Enable for forking stream subscription loop.
|
||||
* Disable to get single threading but blocking on streams
|
||||
* XXX: Integrate with top-level events
|
||||
*/
|
||||
#define STREAM_FORK
|
||||
#undef STREAM_FORK
|
||||
|
||||
/* Keep track of children - when they exit - their FCGX handle needs to be
|
||||
* freed with FCGX_Free(&rbk, 0);
|
||||
|
|
@ -120,6 +121,8 @@ struct stream_child{
|
|||
*/
|
||||
static struct stream_child *STREAM_CHILD = NULL;
|
||||
|
||||
static int backend_eof = 0;
|
||||
|
||||
/*! Find restconf child using PID and cleanup FCGI Request data
|
||||
*
|
||||
* For forked, called on SIGCHILD
|
||||
|
|
@ -174,11 +177,11 @@ stream_child_freeall(clixon_handle h)
|
|||
* @see netconf_notification_cb
|
||||
*/
|
||||
static int
|
||||
restconf_stream_cb(int s,
|
||||
void *arg)
|
||||
stream_fcgi_backend_cb(int s,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
FCGX_Request *r = (FCGX_Request *)arg;
|
||||
FCGX_Request *req = (FCGX_Request *)arg;
|
||||
int eof;
|
||||
cxobj *xtop = NULL; /* top xml */
|
||||
cxobj *xn; /* notification xml */
|
||||
|
|
@ -193,14 +196,13 @@ restconf_stream_cb(int s,
|
|||
clixon_debug(CLIXON_DBG_STREAM, "%s", cbuf_get(cbmsg)); // Also MSG
|
||||
/* handle close from remote end: this will exit the client */
|
||||
if (eof){
|
||||
clixon_debug(CLIXON_DBG_STREAM, "eof");
|
||||
clixon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
|
||||
errno = ESHUTDOWN;
|
||||
FCGX_FPrintF(r->out, "SHUTDOWN\r\n");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
FCGX_FFlush(r->out);
|
||||
clixon_exit_set(1);
|
||||
goto done;
|
||||
clixon_debug(CLIXON_DBG_STREAM, "eof, terminate stream");
|
||||
backend_eof = 1;
|
||||
clixon_exit_set(1); // local timeout
|
||||
FCGX_FPrintF(req->out, "SHUTDOWN\r\n");
|
||||
FCGX_FPrintF(req->out, "\r\n");
|
||||
FCGX_FFlush(req->out);
|
||||
goto ok;
|
||||
}
|
||||
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
|
||||
goto done;
|
||||
|
|
@ -229,9 +231,9 @@ restconf_stream_cb(int s,
|
|||
#endif
|
||||
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
|
||||
goto done;
|
||||
FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb));
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
FCGX_FFlush(r->out);
|
||||
FCGX_FPrintF(req->out, "data: %s\r\n", cbuf_get(cb));
|
||||
FCGX_FPrintF(req->out, "\r\n");
|
||||
FCGX_FFlush(req->out);
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -251,8 +253,8 @@ restconf_stream_cb(int s,
|
|||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
*/
|
||||
static int
|
||||
stream_checkuplink(int s,
|
||||
void *arg)
|
||||
stream_fcgi_uplink_cb(int s,
|
||||
void *arg)
|
||||
{
|
||||
FCGX_Request *r = (FCGX_Request *)arg;
|
||||
|
||||
|
|
@ -289,191 +291,106 @@ fcgi_stream_timeout(int s,
|
|||
/*! Timeout of notification stream, limit lifetime, for debug
|
||||
*/
|
||||
static int
|
||||
fcgi_stream_timeout2(int s,
|
||||
void *arg)
|
||||
stream_timeout_end(int s,
|
||||
void *arg)
|
||||
{
|
||||
clixon_debug(CLIXON_DBG_STREAM, "Terminate stream");
|
||||
clixon_exit_set(1); // XXX This is local eventloop see below, not global
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Process a stream request
|
||||
/*! FCGI specific code for setting up stream sockets
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||
* @param[in] timeout Stream timeout
|
||||
* @param[in] besock Socket to backend
|
||||
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* Consider moving timeout and backend sock to generic code
|
||||
*/
|
||||
int
|
||||
api_stream(clixon_handle h,
|
||||
void *req,
|
||||
cvec *qvec,
|
||||
int timeout,
|
||||
int *finish)
|
||||
stream_sockets_setup(clixon_handle h,
|
||||
void *req,
|
||||
int timeout,
|
||||
int besock,
|
||||
int *finish)
|
||||
{
|
||||
int retval = -1;
|
||||
FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */
|
||||
char *path = NULL;
|
||||
char *method;
|
||||
char **pvec = NULL;
|
||||
int pn;
|
||||
cvec *pcvec = NULL; /* for rest api */
|
||||
cbuf *cb = NULL;
|
||||
char *indata;
|
||||
int pretty;
|
||||
restconf_media media_out = YANG_DATA_XML; /* XXX default */
|
||||
cbuf *cbret = NULL;
|
||||
int s = -1;
|
||||
int ret;
|
||||
cxobj *xerr = NULL;
|
||||
char *streampath;
|
||||
#ifdef STREAM_FORK
|
||||
int pid;
|
||||
struct stream_child *sc;
|
||||
|
||||
if ((pid = fork()) == 0){ /* child */
|
||||
#if 0 // Leaks
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (qvec)
|
||||
cvec_free(qvec);
|
||||
if (pcvec)
|
||||
cvec_free(pcvec);
|
||||
#endif
|
||||
|
||||
clixon_debug(CLIXON_DBG_STREAM, "");
|
||||
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
||||
if ((path = restconf_uripath(h)) == NULL)
|
||||
goto done;
|
||||
pretty = restconf_pretty_get(h);
|
||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||
goto done;
|
||||
/* Sanity check of path. Should be /stream/<name> */
|
||||
if (pn != 3){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (strlen(pvec[0]) != 0){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (strcmp(pvec[1], streampath)){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if ((method = pvec[2]) == NULL){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
clixon_debug(CLIXON_DBG_STREAM, "method=%s", method);
|
||||
|
||||
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
|
||||
goto done;
|
||||
/* data */
|
||||
if ((cb = restconf_get_indata(req)) == NULL)
|
||||
goto done;
|
||||
indata = cbuf_get(cb);
|
||||
clixon_debug(CLIXON_DBG_STREAM, "DATA=%s", indata);
|
||||
|
||||
/* If present, check credentials. See "plugin_credentials" in plugin
|
||||
* See RFC 8040 section 2.5
|
||||
*/
|
||||
if ((ret = restconf_authentication_cb(h, req, pretty, media_out)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
if (restconf_subscription(h, req, method, qvec, pretty, media_out, &s) < 0)
|
||||
goto done;
|
||||
if (s != -1){
|
||||
#ifdef STREAM_FORK
|
||||
if ((pid = fork()) == 0){ /* child */
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (qvec)
|
||||
cvec_free(qvec);
|
||||
if (pcvec)
|
||||
cvec_free(pcvec);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (cbret)
|
||||
cbuf_free(cbret);
|
||||
#endif /* STREAM_FORK */
|
||||
/* Listen to backend socket */
|
||||
if (clixon_event_reg_fd(s,
|
||||
restconf_stream_cb,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
backend_eof = 0;
|
||||
/* Listen to backend socket */
|
||||
if (clixon_event_reg_fd(besock,
|
||||
stream_fcgi_backend_cb,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
if (clixon_event_reg_fd(rfcgi->listen_sock,
|
||||
stream_fcgi_uplink_cb,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
/* Timeout of notification stream, close after limited lifetime, for debug */
|
||||
if (timeout){
|
||||
struct timeval t;
|
||||
gettimeofday(&t, NULL);
|
||||
t.tv_sec += timeout;
|
||||
clixon_event_reg_timeout(t, stream_timeout_end, req, "Stream timeout");
|
||||
}
|
||||
/* Poll upstream errors */
|
||||
fcgi_stream_timeout(0, req);
|
||||
/* Start loop */
|
||||
clixon_event_loop(h);
|
||||
clixon_debug(CLIXON_DBG_STREAM, "after loop");
|
||||
if (backend_eof == 0)
|
||||
if (clicon_rpc_close_session(h) < 0)
|
||||
goto done;
|
||||
if (clixon_event_reg_fd(rfcgi->listen_sock,
|
||||
stream_checkuplink,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
/* Timeout of notification stream, close after limited lifetime, for debug */
|
||||
if (timeout){
|
||||
struct timeval t;
|
||||
gettimeofday(&t, NULL);
|
||||
t.tv_sec += timeout;
|
||||
clixon_event_reg_timeout(t, fcgi_stream_timeout2, req, "Stream timeout");
|
||||
}
|
||||
/* Poll upstream errors */
|
||||
fcgi_stream_timeout(0, req);
|
||||
/* Start loop */
|
||||
clixon_event_loop(h);
|
||||
clixon_debug(CLIXON_DBG_STREAM, "after loop");
|
||||
clicon_rpc_close_session(h);
|
||||
clixon_event_unreg_fd(s, restconf_stream_cb);
|
||||
close(s);
|
||||
clixon_event_unreg_fd(rfcgi->listen_sock,
|
||||
restconf_stream_cb);
|
||||
clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
|
||||
clixon_event_unreg_timeout(fcgi_stream_timeout2, (void*)req);
|
||||
clixon_exit_set(0); /* reset */
|
||||
clixon_event_unreg_fd(besock, stream_fcgi_backend_cb);
|
||||
close(besock);
|
||||
clixon_event_unreg_fd(rfcgi->listen_sock, stream_fcgi_uplink_cb);
|
||||
clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
|
||||
clixon_event_unreg_timeout(stream_timeout_end, (void*)req);
|
||||
clixon_exit_set(0); /* reset */
|
||||
#ifdef STREAM_FORK
|
||||
#if 0 /* Seems to be a global resource, but there is till some timing error here */
|
||||
FCGX_Finish_r(rfcgi);
|
||||
FCGX_Free(rfcgi, 0);
|
||||
FCGX_Finish_r(rfcgi);
|
||||
FCGX_Free(rfcgi, 0);
|
||||
#endif
|
||||
restconf_terminate(h);
|
||||
exit(0);
|
||||
}
|
||||
/* parent */
|
||||
/* Create stream_child struct and store pid and FCGI data, when child
|
||||
* killed, call FCGX_Free
|
||||
*/
|
||||
if ((sc = malloc(sizeof(struct stream_child))) == NULL){
|
||||
clixon_err(OE_XML, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(sc, 0, sizeof(struct stream_child));
|
||||
sc->sc_pid = pid;
|
||||
sc->sc_r = *rfcgi; /* XXX by value */
|
||||
|
||||
ADDQ(sc, STREAM_CHILD);
|
||||
*finish = 0; /* If spawn child, we should not finish this stream */
|
||||
#endif /* STREAM_FORK */
|
||||
restconf_terminate(h);
|
||||
exit(0);
|
||||
}
|
||||
ok:
|
||||
/* parent */
|
||||
/* Create stream_child struct and store pid and FCGI data, when child
|
||||
* killed, call FCGX_Free
|
||||
*/
|
||||
if ((sc = malloc(sizeof(struct stream_child))) == NULL){
|
||||
clixon_err(OE_XML, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(sc, 0, sizeof(struct stream_child));
|
||||
sc->sc_pid = pid;
|
||||
sc->sc_r = *rfcgi; /* XXX by value */
|
||||
|
||||
ADDQ(sc, STREAM_CHILD);
|
||||
*finish = 0; /* If spawn child, we should not finish this stream */
|
||||
#endif /* STREAM_FORK */
|
||||
retval = 0;
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (pcvec)
|
||||
cvec_free(pcvec);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (cbret)
|
||||
cbuf_free(cbret);
|
||||
if (path)
|
||||
free(path);
|
||||
return retval;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,101 +86,6 @@
|
|||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||
#include "restconf_stream.h"
|
||||
|
||||
// XXX: copy from restconf_native.c
|
||||
static int
|
||||
native_buf_write_xxx(clixon_handle h,
|
||||
char *buf,
|
||||
size_t buflen,
|
||||
restconf_conn *rc,
|
||||
const char *callfn)
|
||||
{
|
||||
int retval = -1;
|
||||
ssize_t len;
|
||||
ssize_t totlen = 0;
|
||||
int er;
|
||||
SSL *ssl;
|
||||
|
||||
if (rc == NULL){
|
||||
clixon_err(OE_RESTCONF, EINVAL, "rc is NULL");
|
||||
goto done;
|
||||
}
|
||||
ssl = rc->rc_ssl;
|
||||
/* Two problems with debugging buffers that this fixes:
|
||||
* 1. they are not "strings" in the sense they are not NULL-terminated
|
||||
* 2. they are often very long
|
||||
*/
|
||||
if (clixon_debug_get()) {
|
||||
char *dbgstr = NULL;
|
||||
size_t sz;
|
||||
sz = buflen>256?256:buflen; /* Truncate to 256 */
|
||||
if ((dbgstr = malloc(sz+1)) == NULL){
|
||||
clixon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memcpy(dbgstr, buf, sz);
|
||||
dbgstr[sz] = '\0';
|
||||
clixon_debug(CLIXON_DBG_RESTCONF, "%s buflen:%zu buf:\n%s", callfn, buflen, dbgstr);
|
||||
free(dbgstr);
|
||||
}
|
||||
while (totlen < buflen){
|
||||
if (ssl){
|
||||
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
|
||||
er = errno;
|
||||
switch (SSL_get_error(ssl, len)){
|
||||
case SSL_ERROR_SYSCALL: /* 5 */
|
||||
if (er == ECONNRESET || /* Connection reset by peer */
|
||||
er == EPIPE) { /* Reading end of socket is closed */
|
||||
goto closed; /* Close socket and ssl */
|
||||
}
|
||||
else if (er == EAGAIN){
|
||||
clixon_debug(CLIXON_DBG_RESTCONF, "write EAGAIN");
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
else{
|
||||
clixon_err(OE_RESTCONF, er, "SSL_write %d", er);
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
clixon_err(OE_SSL, 0, "SSL_write");
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if ((len = write(rc->rc_s, buf+totlen, buflen-totlen)) < 0){
|
||||
switch (errno){
|
||||
case EAGAIN: /* Operation would block */
|
||||
clixon_debug(CLIXON_DBG_RESTCONF, "write EAGAIN");
|
||||
usleep(10000);
|
||||
continue;
|
||||
break;
|
||||
// case EBADF: // XXX if this happens there is some larger error
|
||||
case ECONNRESET: /* Connection reset by peer */
|
||||
case EPIPE: /* Broken pipe */
|
||||
goto closed; /* Close socket and ssl */
|
||||
break;
|
||||
default:
|
||||
clixon_err(OE_UNIX, errno, "write %d", errno);
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
totlen += len;
|
||||
} /* while */
|
||||
retval = 1;
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
|
||||
return retval;
|
||||
closed:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Callback when stream notifications arrive from backend
|
||||
*
|
||||
* @param[in] s Socket
|
||||
|
|
@ -190,8 +95,8 @@ native_buf_write_xxx(clixon_handle h,
|
|||
* @see netconf_notification_cb
|
||||
*/
|
||||
static int
|
||||
restconf_native_stream_cb(int s,
|
||||
void *arg)
|
||||
stream_native_backend_cb(int s,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
restconf_stream_data *sd = (restconf_stream_data *)arg;
|
||||
|
|
@ -258,7 +163,7 @@ restconf_native_stream_cb(int s,
|
|||
cprintf(cb, "\r\n");
|
||||
cprintf(cb, "\r\n");
|
||||
#endif
|
||||
if ((ret = native_buf_write_xxx(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0)
|
||||
if ((ret = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 0;
|
||||
|
|
@ -278,8 +183,8 @@ restconf_native_stream_cb(int s,
|
|||
/*! Timeout of notification stream, limit lifetime, for debug
|
||||
*/
|
||||
static int
|
||||
native_stream_timeout(int s,
|
||||
void *arg)
|
||||
stream_timeout_end(int s,
|
||||
void *arg)
|
||||
{
|
||||
restconf_conn *rc = (restconf_conn *)arg;
|
||||
|
||||
|
|
@ -299,170 +204,52 @@ stream_close(clixon_handle h,
|
|||
restconf_conn *rc = (restconf_conn *)req;
|
||||
|
||||
clicon_rpc_close_session(h);
|
||||
clixon_event_unreg_fd(rc->rc_event_stream, restconf_native_stream_cb);
|
||||
clixon_event_unreg_timeout(native_stream_timeout, req);
|
||||
clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb);
|
||||
clixon_event_unreg_timeout(stream_timeout_end, req);
|
||||
close(rc->rc_event_stream);
|
||||
rc->rc_event_stream = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Process a stream request, native variant
|
||||
/*! Native specific code for setting up stream sockets
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||
* @param[in] timeout Stream timeout
|
||||
* @param[out] finish Not used in native?
|
||||
* @param[in] besock Socket to backend
|
||||
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see api_stream fcgi implementation
|
||||
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
|
||||
* is XML according to RFC5277. But what is error return? assume XML here
|
||||
* Consider moving timeout and backend sock to generic code
|
||||
*/
|
||||
static int
|
||||
api_native_stream(clixon_handle h,
|
||||
void *req,
|
||||
cvec *qvec,
|
||||
int timeout,
|
||||
int *finish)
|
||||
int
|
||||
stream_sockets_setup(clixon_handle h,
|
||||
void *req,
|
||||
int timeout,
|
||||
int besock,
|
||||
int *finish)
|
||||
{
|
||||
int retval = -1;
|
||||
int retval = -1;
|
||||
restconf_stream_data *sd = (restconf_stream_data *)req;
|
||||
restconf_conn *rc;
|
||||
char *path = NULL;
|
||||
char *request_method = NULL; /* GET,.. */
|
||||
char *streampath;
|
||||
int pretty;
|
||||
char **pvec = NULL;
|
||||
int pn;
|
||||
cvec *pcvec = NULL; /* for rest api */
|
||||
cxobj *xerr = NULL;
|
||||
char *media_str = NULL;
|
||||
char *stream_name;
|
||||
restconf_media media_reply = YANG_DATA_XML;
|
||||
int ret;
|
||||
int backend_socket = -1;
|
||||
restconf_conn *rc;
|
||||
|
||||
clixon_debug(CLIXON_DBG_STREAM, "");
|
||||
if (req == NULL){
|
||||
clixon_err(OE_RESTCONF, EINVAL, "req is NULL");
|
||||
/* Listen to backend socket */
|
||||
if (clixon_event_reg_fd(besock,
|
||||
stream_native_backend_cb,
|
||||
req,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
}
|
||||
rc = sd->sd_conn;
|
||||
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
||||
if ((path = restconf_uripath(h)) == NULL)
|
||||
goto done;
|
||||
clixon_debug(CLIXON_DBG_STREAM, "path:%s", path);
|
||||
request_method = restconf_param_get(h, "REQUEST_METHOD");
|
||||
clixon_debug(CLIXON_DBG_STREAM, "method:%s", request_method);
|
||||
pretty = restconf_pretty_get(h);
|
||||
clixon_debug(CLIXON_DBG_STREAM, "pretty:%d", pretty);
|
||||
/* Get media for output (proactive negotiation) RFC7231 by using
|
||||
* Accept:. This is for methods that have output, such as GET,
|
||||
* operation POST, etc
|
||||
* If accept is * default is yang-json
|
||||
*/
|
||||
media_str = restconf_param_get(h, "HTTP_ACCEPT");
|
||||
clixon_debug(CLIXON_DBG_STREAM, "accept(media):%s", media_str);
|
||||
if (media_str == NULL){
|
||||
if (restconf_not_acceptable(h, sd, pretty, media_reply) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
rc->rc_event_stream = besock;
|
||||
/* Timeout of notification stream, close after limited lifetime, for debug */
|
||||
if (timeout){
|
||||
struct timeval t;
|
||||
gettimeofday(&t, NULL);
|
||||
t.tv_sec += timeout;
|
||||
clixon_event_reg_timeout(t, stream_timeout_end, rc, "Stream timeout");
|
||||
}
|
||||
/* Accept only text_event-stream or */
|
||||
if (strcmp(media_str, "*/*") != 0 &&
|
||||
strcmp(media_str, "text/event-stream") != 0){
|
||||
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
||||
goto done;
|
||||
if (strlen(pvec[0]) != 0){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
else if (strcmp(pvec[1], streampath)){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
else if ((stream_name = pvec[2]) == NULL ||
|
||||
strlen(stream_name) == 0){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
|
||||
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
|
||||
goto done;
|
||||
/* If present, check credentials. See "plugin_credentials" in plugin
|
||||
* See RFC 8040 section 2.5
|
||||
*/
|
||||
if ((ret = restconf_authentication_cb(h, req, pretty, media_reply)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto ok;
|
||||
clixon_debug(CLIXON_DBG_STREAM, "passed auth");
|
||||
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_reply, &backend_socket) < 0)
|
||||
goto done;
|
||||
if (backend_socket != -1){
|
||||
// XXX Could add forking here eventurally
|
||||
/* Listen to backend socket */
|
||||
if (clixon_event_reg_fd(backend_socket,
|
||||
restconf_native_stream_cb,
|
||||
sd,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
rc->rc_event_stream = backend_socket;
|
||||
/* Timeout of notification stream, close after limited lifetime, for debug */
|
||||
if (timeout){
|
||||
struct timeval t;
|
||||
gettimeofday(&t, NULL);
|
||||
t.tv_sec += timeout;
|
||||
clixon_event_reg_timeout(t, native_stream_timeout, rc, "Stream timeout");
|
||||
}
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
|
||||
if (xerr)
|
||||
xml_free(xerr);
|
||||
if (path)
|
||||
free(path);
|
||||
if (pvec)
|
||||
free(pvec);
|
||||
if (pcvec)
|
||||
cvec_free(pcvec);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Process a stream request, native variant
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||
* @param[out] finish Not used in native?
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see api_stream fcgi implementation
|
||||
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
|
||||
* is XML according to RFC5277. But what is error return? assume XML here
|
||||
*/
|
||||
int
|
||||
api_stream(clixon_handle h,
|
||||
void *req,
|
||||
cvec *qvec,
|
||||
int timeout,
|
||||
int *finish)
|
||||
{
|
||||
return api_native_stream(h, req, qvec, timeout, finish);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,10 +212,6 @@
|
|||
*/
|
||||
#undef USE_SHA256
|
||||
|
||||
/*! Restconf native stream support
|
||||
*/
|
||||
#define RESTCONF_NATIVE_STREAM
|
||||
|
||||
/*! Temporary comparison of xyanglibs, should be removed asap
|
||||
*/
|
||||
#define YANG_SCHEMA_CMP_KLUDGE
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ function runtest()
|
|||
|
||||
new "2a) start $extra timeout:${TIMEOUT}s - expect ${LBOUND}-${UBOUND} notifications"
|
||||
ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE)
|
||||
|
||||
match=$(echo "$ret" | grep -Eo "$expect")
|
||||
if [ -z "$match" ]; then
|
||||
err "$expect" "$ret"
|
||||
|
|
@ -149,7 +148,6 @@ function runtest()
|
|||
|
||||
new "2b) start $extra timeout:${TIMEOUT} stop after 5s - expect ${LB}-${UB} notifications"
|
||||
ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE?stop-time=${time1})
|
||||
|
||||
match=$(echo "$ret" | grep -Eo "$expect")
|
||||
if [ -z "$match" ]; then
|
||||
err "$expect" "$ret"
|
||||
|
|
@ -181,6 +179,9 @@ if [ $BE -ne 0 ]; then
|
|||
if [ $? -ne 0 ]; then
|
||||
err
|
||||
fi
|
||||
sudo pkill -f clixon_backend # to be sure
|
||||
fi
|
||||
if [ $BE -ne 0 ]; then
|
||||
new "start backend -s init -f $cfg -- -n ${PERIOD}"
|
||||
# create example notification stream with periodic timeout ${PERIOD} seconds
|
||||
start_backend -s init -f $cfg -- -n ${PERIOD}
|
||||
|
|
@ -192,7 +193,8 @@ wait_backend
|
|||
if [ $RC -ne 0 ]; then
|
||||
new "kill old restconf daemon"
|
||||
stop_restconf_pre
|
||||
|
||||
|
||||
sleep 1
|
||||
new "start restconf daemon -f $cfg -t ${TIMEOUT}"
|
||||
start_restconf -f $cfg -t ${TIMEOUT}
|
||||
fi
|
||||
|
|
@ -233,23 +235,6 @@ if [ "${WITH_RESTCONF}" = "native" ]; then
|
|||
fi
|
||||
|
||||
if false; then # NYI
|
||||
test-pause
|
||||
|
||||
# 2b) start subscription 8s - stoptime after 5s - expect 1-2 notifications
|
||||
new "2b) start subscriptions 8s - stoptime after 5s - expect 1-2 notifications"
|
||||
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 8 -e +10)
|
||||
expect="data: <notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\"><eventTime>${DATE}T[0-9:.]*Z</eventTime><event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>"
|
||||
match=$(echo "$ret" | grep -Eo "$expect")
|
||||
if [ -z "$match" ]; then
|
||||
err "$expect" "$ret"
|
||||
fi
|
||||
nr=$(echo "$ret" | grep -c "data:")
|
||||
if [ $nr -lt 1 -o $nr -gt 2 ]; then
|
||||
err 1 "$nr"
|
||||
fi
|
||||
|
||||
test-pause
|
||||
|
||||
# 2c
|
||||
new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications"
|
||||
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue