[asterisk-commits] res/ari: Register Stasis application on WebSocket attempt (asterisk[master])

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri May 22 11:19:45 CDT 2015


Matt Jordan has submitted this change and it was merged.

Change subject: res/ari: Register Stasis application on WebSocket attempt
......................................................................


res/ari: Register Stasis application on WebSocket attempt

Prior to this patch, when a WebSocket connection is made, ARI would not
be informed of the connection until after the WebSocket layer had
accepted the connection. This created a brief race condition where the
ARI client would be notified that it was connected, a channel would be
sent into the Stasis dialplan application, but ARI would not yet have
registered the Stasis application presented in the HTTP request that
established the WebSocket.

This patch resolves this issue by doing the following:
 * When a WebSocket attempt is made, a callback is made into the ARI
   application layer, which verifies and registers the apps presented in
   the HTTP request. Because we do not yet have a WebSocket, we cannot
   have an event session for the corresponding applications. Some
   defensive checks were thus added to make the application objects
   tolerant to a NULL event session.
 * When a WebSocket connection is made, the registered application is
   updated with the newly created event session that wraps the WebSocket
   connection.

ASTERISK-24988 #close
Reported by: Joshua Colp

Change-Id: Ia5dc60dc2b6bee76cd5aff0f69dd53b36e83f636
---
M res/ari/resource_events.c
M res/ari/resource_events.h
M res/res_ari_events.c
M res/stasis/app.c
M rest-api-templates/ari_resource.h.mustache
M rest-api-templates/res_ari_resource.c.mustache
6 files changed, 231 insertions(+), 25 deletions(-)

Approvals:
  Matt Jordan: Looks good to me, approved; Verified
  Joshua Colp: Looks good to me, but someone else must approve



diff --git a/res/ari/resource_events.c b/res/ari/resource_events.c
index eddcb66..e666f2e 100644
--- a/res/ari/resource_events.c
+++ b/res/ari/resource_events.c
@@ -119,6 +119,10 @@
 	const char *msg_application = S_OR(
 		ast_json_string_get(ast_json_object_get(message, "application")),
 		"");
+
+	if (!session) {
+		return;
+	}
  
 	/* Determine if we've been replaced */
 	if (strcmp(msg_type, "ApplicationReplaced") == 0 &&
@@ -168,7 +172,40 @@
 	return 0;
 }
 
