161 lines
4.8 KiB
C
161 lines
4.8 KiB
C
/*
|
|
* This file is part of the split-flap project.
|
|
* Copyright (c) 2024-2025 GuniaLabs (www.dennisgunia.de)
|
|
* Authors: Dennis Gunia
|
|
*
|
|
* This program is licenced under AGPL-3.0 license.
|
|
*
|
|
*/
|
|
|
|
#include "wsserver.h"
|
|
#include "logging/logger.h"
|
|
/*
|
|
* This section provides a web server to controll the
|
|
* device manager through web sockets
|
|
*/
|
|
|
|
json_object *(*commandparser_func)(json_object *);
|
|
|
|
// this sections handles ws connections and communications
|
|
|
|
/*
|
|
* called on opening websocket client
|
|
* @param client: websocket client connection
|
|
*/
|
|
void ws_opencon(ws_cli_conn_t client)
|
|
{
|
|
char *cli, *port;
|
|
cli = ws_getaddress(client);
|
|
port = ws_getport(client);
|
|
log_message(LOG_DEBUG, "WebSocket connection opened, addr: %s, port: %s", cli, port);
|
|
}
|
|
|
|
/*
|
|
* called on closing websocket client
|
|
* @param client: websocket client connection
|
|
*/
|
|
void ws_closecon(ws_cli_conn_t client)
|
|
{
|
|
char *cli, *port;
|
|
cli = ws_getaddress(client);
|
|
port = ws_getport(client);
|
|
log_message(LOG_DEBUG, "WebSocket connection closed, addr: %s, port: %s", cli, port);
|
|
}
|
|
|
|
/*
|
|
* called on receiving websocket message
|
|
* Handles incoming websocket messages, parses json commands and sends responses.
|
|
*
|
|
* @param client: websocket client connection
|
|
* @param msg: received message
|
|
* @param size: size of received message
|
|
* @param type: type of message
|
|
*/
|
|
void ws_messagehandler(ws_cli_conn_t client, const unsigned char *msg, uint64_t size, int type)
|
|
{
|
|
char *cli = ws_getaddress(client);
|
|
log_message(LOG_DEBUG, "WebSocket received message: %s (%zu), from: %s", msg, size, cli);
|
|
|
|
json_tokener *tok = json_tokener_new();
|
|
json_object *req = json_tokener_parse_ex(tok, (const char *)msg, size);
|
|
enum json_tokener_error jerr;
|
|
jerr = json_tokener_get_error(tok);
|
|
if (jerr != json_tokener_success)
|
|
{
|
|
// check if request can be parsed, if not retrun error
|
|
send_json_error(client, "WebSocket JSON parsing error", json_tokener_error_desc(jerr));
|
|
}
|
|
else
|
|
{
|
|
// if it can be parsed, get command.
|
|
json_object *commandObj = json_object_object_get(req, "command");
|
|
if (commandObj != NULL)
|
|
{
|
|
const char *command = json_object_to_json_string(commandObj);
|
|
// get key
|
|
json_object *res = commandparser_func(req);
|
|
if (res == NULL)
|
|
{
|
|
send_json_error(client, "internal error", "");
|
|
}
|
|
else
|
|
{
|
|
send_json_response(client, res);
|
|
}
|
|
free(res);
|
|
}
|
|
else
|
|
{
|
|
// if key is missing, send error
|
|
send_json_error(client, "format error", "missing key: command");
|
|
}
|
|
}
|
|
free(tok); // always free tokenizer, to prevent memory leak
|
|
}
|
|
|
|
/*
|
|
* send json response to websocket client
|
|
*
|
|
* @param client: websocket client connection
|
|
* @param res: json response object
|
|
*/
|
|
void send_json_response(ws_cli_conn_t client, json_object *res)
|
|
{
|
|
const char *message = json_object_to_json_string_ext(res, JSON_C_TO_STRING_PRETTY);
|
|
ws_sendframe_txt(client, message);
|
|
}
|
|
|
|
/*
|
|
* send json error to websocket client
|
|
*
|
|
* @param client: websocket client connection
|
|
* @param error: error message
|
|
* @param detail: error detail message
|
|
*/
|
|
void send_json_error(ws_cli_conn_t client, char *error, const char *detail)
|
|
{
|
|
json_object *root = json_object_new_object();
|
|
json_object_object_add(root, "error", json_object_new_string(error));
|
|
json_object_object_add(root, "detail", json_object_new_string(detail));
|
|
send_json_response(client, root);
|
|
}
|
|
|
|
/*
|
|
* send json hisotry to all websocket clients
|
|
*
|
|
* @param msg: message
|
|
*/
|
|
void send_json_history(json_object *msg)
|
|
{
|
|
json_object *root = json_object_new_object();
|
|
json_object_object_add(root, "history", root);
|
|
const char *message = json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY);
|
|
ws_sendframe_txt_bcast(-1, message);
|
|
}
|
|
/*
|
|
* Start websocket server
|
|
*
|
|
* @param commandparser_func_ptr: pointer to command parser function
|
|
* @return 0 on success
|
|
*/
|
|
int start_webserver(json_object *(*commandparser_func_ptr)(json_object *))
|
|
{
|
|
commandparser_func = commandparser_func_ptr;
|
|
log_message(LOG_INFO, "Websocket server started at ws://%s:%d", WS_SERVER_ADDR, WS_SERVER_PORT);
|
|
|
|
ws_socket(&(struct ws_server){/*
|
|
* Bind host, such as:
|
|
* localhost -> localhost/127.0.0.1
|
|
* 0.0.0.0 -> global IPv4
|
|
* :: -> global IPv4+IPv6 (Dual stack)
|
|
*/
|
|
.host = WS_SERVER_ADDR,
|
|
.port = WS_SERVER_PORT,
|
|
.thread_loop = 0,
|
|
.timeout_ms = 1000,
|
|
.evs.onopen = &ws_opencon,
|
|
.evs.onclose = &ws_closecon,
|
|
.evs.onmessage = &ws_messagehandler});
|
|
|
|
return (0);
|
|
} |