1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/lib/web.c

317 lines
7 KiB
C
Raw Permalink Normal View History

/** LWS-releated functions.
*
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
2017-04-27 12:56:43 +02:00
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
2017-04-27 12:56:43 +02:00
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
2017-04-27 12:56:43 +02:00
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <libwebsockets.h>
2017-04-27 11:26:10 +02:00
#include <string.h>
#include <villas/config.h>
2017-12-09 02:19:28 +08:00
#include <villas/utils.h>
#include <villas/log.h>
#include <villas/web.h>
#include <villas/api/session.h>
2017-12-09 02:19:28 +08:00
#include <villas/nodes/websocket.h>
/* Forward declarations */
lws_callback_function api_ws_protocol_cb;
lws_callback_function api_http_protocol_cb;
lws_callback_function websocket_protocol_cb;
/** List of libwebsockets protocols. */
struct lws_protocols protocols[] = {
{
.name = "http",
.callback = lws_callback_http_dummy,
.per_session_data_size = 0,
.rx_buffer_size = 1024
},
#ifdef WITH_API
{
.name = "http-api",
.callback = api_http_protocol_cb,
.per_session_data_size = sizeof(struct api_session),
.rx_buffer_size = 1024
},
{
.name = "api",
.callback = api_ws_protocol_cb,
.per_session_data_size = sizeof(struct api_session),
.rx_buffer_size = 0
},
#endif /* WITH_API */
2018-06-25 06:00:34 +02:00
#ifdef LIBWEBSOCKETS_FOUND
{
.name = "live",
.callback = websocket_protocol_cb,
.per_session_data_size = sizeof(struct websocket_connection),
.rx_buffer_size = 0
},
2018-06-25 06:00:34 +02:00
#endif /* LIBWEBSOCKETS_FOUND */
#if 0 /* not supported yet */
{
.name = "log",
.callback = log_ws_protocol_cb,
.per_session_data_size = 0,
.rx_buffer_size = 0
},
{
.name = "stats",
.callback = stats_ws_protocol_cb,
.per_session_data_size = sizeof(struct api_session),
.rx_buffer_size = 0
},
#endif
{ NULL /* terminator */ }
};
/** List of libwebsockets mounts. */
static struct lws_http_mount mounts[] = {
{
.mountpoint = "/",
.origin = NULL,
.def = "/index.html",
.cgienv = NULL,
.cgi_timeout = 0,
.cache_max_age = 0,
.cache_reusable = 0,
.cache_revalidate = 0,
.cache_intermediaries = 0,
.origin_protocol = LWSMPRO_FILE,
2017-09-05 01:07:11 +02:00
.mountpoint_len = 1,
#ifdef WITH_API
2017-09-05 01:07:11 +02:00
.mount_next = &mounts[1]
},
{
.mountpoint = "/api/v1",
.origin = "http-api",
.def = NULL,
.cgienv = NULL,
.cgi_timeout = 0,
.cache_max_age = 0,
.cache_reusable = 0,
.cache_revalidate = 0,
.cache_intermediaries = 0,
.origin_protocol = LWSMPRO_CALLBACK,
.mountpoint_len = 7,
#endif /* WITH_API */
2017-09-05 01:07:11 +02:00
.mount_next = NULL
}
};
/** List of libwebsockets extensions. */
static const struct lws_extension extensions[] = {
{
"permessage-deflate",
lws_extension_callback_pm_deflate,
"permessage-deflate"
},
{
"deflate-frame",
lws_extension_callback_pm_deflate,
"deflate_frame"
},
{ NULL /* terminator */ }
};
2017-08-22 12:12:26 +02:00
extern struct log *global_log;
static void logger(int level, const char *msg) {
int len = strlen(msg);
if (strchr(msg, '\n'))
len -= 1;
/* Decrease severity for some errors. */
if (strstr(msg, "Unable to open") == msg)
level = LLL_WARN;
switch (level) {
2018-06-12 20:02:58 +02:00
case LLL_ERR: log_print(global_log, CLR_RED("Web "), "%.*s", len, msg); break;
case LLL_WARN: log_print(global_log, CLR_YEL("Web "), "%.*s", len, msg); break;
case LLL_NOTICE: log_print(global_log, CLR_WHT("Web "), "%.*s", len, msg); break;
case LLL_INFO: log_print(global_log, "Web ", "%.*s", len, msg); break;
default: /* Everything else is debug */
log_print(global_log, CLR_GRY("Web "), "%.*s", len, msg); break;
}
}
static void * web_worker(void *ctx)
2017-04-07 17:39:37 +02:00
{
struct web *w = ctx;
2017-04-07 17:39:37 +02:00
for (;;)
lws_service(w->context, 100);
2017-04-07 17:39:37 +02:00
return NULL;
}
int web_init(struct web *w, struct api *a)
{
2018-06-12 20:02:58 +02:00
int lvl = LLL_ERR | LLL_WARN | LLL_NOTICE;
if (global_log->level >=10 && global_log->facilities & LOG_WEB)
lvl |= (1 << LLL_COUNT) - 1;
lws_set_log_level(lvl, logger);
w->api = a;
/* Default values */
w->port = getuid() > 0 ? 8080 : 80; /**< @todo Use libcap to check if user can bind to ports < 1024 */
w->htdocs = strdup(WEB_PATH);
w->state = STATE_INITIALIZED;
return 0;
}
int web_parse(struct web *w, json_t *cfg)
{
int ret, enabled = 1;
const char *ssl_cert = NULL;
const char *ssl_private_key = NULL;
const char *htdocs = NULL;
json_error_t err;
ret = json_unpack_ex(cfg, &err, 0, "{ s?: s, s?: s, s?: s, s?: i, s?: b }",
"ssl_cert", &ssl_cert,
"ssl_private_key", &ssl_private_key,
"htdocs", &htdocs,
"port", &w->port,
"enabled", &enabled
);
if (ret)
jerror(&err, "Failed to http section of configuration file");
if (ssl_cert)
w->ssl_cert = strdup(ssl_cert);
if (ssl_private_key)
w->ssl_private_key = strdup(ssl_private_key);
if (htdocs) {
if (w->htdocs)
free(w->htdocs);
w->htdocs = strdup(htdocs);
}
if (!enabled)
w->port = CONTEXT_PORT_NO_LISTEN;
w->state = STATE_PARSED;
return 0;
}
int web_start(struct web *w)
{
2017-04-07 17:39:37 +02:00
int ret;
/* Start server */
struct lws_context_creation_info ctx_info = {
.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT,
.gid = -1,
.uid = -1,
.user = (void *) w,
.protocols = protocols,
.mounts = mounts,
.extensions = extensions,
.port = w->port,
.ssl_cert_filepath = w->ssl_cert,
.ssl_private_key_filepath = w->ssl_private_key
};
info("Starting Web sub-system: webroot=%s", w->htdocs);
{
/* update web root of mount point */
mounts[0].origin = w->htdocs;
w->context = lws_create_context(&ctx_info);
if (w->context == NULL)
error("WebSocket: failed to initialize server context");
w->vhost = lws_create_vhost(w->context, &ctx_info);
if (w->vhost == NULL)
error("WebSocket: failed to initialize virtual host");
}
ret = pthread_create(&w->thread, NULL, web_worker, w);
2017-04-07 17:39:37 +02:00
if (ret)
error("Failed to start Web worker thread");
w->state = STATE_STARTED;
2017-04-07 17:39:37 +02:00
return ret;
}
int web_stop(struct web *w)
2017-03-03 20:21:33 -04:00
{
int ret;
if (w->state != STATE_STARTED)
return 0;
2017-04-03 09:01:14 +02:00
info("Stopping Web sub-system");
{
lws_cancel_service(w->context);
/** @todo Wait for all connections to be closed */
ret = pthread_cancel(w->thread);
if (ret)
serror("Failed to cancel Web worker thread");
ret = pthread_join(w->thread, NULL);
if (ret)
serror("Failed to join Web worker thread");
}
2017-09-02 22:01:52 +02:00
w->state = STATE_STOPPED;
2017-03-03 20:21:33 -04:00
return 0;
}
int web_destroy(struct web *w)
{
if (w->state == STATE_DESTROYED)
return 0;
if (w->context) {
lws_context_destroy(w->context);
}
if (w->ssl_cert)
free(w->ssl_cert);
if (w->ssl_private_key)
free(w->ssl_private_key);
if (w->htdocs)
free(w->htdocs);
w->state = STATE_DESTROYED;
return 0;
}