From ca8429506652da8fbe3e3288067b20df557ae2f8 Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Mon, 5 Oct 2020 11:49:52 +0200 Subject: [PATCH] New mechanism to tweak/query transport plugins via Admin API (#2354) --- html/admin.html | 26 +++++- html/admin.js | 140 ++++++++++++++++++++++++++++ janus.c | 38 ++++++++ mainpage.dox | 7 ++ transports/janus_http.c | 168 ++++++++++++++++++++++++++++++---- transports/janus_mqtt.c | 107 ++++++++++++++++++++-- transports/janus_nanomsg.c | 95 +++++++++++++++++++ transports/janus_pfunix.c | 102 +++++++++++++++++++++ transports/janus_rabbitmq.c | 94 +++++++++++++++++++ transports/janus_websockets.c | 150 ++++++++++++++++++++++++++++++ transports/transport.h | 18 +++- 11 files changed, 916 insertions(+), 29 deletions(-) diff --git a/html/admin.html b/html/admin.html index b87a0a9176..22f501f864 100644 --- a/html/admin.html +++ b/html/admin.html @@ -42,6 +42,7 @@

Janus WebRTC Server: Admin/Monitor

+ @@ -64,7 +65,10 @@

Janus WebRTC Server: Admin/Monitor

and provides you with a way to change them dynamically.

The Plugins tab presents the list of media plugins available in this Janus instance, and allows you to interact with - them, assuming they implement the handle_admin_message API.

+ them, assuming they implement the handle_admin_message API. + The Transports tab does the same for transport plugins, + and allows you to send requests to tweak the behaviour of the plugin + or query its internal state, assuming they support this somehow.

The Handles tab allows you to browse the currently active sessions and handles in Janus. Selecting a specific handle will provide you with all the current info related to it, including plugin it is @@ -132,6 +136,26 @@

Response
+
+

Transports

+
+
+ +
+
+
+
+
Tweak/Query
+ +
+
+
+
Response
+

+								
+
+
+

Sessions (0)

diff --git a/html/admin.js b/html/admin.js index 9e4121099b..269a756bf2 100644 --- a/html/admin.js +++ b/html/admin.js @@ -16,6 +16,7 @@ var session = null; // Selected session var handle = null; // Selected handle var plugins = [], pluginsIndex = []; +var transports = [], transportsIndex = []; var settings = {}; var currentHandle = null; @@ -81,6 +82,8 @@ function randomString(len) { function updateServerInfo() { plugins = []; pluginsIndex = []; + transports = []; + transportsIndex = []; $.ajax({ type: 'GET', url: server + "/info", @@ -176,6 +179,7 @@ function updateServerInfo() { '' ); for(var t in transportsJson) { + transports.push(t); var v = transportsJson[t]; $('#server-transports').append( '' + @@ -184,6 +188,19 @@ function updateServerInfo() { ' ' + v.description + '' + ' ' + v.version_string + '' + ''); + transportsIndex.push(t); + $('#transports-list').append( + ''+t+'' + ); + $('#transport-'+(transportsIndex.length-1)).click(function(event) { + event.preventDefault(); + var ti = parseInt($(this).attr('id').split('transport-')[1]); + var transport = transportsIndex[ti]; + console.log("Selected transport:", transport); + $('#transports-list a').removeClass('active'); + $('#transport-'+ti).addClass('active'); + resetTransportRequest(); + }); } $('#server-handlers').html( '' + @@ -688,6 +705,129 @@ function sendPluginMessage(plugin, message) { }); } +// Transports +function resetTransportRequest() { + $('#transport-request').empty().append( + '' + + ' Name' + + ' Value' + + ' Type' + + ' ' + + '' + + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ''); + $('#addattr').click(addTransportMessageAttribute).click(); + $('#sendmsg').click(function() { + var message = {}; + var num = $('.pm-property').length; + for(var i=0; i' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '' + ); +} + +function sendTransportMessage(transport, message) { + console.log("Sending message to " + transport + ":", message); + var request = { + janus: "query_transport", + transaction: randomString(12), + admin_secret: secret, + transport: transport, + request: message + }; + $.ajax({ + type: 'POST', + url: server, + cache: false, + contentType: "application/json", + data: JSON.stringify(request), + success: function(json) { + if(json["janus"] !== "success") { + console.log("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME + var authenticate = (json["error"].code === 403); + if(!authenticate || (authenticate && !prompting && !alerted)) { + if(authenticate) + alerted = true; + bootbox.alert(json["error"].reason, function() { + if(authenticate) { + promptAccessDetails(); + alerted = false; + } + }); + } + } + $('#transport-response').text(JSON.stringify(json, null, 4)); + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + console.log(textStatus + ": " + errorThrown); // FIXME + if(!prompting && !alerted) { + alerted = true; + bootbox.alert("Couldn't contact the backend: is Janus down, or is the Admin/Monitor interface disabled?", function() { + promptAccessDetails(); + alerted = false; + }); + } + }, + dataType: "json" + }); +} + // Handles function updateSessions() { diff --git a/janus.c b/janus.c index 420a882bd4..a3601ac659 100644 --- a/janus.c +++ b/janus.c @@ -144,6 +144,10 @@ static struct janus_json_parameter st_parameters[] = { static struct janus_json_parameter ans_parameters[] = { {"accept", JANUS_JSON_BOOL, JANUS_JSON_PARAM_REQUIRED} }; +static struct janus_json_parameter querytransport_parameters[] = { + {"transport", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, + {"request", JSON_OBJECT, 0} +}; static struct janus_json_parameter queryhandler_parameters[] = { {"handler", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}, {"request", JSON_OBJECT, 0} @@ -2200,6 +2204,40 @@ int janus_process_incoming_admin_request(janus_request *request) { /* Send the success reply */ ret = janus_process_success(request, reply); goto jsondone; + } else if(!strcasecmp(message_text, "query_transport")) { + /* Contact a transport and expect a response */ + JANUS_VALIDATE_JSON_OBJECT(root, querytransport_parameters, + error_code, error_cause, FALSE, + JANUS_ERROR_MISSING_MANDATORY_ELEMENT, JANUS_ERROR_INVALID_ELEMENT_TYPE); + if(error_code != 0) { + ret = janus_process_error_string(request, session_id, transaction_text, error_code, error_cause); + goto jsondone; + } + json_t *transport = json_object_get(root, "transport"); + const char *transport_value = json_string_value(transport); + janus_transport *t = g_hash_table_lookup(transports, transport_value); + if(t == NULL) { + /* No such transport... */ + g_snprintf(error_cause, sizeof(error_cause), "%s", "Invalid transport"); + ret = janus_process_error_string(request, session_id, transaction_text, JANUS_ERROR_PLUGIN_NOT_FOUND, error_cause); + goto jsondone; + } + if(t->query_transport == NULL) { + /* Transport doesn't implement the hook... */ + g_snprintf(error_cause, sizeof(error_cause), "%s", "Transport plugin doesn't support queries"); + ret = janus_process_error_string(request, session_id, transaction_text, JANUS_ERROR_UNKNOWN, error_cause); + goto jsondone; + } + json_t *query = json_object_get(root, "request"); + json_t *response = t->query_transport(query); + /* Prepare JSON reply */ + json_t *reply = json_object(); + json_object_set_new(reply, "janus", json_string("success")); + json_object_set_new(reply, "transaction", json_string(transaction_text)); + json_object_set_new(reply, "response", response ? response : json_object()); + /* Send the success reply */ + ret = janus_process_success(request, reply); + goto jsondone; } else if(!strcasecmp(message_text, "query_eventhandler")) { /* Contact an event handler and expect a response */ JANUS_VALIDATE_JSON_OBJECT(root, queryhandler_parameters, diff --git a/mainpage.dox b/mainpage.dox index 4ac97b0d04..8132cbcd40 100644 --- a/mainpage.dox +++ b/mainpage.dox @@ -2316,6 +2316,13 @@ const token = getJanusToken('janus', ['janus.plugin.videoroom']), * - \c detach_handle: detached a specific handle; this behaves exactly * as the \c detach request does in the Janus API. * + * \subsection adminreqt Transport-related requests + * - \c query_transport: send a synchronous request to a transport plugin and + * return a response; whether this is implemented, and what functionality is + * provided, can vary from transport to transport, but in general this feature + * is available to tweak some setting dynamically and/or query some internal + * transport-specific information (e.g., the number of served connections). + * * \subsection adminreqe Event handlers-related requests * - \c query_eventhandler: send a synchronous request to an event handler and * return a response; implemented by most event handlers to dynamically diff --git a/transports/janus_http.c b/transports/janus_http.c index fc3d56f4e6..76e1a70803 100644 --- a/transports/janus_http.c +++ b/transports/janus_http.c @@ -45,12 +45,12 @@ /* Transport plugin information */ -#define JANUS_REST_VERSION 2 -#define JANUS_REST_VERSION_STRING "0.0.2" -#define JANUS_REST_DESCRIPTION "This transport plugin adds REST (HTTP/HTTPS) support to the Janus API via libmicrohttpd." -#define JANUS_REST_NAME "JANUS REST (HTTP/HTTPS) transport plugin" -#define JANUS_REST_AUTHOR "Meetecho s.r.l." -#define JANUS_REST_PACKAGE "janus.transport.http" +#define JANUS_HTTP_VERSION 2 +#define JANUS_HTTP_VERSION_STRING "0.0.2" +#define JANUS_HTTP_DESCRIPTION "This transport plugin adds REST (HTTP/HTTPS) support to the Janus API via libmicrohttpd." +#define JANUS_HTTP_NAME "JANUS REST (HTTP/HTTPS) transport plugin" +#define JANUS_HTTP_AUTHOR "Meetecho s.r.l." +#define JANUS_HTTP_PACKAGE "janus.transport.http" /* Transport methods */ janus_transport *create(void); @@ -69,6 +69,7 @@ int janus_http_send_message(janus_transport_session *transport, void *request_id void janus_http_session_created(janus_transport_session *transport, guint64 session_id); void janus_http_session_over(janus_transport_session *transport, guint64 session_id, gboolean timeout, gboolean claimed); void janus_http_session_claimed(janus_transport_session *transport, guint64 session_id); +json_t *janus_http_query_transport(json_t *request); /* Transport setup */ @@ -92,11 +93,13 @@ static janus_transport janus_http_transport = .session_created = janus_http_session_created, .session_over = janus_http_session_over, .session_claimed = janus_http_session_claimed, + + .query_transport = janus_http_query_transport, ); /* Transport creator */ janus_transport *create(void) { - JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_REST_NAME); + JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_HTTP_NAME); return &janus_http_transport; } @@ -111,6 +114,20 @@ static gboolean notify_events = TRUE; /* JSON serialization options */ static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; +/* Parameter validation (for tweaking and queries via Admin API) */ +static struct janus_json_parameter request_parameters[] = { + {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} +}; +static struct janus_json_parameter configure_parameters[] = { + {"events", JANUS_JSON_BOOL, 0}, + {"json", JSON_STRING, 0}, +}; +/* Error codes (for the tweaking and queries via Admin API) */ +#define JANUS_HTTP_ERROR_INVALID_REQUEST 411 +#define JANUS_HTTP_ERROR_MISSING_ELEMENT 412 +#define JANUS_HTTP_ERROR_INVALID_ELEMENT 413 +#define JANUS_HTTP_ERROR_UNKNOWN_ERROR 499 + /* Incoming HTTP message */ typedef struct janus_http_msg { @@ -565,7 +582,7 @@ static void janus_http_add_cors_headers(janus_http_msg *msg, struct MHD_Response /* Static callback that we register to */ static void janus_http_mhd_panic(void *cls, const char *file, unsigned int line, const char *reason) { JANUS_LOG(LOG_WARN, "[%s]: Error in GNU libmicrohttpd %s:%u: %s\n", - JANUS_REST_PACKAGE, file, line, reason); + JANUS_HTTP_PACKAGE, file, line, reason); } /* Transport implementation */ @@ -599,12 +616,12 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path /* Read configuration */ char filename[255]; - g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_REST_PACKAGE); + g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_HTTP_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config == NULL) { - JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_REST_PACKAGE); - g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_REST_PACKAGE); + JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_HTTP_PACKAGE); + g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_HTTP_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); config = janus_config_parse(filename); } @@ -639,7 +656,7 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { - JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_REST_NAME); + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_HTTP_NAME); } /* Check the base paths */ @@ -868,7 +885,7 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path /* Done */ g_atomic_int_set(&initialized, 1); - JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_REST_NAME); + JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_HTTP_NAME); return 0; } @@ -926,7 +943,7 @@ void janus_http_destroy(void) { g_atomic_int_set(&initialized, 0); g_atomic_int_set(&stopping, 0); - JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_REST_NAME); + JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_HTTP_NAME); } int janus_http_get_api_compatibility(void) { @@ -935,27 +952,27 @@ int janus_http_get_api_compatibility(void) { } int janus_http_get_version(void) { - return JANUS_REST_VERSION; + return JANUS_HTTP_VERSION; } const char *janus_http_get_version_string(void) { - return JANUS_REST_VERSION_STRING; + return JANUS_HTTP_VERSION_STRING; } const char *janus_http_get_description(void) { - return JANUS_REST_DESCRIPTION; + return JANUS_HTTP_DESCRIPTION; } const char *janus_http_get_name(void) { - return JANUS_REST_NAME; + return JANUS_HTTP_NAME; } const char *janus_http_get_author(void) { - return JANUS_REST_AUTHOR; + return JANUS_HTTP_AUTHOR; } const char *janus_http_get_package(void) { - return JANUS_REST_PACKAGE; + return JANUS_HTTP_PACKAGE; } gboolean janus_http_is_janus_api_enabled(void) { @@ -1136,6 +1153,117 @@ void janus_http_session_claimed(janus_transport_session *transport, guint64 sess janus_refcount_decrease(&old_session->ref); } +json_t *janus_http_query_transport(json_t *request) { + if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { + return NULL; + } + /* We can use this request to dynamically change the behaviour of + * the transport plugin, and/or query for some specific information */ + json_t *response = json_object(); + int error_code = 0; + char error_cause[512]; + JANUS_VALIDATE_JSON_OBJECT(request, request_parameters, + error_code, error_cause, TRUE, + JANUS_HTTP_ERROR_MISSING_ELEMENT, JANUS_HTTP_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto plugin_response; + /* Get the request */ + const char *request_text = json_string_value(json_object_get(request, "request")); + if(!strcasecmp(request_text, "configure")) { + /* We only allow for the configuration of some basic properties: + * changing more complex things (e.g., port to bind to, etc.) + * would likely require restarting backends, so just too much */ + JANUS_VALIDATE_JSON_OBJECT(request, configure_parameters, + error_code, error_cause, TRUE, + JANUS_HTTP_ERROR_MISSING_ELEMENT, JANUS_HTTP_ERROR_INVALID_ELEMENT); + /* Check if we now need to send events to handlers */ + json_object_set_new(response, "result", json_integer(200)); + json_t *notes = NULL; + gboolean events = json_is_true(json_object_get(request, "events")); + if(events && !gateway->events_is_enabled()) { + /* Notify that this will be ignored */ + notes = json_array(); + json_array_append_new(notes, json_string("Event handlers disabled at the core level")); + json_object_set_new(response, "notes", notes); + } + if(events != notify_events) { + notify_events = events; + if(!notify_events && gateway->events_is_enabled()) { + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_HTTP_NAME); + } + } + const char *indentation = json_string_value(json_object_get(request, "json")); + if(indentation != NULL) { + if(!strcasecmp(indentation, "indented")) { + /* Default: indented, we use three spaces for that */ + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "plain")) { + /* Not indented and no new lines, but still readable */ + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "compact")) { + /* Compact, so no spaces between separators */ + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; + } else { + JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', ignoring tweak\n", indentation); + /* Notify that this will be ignored */ + if(notes == NULL) { + notes = json_array(); + json_object_set_new(response, "notes", notes); + } + json_array_append_new(notes, json_string("Ignored unsupported indentation format")); + } + } + } else if(!strcasecmp(request_text, "connections")) { + /* Return the number of active connections currently handled by the plugin */ + json_object_set_new(response, "result", json_integer(200)); + json_t *connections = json_object(); + json_object_set_new(response, "connections", connections); + if(ws != NULL) { + const union MHD_DaemonInfo *info = MHD_get_daemon_info(ws, + MHD_DAEMON_INFO_CURRENT_CONNECTIONS, NULL); + if(info != NULL) + json_object_set_new(connections, "http", json_integer(info->num_connections)); + } + if(sws != NULL) { + const union MHD_DaemonInfo *info = MHD_get_daemon_info(sws, + MHD_DAEMON_INFO_CURRENT_CONNECTIONS, NULL); + if(info != NULL) + json_object_set_new(connections, "https", json_integer(info->num_connections)); + } + if(admin_ws != NULL) { + const union MHD_DaemonInfo *info = MHD_get_daemon_info(admin_ws, + MHD_DAEMON_INFO_CURRENT_CONNECTIONS, NULL); + if(info != NULL) + json_object_set_new(connections, "admin_http", json_integer(info->num_connections)); + } + if(admin_sws != NULL) { + const union MHD_DaemonInfo *info = MHD_get_daemon_info(admin_sws, + MHD_DAEMON_INFO_CURRENT_CONNECTIONS, NULL); + if(info != NULL) + json_object_set_new(connections, "admin_https", json_integer(info->num_connections)); + } + /* Also add the global number of messages we're serving */ + janus_mutex_lock(&messages_mutex); + guint count = g_hash_table_size(messages); + janus_mutex_unlock(&messages_mutex); + json_object_set_new(response, "messages", json_integer(count)); + } else { + JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); + error_code = JANUS_HTTP_ERROR_INVALID_REQUEST; + g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); + } + +plugin_response: + { + if(error_code != 0) { + /* Prepare JSON error event */ + json_object_set_new(response, "error_code", json_integer(error_code)); + json_object_set_new(response, "error", json_string(error_cause)); + } + return response; + } +} + /* Connection notifiers */ static int janus_http_client_connect(void *cls, const struct sockaddr *addr, socklen_t addrlen) { janus_network_address naddr; diff --git a/transports/janus_mqtt.c b/transports/janus_mqtt.c index 3714956954..1c44b5911f 100644 --- a/transports/janus_mqtt.c +++ b/transports/janus_mqtt.c @@ -59,6 +59,7 @@ int janus_mqtt_send_message(janus_transport_session *transport, void *request_id void janus_mqtt_session_created(janus_transport_session *transport, guint64 session_id); void janus_mqtt_session_over(janus_transport_session *transport, guint64 session_id, gboolean timeout, gboolean claimed); void janus_mqtt_session_claimed(janus_transport_session *transport, guint64 session_id); +json_t *janus_mqtt_query_transport(json_t *request); #define JANUS_MQTT_VERSION_3_1 "3.1" #define JANUS_MQTT_VERSION_3_1_1 "3.1.1" @@ -88,6 +89,8 @@ static janus_transport janus_mqtt_transport_ = .session_created = janus_mqtt_session_created, .session_over = janus_mqtt_session_over, .session_claimed = janus_mqtt_session_claimed, + + .query_transport = janus_mqtt_query_transport, ); /* Transport creator */ @@ -104,7 +107,22 @@ static gboolean janus_mqtt_admin_api_enabled_ = FALSE; static gboolean notify_events = TRUE; /* JSON serialization options */ -static size_t json_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER; +static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + +/* Parameter validation (for tweaking and queries via Admin API) */ +static struct janus_json_parameter request_parameters[] = { + {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} +}; +static struct janus_json_parameter configure_parameters[] = { + {"events", JANUS_JSON_BOOL, 0}, + {"json", JSON_STRING, 0}, +}; +/* Error codes (for the tweaking and queries via Admin API) */ +#define JANUS_MQTT_ERROR_INVALID_REQUEST 411 +#define JANUS_MQTT_ERROR_MISSING_ELEMENT 412 +#define JANUS_MQTT_ERROR_INVALID_ELEMENT 413 +#define JANUS_MQTT_ERROR_UNKNOWN_ERROR 499 + /* MQTT client context */ typedef struct janus_mqtt_context { @@ -323,16 +341,16 @@ int janus_mqtt_init(janus_transport_callbacks *callback, const char *config_path /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(json_item->value, "indented")) { /* Default: indented, we use three spaces for that */ - json_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(json_item->value, "plain")) { /* Not indented and no new lines, but still readable */ - json_format_ = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(json_item->value, "compact")) { /* Compact, so no spaces between separators */ - json_format_ = JSON_COMPACT | JSON_PRESERVE_ORDER; + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", json_item->value); - json_format_ = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } @@ -840,7 +858,7 @@ int janus_mqtt_send_message(janus_transport_session *transport, void *request_id return -1; } - char *payload = json_dumps(message, json_format_); + char *payload = json_dumps(message, json_format); JANUS_LOG(LOG_HUGE, "Sending %s API message via MQTT: %s\n", admin ? "admin" : "Janus", payload); int rc; @@ -964,6 +982,83 @@ void janus_mqtt_session_claimed(janus_transport_session *transport, guint64 sess /* FIXME Is the above statement accurate? Should we care? Unlike the HTTP transport, there is no hashtable to update */ } +json_t *janus_mqtt_query_transport(json_t *request) { + if(context_ == NULL) { + return NULL; + } + /* We can use this request to dynamically change the behaviour of + * the transport plugin, and/or query for some specific information */ + json_t *response = json_object(); + int error_code = 0; + char error_cause[512]; + JANUS_VALIDATE_JSON_OBJECT(request, request_parameters, + error_code, error_cause, TRUE, + JANUS_MQTT_ERROR_MISSING_ELEMENT, JANUS_MQTT_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto plugin_response; + /* Get the request */ + const char *request_text = json_string_value(json_object_get(request, "request")); + if(!strcasecmp(request_text, "configure")) { + /* We only allow for the configuration of some basic properties: + * changing more complex things (e.g., port to bind to, etc.) + * would likely require restarting backends, so just too much */ + JANUS_VALIDATE_JSON_OBJECT(request, configure_parameters, + error_code, error_cause, TRUE, + JANUS_MQTT_ERROR_MISSING_ELEMENT, JANUS_MQTT_ERROR_INVALID_ELEMENT); + /* Check if we now need to send events to handlers */ + json_object_set_new(response, "result", json_integer(200)); + json_t *notes = NULL; + gboolean events = json_is_true(json_object_get(request, "events")); + if(events && !context_->gateway->events_is_enabled()) { + /* Notify that this will be ignored */ + notes = json_array(); + json_array_append_new(notes, json_string("Event handlers disabled at the core level")); + json_object_set_new(response, "notes", notes); + } + if(events != notify_events) { + notify_events = events; + if(!notify_events && context_->gateway->events_is_enabled()) { + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_MQTT_NAME); + } + } + const char *indentation = json_string_value(json_object_get(request, "json")); + if(indentation != NULL) { + if(!strcasecmp(indentation, "indented")) { + /* Default: indented, we use three spaces for that */ + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "plain")) { + /* Not indented and no new lines, but still readable */ + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "compact")) { + /* Compact, so no spaces between separators */ + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; + } else { + JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', ignoring tweak\n", indentation); + /* Notify that this will be ignored */ + if(notes == NULL) { + notes = json_array(); + json_object_set_new(response, "notes", notes); + } + json_array_append_new(notes, json_string("Ignored unsupported indentation format")); + } + } + } else { + JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); + error_code = JANUS_MQTT_ERROR_INVALID_REQUEST; + g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); + } + +plugin_response: + { + if(error_code != 0) { + /* Prepare JSON error event */ + json_object_set_new(response, "error_code", json_integer(error_code)); + json_object_set_new(response, "error", json_string(error_cause)); + } + return response; + } +} + void janus_mqtt_client_connected(void *context, char *cause) { JANUS_LOG(LOG_INFO, "Connected to MQTT broker: %s\n", cause); janus_mqtt_context *ctx = (janus_mqtt_context *)context; diff --git a/transports/janus_nanomsg.c b/transports/janus_nanomsg.c index 6bf614e883..84197310bc 100644 --- a/transports/janus_nanomsg.c +++ b/transports/janus_nanomsg.c @@ -54,6 +54,7 @@ int janus_nanomsg_send_message(janus_transport_session *transport, void *request void janus_nanomsg_session_created(janus_transport_session *transport, guint64 session_id); void janus_nanomsg_session_over(janus_transport_session *transport, guint64 session_id, gboolean timeout, gboolean claimed); void janus_nanomsg_session_claimed(janus_transport_session *transport, guint64 session_id); +json_t *janus_nanomsg_query_transport(json_t *request); /* Transport setup */ @@ -77,6 +78,8 @@ static janus_transport janus_nanomsg_transport = .session_created = janus_nanomsg_session_created, .session_over = janus_nanomsg_session_over, .session_claimed = janus_nanomsg_session_claimed, + + .query_transport = janus_nanomsg_query_transport, ); /* Transport creator */ @@ -96,6 +99,21 @@ static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; #define BUFFER_SIZE 8192 +/* Parameter validation (for tweaking and queries via Admin API) */ +static struct janus_json_parameter request_parameters[] = { + {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} +}; +static struct janus_json_parameter configure_parameters[] = { + {"events", JANUS_JSON_BOOL, 0}, + {"json", JSON_STRING, 0}, +}; +/* Error codes (for the tweaking and queries via Admin API) */ +#define JANUS_NANOMSG_ERROR_INVALID_REQUEST 411 +#define JANUS_NANOMSG_ERROR_MISSING_ELEMENT 412 +#define JANUS_NANOMSG_ERROR_INVALID_ELEMENT 413 +#define JANUS_NANOMSG_ERROR_UNKNOWN_ERROR 499 + + /* Nanomsg server thread */ static GThread *nanomsg_thread = NULL; void *janus_nanomsg_thread(void *data); @@ -404,6 +422,83 @@ void janus_nanomsg_session_claimed(janus_transport_session *transport, guint64 s /* FIXME Is the above statement accurate? Should we care? Unlike the HTTP transport, there is no hashtable to update */ } +json_t *janus_nanomsg_query_transport(json_t *request) { + if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { + return NULL; + } + /* We can use this request to dynamically change the behaviour of + * the transport plugin, and/or query for some specific information */ + json_t *response = json_object(); + int error_code = 0; + char error_cause[512]; + JANUS_VALIDATE_JSON_OBJECT(request, request_parameters, + error_code, error_cause, TRUE, + JANUS_NANOMSG_ERROR_MISSING_ELEMENT, JANUS_NANOMSG_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto plugin_response; + /* Get the request */ + const char *request_text = json_string_value(json_object_get(request, "request")); + if(!strcasecmp(request_text, "configure")) { + /* We only allow for the configuration of some basic properties: + * changing more complex things (e.g., port to bind to, etc.) + * would likely require restarting backends, so just too much */ + JANUS_VALIDATE_JSON_OBJECT(request, configure_parameters, + error_code, error_cause, TRUE, + JANUS_NANOMSG_ERROR_MISSING_ELEMENT, JANUS_NANOMSG_ERROR_INVALID_ELEMENT); + /* Check if we now need to send events to handlers */ + json_object_set_new(response, "result", json_integer(200)); + json_t *notes = NULL; + gboolean events = json_is_true(json_object_get(request, "events")); + if(events && !gateway->events_is_enabled()) { + /* Notify that this will be ignored */ + notes = json_array(); + json_array_append_new(notes, json_string("Event handlers disabled at the core level")); + json_object_set_new(response, "notes", notes); + } + if(events != notify_events) { + notify_events = events; + if(!notify_events && gateway->events_is_enabled()) { + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_NANOMSG_NAME); + } + } + const char *indentation = json_string_value(json_object_get(request, "json")); + if(indentation != NULL) { + if(!strcasecmp(indentation, "indented")) { + /* Default: indented, we use three spaces for that */ + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "plain")) { + /* Not indented and no new lines, but still readable */ + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "compact")) { + /* Compact, so no spaces between separators */ + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; + } else { + JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', ignoring tweak\n", indentation); + /* Notify that this will be ignored */ + if(notes == NULL) { + notes = json_array(); + json_object_set_new(response, "notes", notes); + } + json_array_append_new(notes, json_string("Ignored unsupported indentation format")); + } + } + } else { + JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); + error_code = JANUS_NANOMSG_ERROR_INVALID_REQUEST; + g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); + } + +plugin_response: + { + if(error_code != 0) { + /* Prepare JSON error event */ + json_object_set_new(response, "error_code", json_integer(error_code)); + json_object_set_new(response, "error", json_string(error_cause)); + } + return response; + } +} + /* Thread */ void *janus_nanomsg_thread(void *data) { diff --git a/transports/janus_pfunix.c b/transports/janus_pfunix.c index 55da82bce3..898b7d1622 100644 --- a/transports/janus_pfunix.c +++ b/transports/janus_pfunix.c @@ -69,6 +69,7 @@ int janus_pfunix_send_message(janus_transport_session *transport, void *request_ void janus_pfunix_session_created(janus_transport_session *transport, guint64 session_id); void janus_pfunix_session_over(janus_transport_session *transport, guint64 session_id, gboolean timeout, gboolean claimed); void janus_pfunix_session_claimed(janus_transport_session *transport, guint64 session_id); +json_t *janus_pfunix_query_transport(json_t *request); /* Transport setup */ @@ -92,6 +93,8 @@ static janus_transport janus_pfunix_transport = .session_created = janus_pfunix_session_created, .session_over = janus_pfunix_session_over, .session_claimed = janus_pfunix_session_claimed, + + .query_transport = janus_pfunix_query_transport, ); /* Transport creator */ @@ -111,6 +114,21 @@ static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; #define BUFFER_SIZE 8192 +/* Parameter validation (for tweaking and queries via Admin API) */ +static struct janus_json_parameter request_parameters[] = { + {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} +}; +static struct janus_json_parameter configure_parameters[] = { + {"events", JANUS_JSON_BOOL, 0}, + {"json", JSON_STRING, 0}, +}; +/* Error codes (for the tweaking and queries via Admin API) */ +#define JANUS_PFUNIX_ERROR_INVALID_REQUEST 411 +#define JANUS_PFUNIX_ERROR_MISSING_ELEMENT 412 +#define JANUS_PFUNIX_ERROR_INVALID_ELEMENT 413 +#define JANUS_PFUNIX_ERROR_UNKNOWN_ERROR 499 + + struct sockaddr_un sizecheck; #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX sizeof(sizecheck.sun_path) @@ -477,6 +495,90 @@ void janus_pfunix_session_claimed(janus_transport_session *transport, guint64 se /* FIXME Is the above statement accurate? Should we care? Unlike the HTTP transport, there is no hashtable to update */ } +json_t *janus_pfunix_query_transport(json_t *request) { + if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { + return NULL; + } + /* We can use this request to dynamically change the behaviour of + * the transport plugin, and/or query for some specific information */ + json_t *response = json_object(); + int error_code = 0; + char error_cause[512]; + JANUS_VALIDATE_JSON_OBJECT(request, request_parameters, + error_code, error_cause, TRUE, + JANUS_PFUNIX_ERROR_MISSING_ELEMENT, JANUS_PFUNIX_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto plugin_response; + /* Get the request */ + const char *request_text = json_string_value(json_object_get(request, "request")); + if(!strcasecmp(request_text, "configure")) { + /* We only allow for the configuration of some basic properties: + * changing more complex things (e.g., port to bind to, etc.) + * would likely require restarting backends, so just too much */ + JANUS_VALIDATE_JSON_OBJECT(request, configure_parameters, + error_code, error_cause, TRUE, + JANUS_PFUNIX_ERROR_MISSING_ELEMENT, JANUS_PFUNIX_ERROR_INVALID_ELEMENT); + /* Check if we now need to send events to handlers */ + json_object_set_new(response, "result", json_integer(200)); + json_t *notes = NULL; + gboolean events = json_is_true(json_object_get(request, "events")); + if(events && !gateway->events_is_enabled()) { + /* Notify that this will be ignored */ + notes = json_array(); + json_array_append_new(notes, json_string("Event handlers disabled at the core level")); + json_object_set_new(response, "notes", notes); + } + if(events != notify_events) { + notify_events = events; + if(!notify_events && gateway->events_is_enabled()) { + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_PFUNIX_NAME); + } + } + const char *indentation = json_string_value(json_object_get(request, "json")); + if(indentation != NULL) { + if(!strcasecmp(indentation, "indented")) { + /* Default: indented, we use three spaces for that */ + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "plain")) { + /* Not indented and no new lines, but still readable */ + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "compact")) { + /* Compact, so no spaces between separators */ + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; + } else { + JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', ignoring tweak\n", indentation); + /* Notify that this will be ignored */ + if(notes == NULL) { + notes = json_array(); + json_object_set_new(response, "notes", notes); + } + json_array_append_new(notes, json_string("Ignored unsupported indentation format")); + } + } + } else if(!strcasecmp(request_text, "connections")) { + /* Return the number of active connections currently handled by the plugin */ + json_object_set_new(response, "result", json_integer(200)); + janus_mutex_lock(&clients_mutex); + guint connections = g_hash_table_size(clients); + janus_mutex_unlock(&clients_mutex); + json_object_set_new(response, "connections", json_integer(connections)); + } else { + JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); + error_code = JANUS_PFUNIX_ERROR_INVALID_REQUEST; + g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); + } + +plugin_response: + { + if(error_code != 0) { + /* Prepare JSON error event */ + json_object_set_new(response, "error_code", json_integer(error_code)); + json_object_set_new(response, "error", json_string(error_cause)); + } + return response; + } +} + /* Thread */ void *janus_pfunix_thread(void *data) { diff --git a/transports/janus_rabbitmq.c b/transports/janus_rabbitmq.c index 9c17a9a995..d8babf9d81 100644 --- a/transports/janus_rabbitmq.c +++ b/transports/janus_rabbitmq.c @@ -70,6 +70,7 @@ int janus_rabbitmq_send_message(janus_transport_session *transport, void *reques void janus_rabbitmq_session_created(janus_transport_session *transport, guint64 session_id); void janus_rabbitmq_session_over(janus_transport_session *transport, guint64 session_id, gboolean timeout, gboolean claimed); void janus_rabbitmq_session_claimed(janus_transport_session *transport, guint64 session_id); +json_t *janus_rabbitmq_query_transport(json_t *request); /* Transport setup */ @@ -93,6 +94,8 @@ static janus_transport janus_rabbitmq_transport = .session_created = janus_rabbitmq_session_created, .session_over = janus_rabbitmq_session_over, .session_claimed = janus_rabbitmq_session_claimed, + + .query_transport = janus_rabbitmq_query_transport, ); /* Transport creator */ @@ -114,6 +117,20 @@ static gboolean notify_events = TRUE; /* JSON serialization options */ static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; +/* Parameter validation (for tweaking and queries via Admin API) */ +static struct janus_json_parameter request_parameters[] = { + {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} +}; +static struct janus_json_parameter configure_parameters[] = { + {"events", JANUS_JSON_BOOL, 0}, + {"json", JSON_STRING, 0}, +}; +/* Error codes (for the tweaking and queries via Admin API) */ +#define JANUS_RABBITMQ_ERROR_INVALID_REQUEST 411 +#define JANUS_RABBITMQ_ERROR_MISSING_ELEMENT 412 +#define JANUS_RABBITMQ_ERROR_INVALID_ELEMENT 413 +#define JANUS_RABBITMQ_ERROR_UNKNOWN_ERROR 499 + /* RabbitMQ client session: we only create a single one as of now */ typedef struct janus_rabbitmq_client { @@ -653,6 +670,83 @@ void janus_rabbitmq_session_claimed(janus_transport_session *transport, guint64 /* FIXME Is the above statement accurate? Should we care? Unlike the HTTP transport, there is no hashtable to update */ } +json_t *janus_rabbitmq_query_transport(json_t *request) { + if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { + return NULL; + } + /* We can use this request to dynamically change the behaviour of + * the transport plugin, and/or query for some specific information */ + json_t *response = json_object(); + int error_code = 0; + char error_cause[512]; + JANUS_VALIDATE_JSON_OBJECT(request, request_parameters, + error_code, error_cause, TRUE, + JANUS_RABBITMQ_ERROR_MISSING_ELEMENT, JANUS_RABBITMQ_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto plugin_response; + /* Get the request */ + const char *request_text = json_string_value(json_object_get(request, "request")); + if(!strcasecmp(request_text, "configure")) { + /* We only allow for the configuration of some basic properties: + * changing more complex things (e.g., port to bind to, etc.) + * would likely require restarting backends, so just too much */ + JANUS_VALIDATE_JSON_OBJECT(request, configure_parameters, + error_code, error_cause, TRUE, + JANUS_RABBITMQ_ERROR_MISSING_ELEMENT, JANUS_RABBITMQ_ERROR_INVALID_ELEMENT); + /* Check if we now need to send events to handlers */ + json_object_set_new(response, "result", json_integer(200)); + json_t *notes = NULL; + gboolean events = json_is_true(json_object_get(request, "events")); + if(events && !gateway->events_is_enabled()) { + /* Notify that this will be ignored */ + notes = json_array(); + json_array_append_new(notes, json_string("Event handlers disabled at the core level")); + json_object_set_new(response, "notes", notes); + } + if(events != notify_events) { + notify_events = events; + if(!notify_events && gateway->events_is_enabled()) { + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_RABBITMQ_NAME); + } + } + const char *indentation = json_string_value(json_object_get(request, "json")); + if(indentation != NULL) { + if(!strcasecmp(indentation, "indented")) { + /* Default: indented, we use three spaces for that */ + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "plain")) { + /* Not indented and no new lines, but still readable */ + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "compact")) { + /* Compact, so no spaces between separators */ + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; + } else { + JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', ignoring tweak\n", indentation); + /* Notify that this will be ignored */ + if(notes == NULL) { + notes = json_array(); + json_object_set_new(response, "notes", notes); + } + json_array_append_new(notes, json_string("Ignored unsupported indentation format")); + } + } + } else { + JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); + error_code = JANUS_RABBITMQ_ERROR_INVALID_REQUEST; + g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); + } + +plugin_response: + { + if(error_code != 0) { + /* Prepare JSON error event */ + json_object_set_new(response, "error_code", json_integer(error_code)); + json_object_set_new(response, "error", json_string(error_cause)); + } + return response; + } +} + /* Threads */ void *janus_rmq_in_thread(void *data) { diff --git a/transports/janus_websockets.c b/transports/janus_websockets.c index 4230da33a4..9728b01add 100644 --- a/transports/janus_websockets.c +++ b/transports/janus_websockets.c @@ -66,6 +66,7 @@ int janus_websockets_send_message(janus_transport_session *transport, void *requ void janus_websockets_session_created(janus_transport_session *transport, guint64 session_id); void janus_websockets_session_over(janus_transport_session *transport, guint64 session_id, gboolean timeout, gboolean claimed); void janus_websockets_session_claimed(janus_transport_session *transport, guint64 session_id); +json_t *janus_websockets_query_transport(json_t *request); /* Transport setup */ @@ -89,6 +90,8 @@ static janus_transport janus_websockets_transport = .session_created = janus_websockets_session_created, .session_over = janus_websockets_session_over, .session_claimed = janus_websockets_session_claimed, + + .query_transport = janus_websockets_query_transport, ); /* Transport creator */ @@ -114,6 +117,21 @@ static janus_mutex writable_mutex; /* JSON serialization options */ static size_t json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; +/* Parameter validation (for tweaking and queries via Admin API) */ +static struct janus_json_parameter request_parameters[] = { + {"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED} +}; +static struct janus_json_parameter configure_parameters[] = { + {"events", JANUS_JSON_BOOL, 0}, + {"json", JSON_STRING, 0}, + {"logging", JSON_STRING, 0}, +}; +/* Error codes (for the tweaking and queries via Admin API) */ +#define JANUS_WEBSOCKETS_ERROR_INVALID_REQUEST 411 +#define JANUS_WEBSOCKETS_ERROR_MISSING_ELEMENT 412 +#define JANUS_WEBSOCKETS_ERROR_INVALID_ELEMENT 413 +#define JANUS_WEBSOCKETS_ERROR_UNKNOWN_ERROR 499 + /* Logging */ static int ws_log_level = 0; @@ -997,6 +1015,138 @@ void janus_websockets_session_claimed(janus_transport_session *transport, guint6 /* FIXME Is the above statement accurate? Should we care? Unlike the HTTP transport, there is no hashtable to update */ } +json_t *janus_websockets_query_transport(json_t *request) { + if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { + return NULL; + } + /* We can use this request to dynamically change the behaviour of + * the transport plugin, and/or query for some specific information */ + json_t *response = json_object(); + int error_code = 0; + char error_cause[512]; + JANUS_VALIDATE_JSON_OBJECT(request, request_parameters, + error_code, error_cause, TRUE, + JANUS_WEBSOCKETS_ERROR_MISSING_ELEMENT, JANUS_WEBSOCKETS_ERROR_INVALID_ELEMENT); + if(error_code != 0) + goto plugin_response; + /* Get the request */ + const char *request_text = json_string_value(json_object_get(request, "request")); + if(!strcasecmp(request_text, "configure")) { + /* We only allow for the configuration of some basic properties: + * changing more complex things (e.g., port to bind to, etc.) + * would likely require restarting backends, so just too much */ + JANUS_VALIDATE_JSON_OBJECT(request, configure_parameters, + error_code, error_cause, TRUE, + JANUS_WEBSOCKETS_ERROR_MISSING_ELEMENT, JANUS_WEBSOCKETS_ERROR_INVALID_ELEMENT); + /* Check if we now need to send events to handlers */ + json_object_set_new(response, "result", json_integer(200)); + json_t *notes = NULL; + gboolean events = json_is_true(json_object_get(request, "events")); + if(events && !gateway->events_is_enabled()) { + /* Notify that this will be ignored */ + notes = json_array(); + json_array_append_new(notes, json_string("Event handlers disabled at the core level")); + json_object_set_new(response, "notes", notes); + } + if(events != notify_events) { + notify_events = events; + if(!notify_events && gateway->events_is_enabled()) { + JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_WEBSOCKETS_NAME); + } + } + const char *indentation = json_string_value(json_object_get(request, "json")); + if(indentation != NULL) { + if(!strcasecmp(indentation, "indented")) { + /* Default: indented, we use three spaces for that */ + json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "plain")) { + /* Not indented and no new lines, but still readable */ + json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; + } else if(!strcasecmp(indentation, "compact")) { + /* Compact, so no spaces between separators */ + json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; + } else { + JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', ignoring tweak\n", indentation); + /* Notify that this will be ignored */ + if(notes == NULL) { + notes = json_array(); + json_object_set_new(response, "notes", notes); + } + json_array_append_new(notes, json_string("Ignored unsupported indentation format")); + } + } + const char *logging = json_string_value(json_object_get(request, "logging")); + if(logging != NULL) { + /* libwebsockets uses a mask to set log levels, as documented here: + * https://libwebsockets.org/lws-api-doc-master/html/group__log.html */ + if(strstr(logging, "none")) { + /* Disable libwebsockets logging completely (the default) */ + } else if(strstr(logging, "all")) { + /* Enable all libwebsockets logging */ + ws_log_level = LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | + LLL_DEBUG | LLL_PARSER | LLL_HEADER | LLL_EXT | +#if (LWS_LIBRARY_VERSION_MAJOR >= 2 && LWS_LIBRARY_VERSION_MINOR >= 2) || (LWS_LIBRARY_VERSION_MAJOR >= 3) + LLL_CLIENT | LLL_LATENCY | LLL_USER | LLL_COUNT; +#else + LLL_CLIENT | LLL_LATENCY | LLL_COUNT; +#endif + } else { + /* Only enable some of the properties */ + ws_log_level = 0; + if(strstr(logging, "err")) + ws_log_level |= LLL_ERR; + if(strstr(logging, "warn")) + ws_log_level |= LLL_WARN; + if(strstr(logging, "notice")) + ws_log_level |= LLL_NOTICE; + if(strstr(logging, "info")) + ws_log_level |= LLL_INFO; + if(strstr(logging, "debug")) + ws_log_level |= LLL_DEBUG; + if(strstr(logging, "parser")) + ws_log_level |= LLL_PARSER; + if(strstr(logging, "header")) + ws_log_level |= LLL_HEADER; + if(strstr(logging, "ext")) + ws_log_level |= LLL_EXT; + if(strstr(logging, "client")) + ws_log_level |= LLL_CLIENT; + if(strstr(logging, "latency")) + ws_log_level |= LLL_LATENCY; +#if (LWS_LIBRARY_VERSION_MAJOR >= 2 && LWS_LIBRARY_VERSION_MINOR >= 2) || (LWS_LIBRARY_VERSION_MAJOR >= 3) + if(strstr(logging, "user")) + ws_log_level |= LLL_USER; +#endif + if(strstr(logging, "count")) + ws_log_level |= LLL_COUNT; + } + JANUS_LOG(LOG_INFO, "libwebsockets logging: %d\n", ws_log_level); + lws_set_log_level(ws_log_level, janus_websockets_log_emit_function); + } + } else if(!strcasecmp(request_text, "connections")) { + /* Return the number of active connections currently handled by the plugin */ + json_object_set_new(response, "result", json_integer(200)); + janus_mutex_lock(&writable_mutex); + guint connections = g_hash_table_size(clients); + janus_mutex_unlock(&writable_mutex); + json_object_set_new(response, "connections", json_integer(connections)); + } else { + JANUS_LOG(LOG_VERB, "Unknown request '%s'\n", request_text); + error_code = JANUS_WEBSOCKETS_ERROR_INVALID_REQUEST; + g_snprintf(error_cause, 512, "Unknown request '%s'", request_text); + } + +plugin_response: + { + if(error_code != 0) { + /* Prepare JSON error event */ + json_object_set_new(response, "error_code", json_integer(error_code)); + json_object_set_new(response, "error", json_string(error_cause)); + } + return response; + } +} + /* Thread */ void *janus_websockets_thread(void *data) { diff --git a/transports/transport.h b/transports/transport.h index d9140dd81c..a3c3e11205 100644 --- a/transports/transport.h +++ b/transports/transport.h @@ -97,7 +97,7 @@ janus_transport *create(void) { /*! \brief Version of the API, to match the one transport plugins were compiled against */ -#define JANUS_TRANSPORT_API_VERSION 7 +#define JANUS_TRANSPORT_API_VERSION 8 /*! \brief Initialization of all transport plugin properties to NULL * @@ -128,7 +128,8 @@ static janus_transport janus_http_transport_plugin = .send_message = NULL, \ .session_created = NULL, \ .session_over = NULL, \ - .session_claimed = NULL, \ + .session_claimed = NULL, \ + .query_transport = NULL, \ ## __VA_ARGS__ } @@ -230,6 +231,19 @@ struct janus_transport { * @param[in] session_id The session ID that was claimed (if the transport cares) */ void (* const session_claimed)(janus_transport_session *transport, guint64 session_id); + /*! \brief Method to send a management request to this specific transport plugin + * \details The method takes a Jansson json_t, that contains all the info related + * to the request. This object will come from an Admin API request, and is + * meant to represent a synchronous request. Since each transport plugin can have + * its own bells and whistles, there's no constraint on what this object should + * contain, which is entirely handler specific. A json_t object needs to be + * returned as a response, which will be sent in response to the Admin API call. + * This can be useful to tweak settings in real-time, or to probe the internals + * of the transport plugin for monitoring purposes. + * @param[in] request Jansson object containing the request + * @returns A Jansson object containing the response for the client */ + json_t *(* const query_transport)(json_t *request); + }; /*! \brief Callbacks to contact the Janus core */