2017-02-18 10:57:29 -05:00
|
|
|
/** 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-05-05 19:24:16 +00:00
|
|
|
*
|
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-05-05 19:24:16 +00:00
|
|
|
*
|
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/>.
|
2017-02-18 10:57:29 -05:00
|
|
|
*********************************************************************************/
|
|
|
|
|
|
|
|
#include <libconfig.h>
|
|
|
|
#include <libwebsockets.h>
|
|
|
|
|
|
|
|
#include <linux/limits.h>
|
2017-04-27 11:26:10 +02:00
|
|
|
#include <string.h>
|
2017-02-18 10:57:29 -05:00
|
|
|
|
|
|
|
#include "utils.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "web.h"
|
2017-04-09 16:24:00 +02:00
|
|
|
#include "api/session.h"
|
2017-02-18 10:57:29 -05:00
|
|
|
|
2017-03-05 10:06:32 -04:00
|
|
|
#include "nodes/websocket.h"
|
|
|
|
|
2017-02-18 10:57:29 -05:00
|
|
|
/* Forward declarations */
|
2017-03-11 23:50:30 -03:00
|
|
|
lws_callback_function api_ws_protocol_cb;
|
|
|
|
lws_callback_function api_http_protocol_cb;
|
2017-03-05 10:06:32 -04:00
|
|
|
lws_callback_function websocket_protocol_cb;
|
2017-02-18 10:57:29 -05:00
|
|
|
|
|
|
|
/** List of libwebsockets protocols. */
|
|
|
|
static struct lws_protocols protocols[] = {
|
|
|
|
{
|
2017-03-11 23:50:30 -03:00
|
|
|
.name = "http-api",
|
|
|
|
.callback = api_http_protocol_cb,
|
2017-02-18 10:57:29 -05:00
|
|
|
.per_session_data_size = sizeof(struct api_session),
|
|
|
|
.rx_buffer_size = 0
|
|
|
|
},
|
|
|
|
{
|
2017-03-11 23:50:30 -03:00
|
|
|
.name = "api",
|
|
|
|
.callback = api_ws_protocol_cb,
|
|
|
|
.per_session_data_size = sizeof(struct api_session),
|
2017-02-18 10:57:29 -05:00
|
|
|
.rx_buffer_size = 0
|
|
|
|
},
|
2017-03-11 23:50:30 -03:00
|
|
|
#if 0 /* not supported yet */
|
2017-02-18 10:57:29 -05:00
|
|
|
{
|
2017-03-11 23:50:30 -03:00
|
|
|
.name = "log",
|
|
|
|
.callback = log_ws_protocol_cb,
|
|
|
|
.per_session_data_size = 0,
|
|
|
|
.rx_buffer_size = 0
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.name = "stats",
|
|
|
|
.callback = stats_ws_protocol_cb,
|
2017-02-18 10:57:29 -05:00
|
|
|
.per_session_data_size = sizeof(struct api_session),
|
|
|
|
.rx_buffer_size = 0
|
|
|
|
},
|
2017-03-11 23:50:30 -03:00
|
|
|
#endif
|
|
|
|
{
|
|
|
|
.name = "live",
|
|
|
|
.callback = websocket_protocol_cb,
|
|
|
|
.per_session_data_size = sizeof(struct websocket_connection),
|
|
|
|
.rx_buffer_size = 0
|
|
|
|
},
|
2017-03-05 10:06:32 -04:00
|
|
|
{ NULL /* terminator */ }
|
2017-02-18 10:57:29 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
/** List of libwebsockets mounts. */
|
|
|
|
static struct lws_http_mount mounts[] = {
|
|
|
|
{
|
|
|
|
.mount_next = &mounts[1],
|
2017-03-11 23:50:30 -03:00
|
|
|
.mountpoint = "/",
|
|
|
|
.origin = NULL,
|
|
|
|
.def = "/index.html",
|
2017-02-18 10:57:29 -05:00
|
|
|
.cgienv = NULL,
|
|
|
|
.cgi_timeout = 0,
|
|
|
|
.cache_max_age = 0,
|
|
|
|
.cache_reusable = 0,
|
|
|
|
.cache_revalidate = 0,
|
|
|
|
.cache_intermediaries = 0,
|
2017-03-11 23:50:30 -03:00
|
|
|
.origin_protocol = LWSMPRO_FILE,
|
|
|
|
.mountpoint_len = 1
|
2017-02-18 10:57:29 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
.mount_next = NULL,
|
2017-03-11 23:50:30 -03:00
|
|
|
.mountpoint = "/api/v1/",
|
|
|
|
.origin = "http-api",
|
|
|
|
.def = NULL,
|
2017-02-18 10:57:29 -05:00
|
|
|
.cgienv = NULL,
|
|
|
|
.cgi_timeout = 0,
|
|
|
|
.cache_max_age = 0,
|
|
|
|
.cache_reusable = 0,
|
|
|
|
.cache_revalidate = 0,
|
|
|
|
.cache_intermediaries = 0,
|
2017-03-11 23:50:30 -03:00
|
|
|
.origin_protocol = LWSMPRO_CALLBACK,
|
|
|
|
.mountpoint_len = 8
|
2017-02-18 10:57:29 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** 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 */ }
|
|
|
|
};
|
|
|
|
|
|
|
|
static void logger(int level, const char *msg) {
|
|
|
|
int len = strlen(msg);
|
|
|
|
if (strchr(msg, '\n'))
|
|
|
|
len -= 1;
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-02-18 10:57:29 -05:00
|
|
|
/* Decrease severity for some errors. */
|
|
|
|
if (strstr(msg, "Unable to open") == msg)
|
|
|
|
level = LLL_WARN;
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-02-18 10:57:29 -05:00
|
|
|
switch (level) {
|
2017-04-02 13:03:14 +02:00
|
|
|
case LLL_ERR: warn("LWS: %.*s", len, msg); break;
|
2017-02-18 10:57:29 -05:00
|
|
|
case LLL_WARN: warn("LWS: %.*s", len, msg); break;
|
|
|
|
case LLL_INFO: info("LWS: %.*s", len, msg); break;
|
|
|
|
default: debug(LOG_WEBSOCKET | 1, "LWS: %.*s", len, msg); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-07 17:39:37 +02:00
|
|
|
static void * worker(void *ctx)
|
|
|
|
{
|
|
|
|
struct web *w = ctx;
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-04-07 17:39:37 +02:00
|
|
|
for (;;)
|
|
|
|
lws_service(w->context, 100);
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-04-07 17:39:37 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-03-11 23:50:30 -03:00
|
|
|
int web_init(struct web *w, struct api *a)
|
2017-02-18 10:57:29 -05:00
|
|
|
{
|
2017-03-12 17:13:37 -03:00
|
|
|
lws_set_log_level((1 << LLL_COUNT) - 1, logger);
|
2017-03-11 23:50:30 -03:00
|
|
|
|
|
|
|
w->api = a;
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-03-29 04:25:30 +02:00
|
|
|
/* Default values */
|
2017-05-24 14:47:24 +00:00
|
|
|
w->port = getuid() > 0 ? 8080 : 80; /**< @todo Use libcap to check if user can bind to ports < 1024 */
|
2017-03-29 04:25:30 +02:00
|
|
|
w->htdocs = WEB_PATH;
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-03-29 04:25:30 +02:00
|
|
|
w->state = STATE_INITIALIZED;
|
|
|
|
|
2017-03-11 23:50:30 -03:00
|
|
|
return 0;
|
2017-02-18 10:57:29 -05:00
|
|
|
}
|
|
|
|
|
2017-03-05 10:06:32 -04:00
|
|
|
int web_parse(struct web *w, config_setting_t *cfg)
|
2017-02-18 10:57:29 -05:00
|
|
|
{
|
2017-04-24 19:55:46 +02:00
|
|
|
int enabled = true;
|
|
|
|
|
2017-03-05 10:06:32 -04:00
|
|
|
if (!config_setting_is_group(cfg))
|
|
|
|
cerror(cfg, "Setting 'http' must be a group.");
|
2017-02-18 10:57:29 -05:00
|
|
|
|
2017-03-05 10:06:32 -04:00
|
|
|
config_setting_lookup_string(cfg, "ssl_cert", &w->ssl_cert);
|
|
|
|
config_setting_lookup_string(cfg, "ssl_private_key", &w->ssl_private_key);
|
2017-03-29 04:25:30 +02:00
|
|
|
config_setting_lookup_int(cfg, "port", &w->port);
|
|
|
|
config_setting_lookup_string(cfg, "htdocs", &w->htdocs);
|
2017-04-24 19:55:46 +02:00
|
|
|
config_setting_lookup_bool(cfg, "enabled", &enabled);
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-04-24 19:55:46 +02:00
|
|
|
if (!enabled)
|
|
|
|
w->port = CONTEXT_PORT_NO_LISTEN;
|
2017-03-11 23:50:30 -03:00
|
|
|
|
|
|
|
w->state = STATE_PARSED;
|
|
|
|
|
2017-02-18 10:57:29 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-11 23:50:30 -03:00
|
|
|
int web_start(struct web *w)
|
2017-02-18 10:57:29 -05:00
|
|
|
{
|
2017-04-07 17:39:37 +02:00
|
|
|
int ret;
|
|
|
|
|
2017-02-18 10:57:29 -05:00
|
|
|
/* Start server */
|
|
|
|
struct lws_context_creation_info ctx_info = {
|
2017-03-05 10:06:32 -04:00
|
|
|
.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT,
|
2017-02-18 10:57:29 -05:00
|
|
|
.gid = -1,
|
|
|
|
.uid = -1,
|
|
|
|
.user = (void *) w
|
|
|
|
};
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-02-18 10:57:29 -05:00
|
|
|
struct lws_context_creation_info vhost_info = {
|
|
|
|
.protocols = protocols,
|
|
|
|
.mounts = mounts,
|
|
|
|
.extensions = extensions,
|
|
|
|
.port = w->port,
|
|
|
|
.ssl_cert_filepath = w->ssl_cert,
|
|
|
|
.ssl_private_key_filepath = w->ssl_private_key
|
|
|
|
};
|
|
|
|
|
2017-03-29 04:25:30 +02:00
|
|
|
info("Starting Web sub-system: webroot=%s", w->htdocs);
|
2017-03-12 17:13:37 -03:00
|
|
|
|
|
|
|
{ INDENT
|
|
|
|
/* 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");
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-03-12 17:13:37 -03:00
|
|
|
w->vhost = lws_create_vhost(w->context, &vhost_info);
|
|
|
|
if (w->vhost == NULL)
|
|
|
|
error("WebSocket: failed to initialize server");
|
|
|
|
}
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-04-07 17:39:37 +02:00
|
|
|
ret = pthread_create(&w->thread, NULL, worker, w);
|
|
|
|
if (ret)
|
|
|
|
error("Failed to start Web worker");
|
2017-03-12 17:13:37 -03:00
|
|
|
|
2017-05-14 11:36:44 +02:00
|
|
|
w->state = STATE_STARTED;
|
2017-02-18 10:57:29 -05:00
|
|
|
|
2017-04-07 17:39:37 +02:00
|
|
|
return ret;
|
2017-02-18 10:57:29 -05:00
|
|
|
}
|
|
|
|
|
2017-03-11 23:50:30 -03:00
|
|
|
int web_stop(struct web *w)
|
2017-03-03 20:21:33 -04:00
|
|
|
{
|
2017-04-03 09:01:14 +02:00
|
|
|
info("Stopping Web sub-system");
|
|
|
|
|
2017-06-09 12:02:01 +02:00
|
|
|
if (w->state == STATE_STARTED) {
|
2017-04-02 04:56:08 +02:00
|
|
|
lws_cancel_service(w->context);
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-06-09 12:02:01 +02:00
|
|
|
/** @todo Wait for all connections to be closed */
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-06-09 12:02:01 +02:00
|
|
|
pthread_cancel(w->thread);
|
|
|
|
pthread_join(w->thread, NULL);
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-06-09 12:02:01 +02:00
|
|
|
w->state = STATE_STOPPED;
|
|
|
|
}
|
2017-03-03 20:21:33 -04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-11 23:50:30 -03:00
|
|
|
int web_destroy(struct web *w)
|
2017-02-18 10:57:29 -05:00
|
|
|
{
|
2017-04-02 04:56:08 +02:00
|
|
|
if (w->state == STATE_DESTROYED)
|
|
|
|
return 0;
|
2017-05-05 19:24:16 +00:00
|
|
|
|
2017-04-02 04:56:08 +02:00
|
|
|
if (w->context)
|
|
|
|
lws_context_destroy(w->context);
|
2017-03-11 23:50:30 -03:00
|
|
|
|
|
|
|
w->state = STATE_DESTROYED;
|
2017-02-18 10:57:29 -05:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|