-void ast_ari_websocket_events_event_websocket(struct ast_ari_websocket_session *ws_session,
+int ast_ari_websocket_events_event_websocket_attempted(struct ast_tcptls_session_instance *ser,
+	struct ast_variable *headers,
+	struct ast_ari_events_event_websocket_args *args)
+{
+	int res = 0;
+	size_t i, j;
+
+	ast_debug(3, "/events WebSocket attempted\n");
+
+	if (args->app_count == 0) {
+		ast_http_error(ser, 400, "Bad Request", "Missing param 'app'");
+		return -1;
+	}
+
+	for (i = 0; i < args->app_count; ++i) {
+		if (ast_strlen_zero(args->app[i])) {
+			res = -1;
+			break;
+		}
+
+		res |= stasis_app_register(args->app[i], app_handler, NULL);
+	}
+
+	if (res) {
+		for (j = 0; j < i; ++j) {
+			stasis_app_unregister(args->app[j]);
+		}
+		ast_http_error(ser, 400, "Bad Request", "Invalid application provided in param 'app'.");
+	}
+
+	return res;
+}
+
+void ast_ari_websocket_events_event_websocket_established(struct ast_ari_websocket_session *ws_session,
 	struct ast_variable *headers,
 	struct ast_ari_events_event_websocket_args *args)
 {
diff --git a/res/ari/resource_events.h b/res/ari/resource_events.h
index 646cf9b..2b63181 100644
--- a/res/ari/resource_events.h
+++ b/res/ari/resource_events.h
@@ -48,6 +48,19 @@
 	/*! Parsing context for app. */
 	char *app_parse;
 };
+
+/*!
+ * \brief WebSocket connection for events.
+ *
+ * \param ser HTTP TCP/TLS Server Session
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ *
+ * \retval 0 success
+ * \retval non-zero error
+ */
+int ast_ari_websocket_events_event_websocket_attempted(struct ast_tcptls_session_instance *ser, struct ast_variable *headers, struct ast_ari_events_event_websocket_args *args);
+
 /*!
  * \brief WebSocket connection for events.
  *
@@ -55,7 +68,7 @@
  * \param headers HTTP headers.
  * \param args Swagger parameters.
  */
-void ast_ari_websocket_events_event_websocket(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_events_event_websocket_args *args);
+void ast_ari_websocket_events_event_websocket_established(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_events_event_websocket_args *args);
 /*! Argument struct for ast_ari_events_user_event() */
 struct ast_ari_events_user_event_args {
 	/*! Event name */
diff --git a/res/res_ari_events.c b/res/res_ari_events.c
index 66b01a7..4542339 100644
--- a/res/res_ari_events.c
+++ b/res/res_ari_events.c
@@ -53,7 +53,92 @@
 
 #define MAX_VALS 128
 
-static void ast_ari_events_event_websocket_ws_cb(struct ast_websocket *ws_session,
+static int ast_ari_events_event_websocket_ws_attempted_cb(struct ast_tcptls_session_instance *ser, struct ast_variable *get_params, struct ast_variable *headers)
+{
+	struct ast_ari_events_event_websocket_args args = {};
+	int res = 0;
+	RAII_VAR(struct ast_ari_response *, response, NULL, ast_free);
+	struct ast_variable *i;
+
+	response = ast_calloc(1, sizeof(*response));
+	if (!response) {
+		ast_log(LOG_ERROR, "Failed to create response.\n");
+		goto fin;
+	}
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "app") == 0) {
+			/* Parse comma separated list */
+			char *vals[MAX_VALS];
+			size_t j;
+
+			args.app_parse = ast_strdup(i->value);
+			if (!args.app_parse) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (strlen(args.app_parse) == 0) {
+				/* ast_app_separate_args can't handle "" */
+				args.app_count = 1;
+				vals[0] = args.app_parse;
+			} else {
+				args.app_count = ast_app_separate_args(
+					args.app_parse, ',', vals,
+					ARRAY_LEN(vals));
+			}
+
+			if (args.app_count == 0) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			if (args.app_count >= MAX_VALS) {
+				ast_ari_response_error(response, 400,
+					"Bad Request",
+					"Too many values for app");
+				goto fin;
+			}
+
+			args.app = ast_malloc(sizeof(*args.app) * args.app_count);
+			if (!args.app) {
+				ast_ari_response_alloc_failed(response);
+				goto fin;
+			}
+
+			for (j = 0; j < args.app_count; ++j) {
+				args.app[j] = (vals[j]);
+			}
+		} else
+		{}
+	}
+
+	res = ast_ari_websocket_events_event_websocket_attempted(ser, headers, &args);
+
+fin: __attribute__((unused))
+	if (!response) {
+		ast_http_error(ser, 500, "Server Error", "Memory allocation error");
+		res = -1;
+	} else if (response->response_code != 0) {
+		/* Param parsing failure */
+		RAII_VAR(char *, msg, NULL, ast_json_free);
+		if (response->message) {
+			msg = ast_json_dump_string(response->message);
+		} else {
+			ast_log(LOG_ERROR, "Missing response message\n");
+		}
+
+		if (msg) {
+			ast_http_error(ser, response->response_code, response->response_text, msg);
+		}
+		res = -1;
+	}
+	ast_free(args.app_parse);
+	ast_free(args.app);
+	return res;
+}
+
+static void ast_ari_events_event_websocket_ws_established_cb(struct ast_websocket *ws_session,
 	struct ast_variable *get_params, struct ast_variable *headers)
 {
 	struct ast_ari_events_event_websocket_args args = {};
@@ -126,16 +211,11 @@
 		{}
 	}
 
