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
committed by github-actions[bot]
parent edadca7151
commit 3d5ae0b5e1
20 changed files with 2154 additions and 1300 deletions

View File

@@ -50,7 +50,7 @@ struct ast_ari_response;
/*!
* \brief Callback type for RESTful method handlers.
* \param ser TCP/TLS session object
* \param ser TCP/TLS session object (Maybe NULL if not available).
* \param get_params GET parameters from the HTTP request.
* \param path_vars Path variables from any wildcard path segments.
* \param headers HTTP headers from the HTTP requiest.
@@ -78,8 +78,17 @@ struct stasis_rest_handlers {
int is_wildcard;
/*! Callbacks for all handled HTTP methods. */
stasis_rest_callback callbacks[AST_HTTP_MAX_METHOD];
/*! WebSocket server for handling WebSocket upgrades. */
struct ast_websocket_server *ws_server;
/*!
* ws_server is no longer needed to indicate if a path should cause
* an Upgrade to websocket but is kept for backwards compatability.
* Instead, simply set is_websocket to true.
*/
union {
/*! \deprecated WebSocket server for handling WebSocket upgrades. */
struct ast_websocket_server *ws_server;
/*! The path segment is handled by the websocket */
int is_websocket;
};
/*! Number of children in the children array */
size_t num_children;
/*! Handlers for sub-paths */
@@ -121,6 +130,26 @@ int ast_ari_add_handler(struct stasis_rest_handlers *handler);
*/
int ast_ari_remove_handler(struct stasis_rest_handlers *handler);
/*!
* \internal
* \brief Stasis RESTful invocation handler response codes.
*/
enum ast_ari_invoke_result {
ARI_INVOKE_RESULT_SUCCESS = 0,
ARI_INVOKE_RESULT_ERROR_CONTINUE = -1,
ARI_INVOKE_RESULT_ERROR_CLOSE = -2,
};
/*!
* \internal
* \brief How was Stasis RESTful invocation handler invoked?
*/
enum ast_ari_invoke_source {
ARI_INVOKE_SOURCE_REST = 0,
ARI_INVOKE_SOURCE_WEBSOCKET,
ARI_INVOKE_SOURCE_TEST,
};
/*!
* \internal
* \brief Stasis RESTful invocation handler.
@@ -135,8 +164,10 @@ int ast_ari_remove_handler(struct stasis_rest_handlers *handler);
* \param headers HTTP headers.
* \param body
* \param[out] response RESTful HTTP response.
* \param is_websocket Flag to indicate if this is a WebSocket request.
*/
void ast_ari_invoke(struct ast_tcptls_session_instance *ser,
enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *ser,
enum ast_ari_invoke_source source, const struct ast_http_uri *urih,
const char *uri, enum ast_http_method method,
struct ast_variable *get_params, struct ast_variable *headers,
struct ast_json *body, struct ast_ari_response *response);
@@ -155,63 +186,6 @@ void ast_ari_invoke(struct ast_tcptls_session_instance *ser,
*/
void ast_ari_get_docs(const char *uri, const char *prefix, struct ast_variable *headers, struct ast_ari_response *response);
/*! \brief Abstraction for reading/writing JSON to a WebSocket */
struct ast_ari_websocket_session;
/*!
* \brief Create an ARI WebSocket session.
*
* If \c NULL is given for the validator function, no validation will be
* performed.
*
* \param ws_session Underlying WebSocket session.
* \param validator Function to validate outgoing messages.
* \return New ARI WebSocket session.
* \retval NULL on error.
*/
struct ast_ari_websocket_session *ast_ari_websocket_session_create(
struct ast_websocket *ws_session, int (*validator)(struct ast_json *));
/*!
* \brief Read a message from an ARI WebSocket.
*
* \param session Session to read from.
* \return Message received.
* \retval NULL if WebSocket could not be read.
*/
struct ast_json *ast_ari_websocket_session_read(
struct ast_ari_websocket_session *session);
/*!
* \brief Send a message to an ARI WebSocket.
*
* \param session Session to write to.
* \param message Message to send.
* \retval 0 on success.
* \retval Non-zero on error.
*/
int ast_ari_websocket_session_write(struct ast_ari_websocket_session *session,
struct ast_json *message);
/*!
* \brief Get the Session ID for an ARI WebSocket.
*
* \param session Session to query.
* \return Session ID.
* \retval NULL on error.
*/
const char *ast_ari_websocket_session_id(
const struct ast_ari_websocket_session *session);
/*!
* \brief Get the remote address from an ARI WebSocket.
*
* \param session Session to write to.
* \return ast_sockaddr (does not have to be freed)
*/
struct ast_sockaddr *ast_ari_websocket_session_get_remote_addr(
struct ast_ari_websocket_session *session);
/*!
* \brief The stock message to return when out of memory.
*

View File

@@ -156,6 +156,11 @@ void ast_http_uri_unlink_all_with_key(const char *key);
*/
const char *ast_get_http_method(enum ast_http_method method) attribute_pure;
/*!
* \brief Return http method from string
*/
enum ast_http_method ast_get_http_method_from_string(const char *method);
/*!
* \brief Return mime type based on extension
* \param ftype filename extension
@@ -279,6 +284,20 @@ int ast_http_body_discard(struct ast_tcptls_session_instance *ser);
*/
struct ast_variable *ast_http_get_post_vars(struct ast_tcptls_session_instance *ser, struct ast_variable *headers);
/*!
* \brief Get post variables from an application/x-www-form-urlencoded buffer
* \param buf input buffer
* \param content_len Buffer length
* \param content_type Content type (must be "application/x-www-form-urlencoded")
*
* \warning The input buffer will be modified by strsep() so pass in a copy
* if you need to keep the original.
*
* \return List of ast_variables from the buffer. Must be freed with ast_variables_destroy().
*/
struct ast_variable *ast_http_parse_post_form(char *buf, int content_length,
const char *content_type);
struct ast_json;
/*!

View File

@@ -1137,6 +1137,63 @@ enum ast_json_to_ast_vars_code {
*/
enum ast_json_to_ast_vars_code ast_json_to_ast_variables(struct ast_json *json_variables, struct ast_variable **variables);
enum ast_json_nvp_ast_vars_code {
/*! \brief Conversion successful */
AST_JSON_NVP_AST_VARS_CODE_SUCCESS,
/*!
* \brief Conversion failed because invalid value type supplied.
* \note Only string values allowed.
*/
AST_JSON_NVP_AST_VARS_CODE_INVALID_TYPE,
/*! \brief Conversion failed because of allocation failure. (Out Of Memory) */
AST_JSON_NVP_AST_VARS_CODE_OOM,
/*! \brief Input was NULL or empty */
AST_JSON_NVP_AST_VARS_CODE_NO_INPUT,
};
/*!
* \brief Convert a \c ast_json array of name/value pairs into an \c ast_variable list
*
* This is the inverse of \ref ast_variables_to_json_nvp_array().
*
* \param json_array The JSON array containing the name/value pairs
* \param[out] variables The ast_variable list containing the name/value pairs
*
* If the variables list already exists, new values are appended to it.
*
* \note The JSON array must be in the following format:
* \code
* [
* {
* "name": "foo",
* "value": "bar"
* },
* {
* "name": "foo2",
* "value": "bar2"
* }
* ]
* \endcode
*
* \warning If an error occurred during parsing the variables list will contain
* all variables that had been successfully parsed before the error.
*
* \return enum ast_json_to_ast_vars_code indicating status.
*/
enum ast_json_nvp_ast_vars_code ast_json_nvp_array_to_ast_variables(
struct ast_json *json_array, struct ast_variable **variables);
/*!
* \brief Convert a \c ast_variable list into a \c ast_json array of name/value pairs
*
* This is the inverse of \ref ast_json_nvp_array_to_ast_variables().
*
* \param variables The ast_variable list to convert
* \return JSON array of name/value pairs. Must be freed with \ref ast_json_unref().
*/
struct ast_json *ast_variables_to_json_nvp_array(struct ast_variable *variables);
struct varshead;
/*!