ARI: REST over Websocket

This commit adds the ability to make ARI REST requests over the same
websocket used to receive events.

For full details on how to use the new capability, visit...

https://docs.asterisk.org/Configuration/Interfaces/Asterisk-REST-Interface-ARI/ARI-REST-over-WebSocket/

Changes:

* Added utilities to http.c:
  * ast_get_http_method_from_string().
  * ast_http_parse_post_form().
* Added utilities to json.c:
  * ast_json_nvp_array_to_ast_variables().
  * ast_variables_to_json_nvp_array().
* Added definitions for new events to carry REST responses.
* Created res/ari/ari_websocket_requests.c to house the new request handlers.
* Moved non-event specific code out of res/ari/resource_events.c into
  res/ari/ari_websockets.c
* Refactored res/res_ari.c to move non-http code out of ast_ari_callback()
  (which is http specific) and into ast_ari_invoke() so it can be shared
  between both the http and websocket transports.

UpgradeNote: This commit adds the ability to make ARI REST requests over the same
websocket used to receive events.
See https://docs.asterisk.org/Configuration/Interfaces/Asterisk-REST-Interface-ARI/ARI-REST-over-WebSocket/
This commit is contained in:
George Joseph
2025-03-12 15:58:51 -06:00
parent ca8adc2454
commit 6bc055416b
20 changed files with 2154 additions and 1300 deletions

View File

@@ -203,6 +203,19 @@ const char *ast_get_http_method(enum ast_http_method method)
return NULL;
}
enum ast_http_method ast_get_http_method_from_string(const char *method)
{
int x;
for (x = 0; x < ARRAY_LEN(ast_http_methods_text); x++) {
if (ast_strings_equal(method, ast_http_methods_text[x].text)) {
return ast_http_methods_text[x].method;
}
}
return AST_HTTP_UNKNOWN;
}
const char *ast_http_ftype2mtype(const char *ftype)
{
int x;
@@ -1353,33 +1366,21 @@ struct ast_json *ast_http_get_json(
* get post variables from client Request Entity-Body, if content type is
* application/x-www-form-urlencoded
*/
struct ast_variable *ast_http_get_post_vars(
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
struct ast_variable *ast_http_parse_post_form(char *buf, int content_length,
const char *content_type)
{
int content_length = 0;
struct ast_variable *v, *post_vars=NULL, *prev = NULL;
char *var, *val;
RAII_VAR(char *, buf, NULL, ast_free);
RAII_VAR(char *, type, get_content_type(headers), ast_free);
/* Use errno to distinguish errors from no params */
errno = 0;
if (ast_strlen_zero(type) ||
strcasecmp(type, "application/x-www-form-urlencoded")) {
if (ast_strlen_zero(content_type) ||
strcasecmp(content_type, "application/x-www-form-urlencoded") != 0) {
/* Content type is not form data. Don't read the body. */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
if (!buf || !content_length) {
/*
* errno already set
* or it is not an error to have zero content
*/
return NULL;
}
while ((val = strsep(&buf, "&"))) {
var = strsep(&val, "=");
if (val) {
@@ -1401,6 +1402,34 @@ struct ast_variable *ast_http_get_post_vars(
return post_vars;
}
struct ast_variable *ast_http_get_post_vars(
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
int content_length = 0;
RAII_VAR(char *, buf, NULL, ast_free);
RAII_VAR(char *, type, get_content_type(headers), ast_free);
/* Use errno to distinguish errors from no params */
errno = 0;
if (ast_strlen_zero(type) ||
strcasecmp(type, "application/x-www-form-urlencoded")) {
/* Content type is not form data. Don't read the body. */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
if (!buf || !content_length) {
/*
* errno already set
* or it is not an error to have zero content
*/
return NULL;
}
return ast_http_parse_post_form(buf, content_length, type);
}
static int handle_uri(struct ast_tcptls_session_instance *ser, char *uri,
enum ast_http_method method, struct ast_variable *headers)
{

View File

@@ -861,6 +861,83 @@ enum ast_json_to_ast_vars_code ast_json_to_ast_variables(struct ast_json *json_v
return AST_JSON_TO_AST_VARS_CODE_SUCCESS;
}
enum ast_json_nvp_ast_vars_code ast_json_nvp_array_to_ast_variables(
struct ast_json *json_variables, struct ast_variable **variables)
{
struct ast_variable *tail = NULL;
int i = 0;
size_t len = json_variables ? ast_json_array_size(json_variables) : 0;
if (len == 0) {
return AST_JSON_NVP_AST_VARS_CODE_NO_INPUT;
}
for (i = 0; i < len; i++) {
struct ast_variable *new_var;
struct ast_json *json_value;
struct ast_json *json_key;
const char *key;
const char *value;
json_value = ast_json_array_get(json_variables, i);
if (!json_value || ast_json_is_null(json_value) || ast_json_typeof(json_value) != AST_JSON_OBJECT) {
/* Error: Only objects allowed */
return AST_JSON_NVP_AST_VARS_CODE_INVALID_TYPE;
}
json_key = ast_json_object_get(json_value, "name");
if (!json_key || ast_json_is_null(json_key) || ast_json_typeof(json_key) != AST_JSON_STRING) {
/* Error: Only strings allowed */
return AST_JSON_NVP_AST_VARS_CODE_INVALID_TYPE;
}
key = ast_json_string_get(json_key);
json_key = ast_json_object_get(json_value, "value");
if (!json_key || ast_json_is_null(json_key) || ast_json_typeof(json_key) != AST_JSON_STRING) {
/* Error: Only strings allowed */
return AST_JSON_NVP_AST_VARS_CODE_INVALID_TYPE;
}
value = ast_json_string_get(json_key);
new_var = ast_variable_new(key, value, "");
if (!new_var) {
/* Error: OOM */
return AST_JSON_NVP_AST_VARS_CODE_OOM;
}
tail = ast_variable_list_append_hint(variables, tail, new_var);
}
return AST_JSON_NVP_AST_VARS_CODE_SUCCESS;
}
struct ast_json *ast_variables_to_json_nvp_array(struct ast_variable *variables)
{
struct ast_variable *v = NULL;
struct ast_json *json_variables = ast_json_array_create();
if (!variables || !json_variables) {
return NULL;
}
for (v = variables; v; v = v->next) {
struct ast_json *obj = ast_json_pack("{s: s, s: s}",
"name", v->name,
"value", v->value);
if (!obj) {
ast_json_unref(json_variables);
return NULL;
}
if (ast_json_array_append(json_variables, obj)) {
ast_json_unref(json_variables);
ast_json_unref(obj);
return NULL;
}
}
return json_variables;
}
struct ast_json *ast_json_channel_vars(struct varshead *channelvars)
{
struct ast_json *ret;