-	ast_ari_websocket_events_event_websocket(session, headers, &args);
+	ast_ari_websocket_events_event_websocket_established(session, headers, &args);
 
 fin: __attribute__((unused))
 	if (response && response->response_code != 0) {
 		/* Param parsing failure */
-		/* TODO - ideally, this would return the error code to the
-		 * HTTP client; but we've already done the WebSocket
-		 * negotiation. Param parsing should happen earlier, but we
-		 * need a way to pass it through the WebSocket code to the
-		 * callback */
 		RAII_VAR(char *, msg, NULL, ast_json_free);
 		if (response->message) {
 			msg = ast_json_dump_string(response->message);
@@ -351,12 +431,22 @@
 static int load_module(void)
 {
 	int res = 0;
+	struct ast_websocket_protocol *protocol;
+
 	events.ws_server = ast_websocket_server_create();
 	if (!events.ws_server) {
 		return AST_MODULE_LOAD_FAILURE;
 	}
-	res |= ast_websocket_server_add_protocol(events.ws_server,
-		"ari", ast_ari_events_event_websocket_ws_cb);
+
+	protocol = ast_websocket_sub_protocol_alloc("ari");
+	if (!protocol) {
+		ao2_ref(events.ws_server, -1);
+		events.ws_server = NULL;
+		return AST_MODULE_LOAD_FAILURE;
+	}
+	protocol->session_attempted = ast_ari_events_event_websocket_ws_attempted_cb;
+	protocol->session_established = ast_ari_events_event_websocket_ws_established_cb;
+	res |= ast_websocket_server_add_protocol2(events.ws_server, protocol);
 	stasis_app_ref();
 	res |= ast_ari_add_handler(&events);
 	return res;
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 5a1c5f8..b99e232 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -871,8 +871,7 @@
 
 	strncpy(app->name, name, size - sizeof(*app));
 	app->handler = handler;
-	ao2_ref(data, +1);
-	app->data = data;
+	app->data = ao2_bump(data);
 
 	ao2_ref(app, +1);
 	return app;
@@ -950,7 +949,7 @@
 {
 	SCOPED_AO2LOCK(lock, app);
 
-	if (app->handler) {
+	if (app->handler && app->data) {
 		RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 
 		ast_verb(1, "Replacing Stasis app '%s'\n", app->name);
diff --git a/rest-api-templates/ari_resource.h.mustache b/rest-api-templates/ari_resource.h.mustache
index 3a20776..d3f40b6 100644
--- a/rest-api-templates/ari_resource.h.mustache
+++ b/rest-api-templates/ari_resource.h.mustache
@@ -89,6 +89,23 @@
 void ast_ari_{{c_name}}_{{c_nickname}}(struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args, struct ast_ari_response *response);
 {{/is_req}}
 {{#is_websocket}}
+
+/*!
+ * \brief {{summary}}
+{{#notes}}
+ *
+ * {{{notes}}}
+{{/notes}}
+ *
+ * \param ser HTTP TCP/TLS Server Session
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ *
+ * \retval 0 success
+ * \retval non-zero error
+ */
+int ast_ari_websocket_{{c_name}}_{{c_nickname}}_attempted(struct ast_tcptls_session_instance *ser, struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args);
+
 /*!
  * \brief {{summary}}
 {{#notes}}
@@ -100,7 +117,7 @@
  * \param headers HTTP headers.
  * \param args Swagger parameters.
  */
-void ast_ari_websocket_{{c_name}}_{{c_nickname}}(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args);
+void ast_ari_websocket_{{c_name}}_{{c_nickname}}_established(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args);
 {{/is_websocket}}
 {{/operations}}
 {{/apis}}
diff --git a/rest-api-templates/res_ari_resource.c.mustache b/rest-api-templates/res_ari_resource.c.mustache
index 7d138b7..7fe360e 100644
--- a/rest-api-templates/res_ari_resource.c.mustache
+++ b/rest-api-templates/res_ari_resource.c.mustache
@@ -137,7 +137,52 @@
 }
 {{/is_req}}
 {{#is_websocket}}
-static void ast_ari_{{c_name}}_{{c_nickname}}_ws_cb(struct ast_websocket *ws_session,
+static int ast_ari_{{c_name}}_{{c_nickname}}_ws_attempted_cb(struct ast_tcptls_session_instance *ser, struct ast_variable *get_params, struct ast_variable *headers)
+{
+	struct ast_ari_{{c_name}}_{{c_nickname}}_args args = {};
+{{#has_parameters}}
+	int res = 0;
+	RAII_VAR(struct ast_ari_response *, response, NULL, ast_free);
+	struct ast_variable *i;
+{{/has_parameters}}
+
+{{#has_parameters}}
+	response = ast_calloc(1, sizeof(*response));
+	if (!response) {
+		ast_log(LOG_ERROR, "Failed to create response.\n");
+		goto fin;
+	}
+{{/has_parameters}}
+
+{{> param_parsing}}
+
+	res = ast_ari_websocket_{{c_name}}_{{c_nickname}}_attempted(ser, headers, &args);
+
+fin: __attribute__((unused))
+	if (!response) {
+		ast_http_error(ser, 500, "Server Error", "Memory allocation error");
+		res = -1;
+	} else if (response->response_code != 0) {
+		/* Param parsing failure */
+		RAII_VAR(char *, msg, NULL, ast_json_free);
+		if (response->message) {
+			msg = ast_json_dump_string(response->message);
+		} else {
+			ast_log(LOG_ERROR, "Missing response message\n");
+		}
+
+		if (msg) {
+			ast_http_error(ser, response->response_code, response->response_text, msg);
+		}
+		res = -1;
+	}
+{{> param_cleanup}}
+{{#has_parameters}}
+	return res;
+{{/has_parameters}}
+}
+
+static void ast_ari_{{c_name}}_{{c_nickname}}_ws_established_cb(struct ast_websocket *ws_session,
 	struct ast_variable *get_params, struct ast_variable *headers)
 {
 	struct ast_ari_{{c_name}}_{{c_nickname}}_args args = {};
@@ -175,16 +220,11 @@
 
 {{> param_parsing}}
 
-	ast_ari_websocket_{{c_name}}_{{c_nickname}}(session, headers, &args);
+	ast_ari_websocket_{{c_name}}_{{c_nickname}}_established(session, headers, &args);
 
 fin: __attribute__((unused))
 	if (response && response->response_code != 0) {
 		/* Param parsing failure */
-		/* TODO - ideally, this would return the error code to the
-		 * HTTP client; but we've already done the WebSocket
-		 * negotiation. Param parsing should happen earlier, but we
-		 * need a way to pass it through the WebSocket code to the
-		 * callback */
 		RAII_VAR(char *, msg, NULL, ast_json_free);
 		if (response->message) {
 			msg = ast_json_dump_string(response->message);
@@ -211,16 +251,26 @@
 {
 	int res = 0;
 {{#apis}}
+{{#operations}}
 {{#has_websocket}}
+	struct ast_websocket_protocol *protocol;
+
 	{{full_name}}.ws_server = ast_websocket_server_create();
 	if (!{{full_name}}.ws_server) {
 		return AST_MODULE_LOAD_FAILURE;
 	}
+
+	protocol = ast_websocket_sub_protocol_alloc("{{websocket_protocol}}");
+	if (!protocol) {
+		ao2_ref({{full_name}}.ws_server, -1);
+		{{full_name}}.ws_server = NULL;
+		return AST_MODULE_LOAD_FAILURE;
+	}
+	protocol->session_attempted = ast_ari_{{c_name}}_{{c_nickname}}_ws_attempted_cb;
+	protocol->session_established = ast_ari_{{c_name}}_{{c_nickname}}_ws_established_cb;
 {{/has_websocket}}
-{{#operations}}
 {{#is_websocket}}
-	res |= ast_websocket_server_add_protocol({{full_name}}.ws_server,
-		"{{websocket_protocol}}", ast_ari_{{c_name}}_{{c_nickname}}_ws_cb);
+	res |= ast_websocket_server_add_protocol2({{full_name}}.ws_server, protocol);
 {{/is_websocket}}
 {{/operations}}
 {{/apis}}

-- 
To view, visit https://gerrit.asterisk.org/520
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ia5dc60dc2b6bee76cd5aff0f69dd53b36e83f636
Gerrit-PatchSet: 1
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>



More information about the asterisk-commits mailing list