When a client takes a long time to process information received from Asterisk, a write operation using fwrite may fail to write all information. This causes the underlying file stream to be in an unknown state, such that the socket must be disconnected. Unfortunately, there are two problems with this in Asterisk's existing websocket code: 1. Periodically, during the read loop, Asterisk must write to the connected websocket to respond to pings. As such, Asterisk maintains a reference to the session during the loop. When ast_http_websocket_write fails, it may cause the session to decrement its ref count, but this in and of itself does not break the read loop. The read loop's write, on the other hand, does not break the loop if it fails. This causes the socket to get in a 'stuck' state, preventing the client from reconnecting to the server. 2. More importantly, however, is that the fwrite in ast_http_websocket_write fails with a large volume of data when the client takes awhile to process the information. When it does fail, it fails writing only a portion of the bytes. With some debugging, it was shown that this was failing in a similar fashion to ASTERISK-12767. Switching this over to ast_careful_fwrite with a long enough timeout solved the problem. Note that this version of the patch, unlike r417310 in Asterisk 11, exposes configuration options beyond just chan_sip's sip.conf. Configuration options to configure the write timeout have also been added to pjsip.conf and ari.conf. #ASTERISK-23917 #close Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/3624/ ........ Merged revisions 417310 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 417311 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@417317 65c4cc65-6c06-0410-ace0-fbb531ad65f3
191 lines
4.6 KiB
C
191 lines
4.6 KiB
C
/*
|
|
* Asterisk -- An open source telephony toolkit.
|
|
*
|
|
* Copyright (C) 2013, Digium, Inc.
|
|
*
|
|
* David M. Lee, II <dlee@digium.com>
|
|
*
|
|
* See http://www.asterisk.org for more information about
|
|
* the Asterisk project. Please do not directly contact
|
|
* any of the maintainers of this project for assistance;
|
|
* the project provides a web site, mailing lists and IRC
|
|
* channels for your use.
|
|
*
|
|
* This program is free software, distributed under the terms of
|
|
* the GNU General Public License Version 2. See the LICENSE file
|
|
* at the top of the source tree.
|
|
*/
|
|
|
|
#include "asterisk.h"
|
|
|
|
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|
|
|
#include "asterisk/ari.h"
|
|
#include "asterisk/astobj2.h"
|
|
#include "asterisk/http_websocket.h"
|
|
#include "internal.h"
|
|
|
|
/*! \file
|
|
*
|
|
* \brief WebSocket support for RESTful API's.
|
|
* \author David M. Lee, II <dlee@digium.com>
|
|
*/
|
|
|
|
struct ast_ari_websocket_session {
|
|
struct ast_websocket *ws_session;
|
|
int (*validator)(struct ast_json *);
|
|
};
|
|
|
|
static void websocket_session_dtor(void *obj)
|
|
{
|
|
struct ast_ari_websocket_session *session = obj;
|
|
|
|
ast_websocket_unref(session->ws_session);
|
|
session->ws_session = NULL;
|
|
}
|
|
|
|
/*!
|
|
* \brief Validator that always succeeds.
|
|
*/
|
|
static int null_validator(struct ast_json *json)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
struct ast_ari_websocket_session *ast_ari_websocket_session_create(
|
|
struct ast_websocket *ws_session, int (*validator)(struct ast_json *))
|
|
{
|
|
RAII_VAR(struct ast_ari_websocket_session *, session, NULL, ao2_cleanup);
|
|
RAII_VAR(struct ast_ari_conf *, config, ast_ari_config_get(), ao2_cleanup);
|
|
|
|
if (ws_session == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (config == NULL || config->general == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (validator == NULL) {
|
|
validator = null_validator;
|
|
}
|
|
|
|
if (ast_websocket_set_nonblock(ws_session) != 0) {
|
|
ast_log(LOG_ERROR,
|
|
"ARI web socket failed to set nonblock; closing: %s\n",
|
|
strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
if (ast_websocket_set_timeout(ws_session, config->general->write_timeout)) {
|
|
ast_log(LOG_WARNING, "Failed to set write timeout %d on ARI web socket\n",
|
|
config->general->write_timeout);
|
|
}
|
|
|
|
session = ao2_alloc(sizeof(*session), websocket_session_dtor);
|
|
if (!session) {
|
|
return NULL;
|
|
}
|
|
|
|
ao2_ref(ws_session, +1);
|
|
session->ws_session = ws_session;
|
|
session->validator = validator;
|
|
|
|
ao2_ref(session, +1);
|
|
return session;
|
|
}
|
|
|
|
struct ast_json *ast_ari_websocket_session_read(
|
|
struct ast_ari_websocket_session *session)
|
|
{
|
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
|
|
|
while (!message) {
|
|
int res;
|
|
char *payload;
|
|
uint64_t payload_len;
|
|
enum ast_websocket_opcode opcode;
|
|
int fragmented;
|
|
|
|
res = ast_wait_for_input(
|
|
ast_websocket_fd(session->ws_session), -1);
|
|
|
|
if (res <= 0) {
|
|
ast_log(LOG_WARNING, "WebSocket poll error: %s\n",
|
|
strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
res = ast_websocket_read(session->ws_session, &payload,
|
|
&payload_len, &opcode, &fragmented);
|
|
|
|
if (res != 0) {
|
|
ast_log(LOG_WARNING, "WebSocket read error: %s\n",
|
|
strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
switch (opcode) {
|
|
case AST_WEBSOCKET_OPCODE_CLOSE:
|
|
ast_debug(1, "WebSocket closed by peer\n");
|
|
return NULL;
|
|
case AST_WEBSOCKET_OPCODE_TEXT:
|
|
message = ast_json_load_buf(payload, payload_len, NULL);
|
|
if (message == NULL) {
|
|
ast_log(LOG_WARNING,
|
|
"WebSocket input failed to parse\n");
|
|
}
|
|
break;
|
|
default:
|
|
/* Ignore all other message types */
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ast_json_ref(message);
|
|
}
|
|
|
|
#define VALIDATION_FAILED \
|
|
"{" \
|
|
" \"error\": \"InvalidMessage\"," \
|
|
" \"message\": \"Message validation failed\"" \
|
|
"}"
|
|
|
|
int ast_ari_websocket_session_write(struct ast_ari_websocket_session *session,
|
|
struct ast_json *message)
|
|
{
|
|
RAII_VAR(char *, str, NULL, ast_json_free);
|
|
|
|
#ifdef AST_DEVMODE
|
|
if (!session->validator(message)) {
|
|
ast_log(LOG_ERROR, "Outgoing message failed validation\n");
|
|
return ast_websocket_write(session->ws_session,
|
|
AST_WEBSOCKET_OPCODE_TEXT, VALIDATION_FAILED,
|
|
strlen(VALIDATION_FAILED));
|
|
}
|
|
#endif
|
|
|
|
str = ast_json_dump_string_format(message, ast_ari_json_format());
|
|
|
|
if (str == NULL) {
|
|
ast_log(LOG_ERROR, "Failed to encode JSON object\n");
|
|
return -1;
|
|
}
|
|
|
|
ast_debug(3, "Examining ARI event: \n%s\n", str);
|
|
return ast_websocket_write(session->ws_session,
|
|
AST_WEBSOCKET_OPCODE_TEXT, str, strlen(str));
|
|
}
|
|
|
|
void ari_handle_websocket(struct ast_websocket_server *ws_server,
|
|
struct ast_tcptls_session_instance *ser, const char *uri,
|
|
enum ast_http_method method, struct ast_variable *get_params,
|
|
struct ast_variable *headers)
|
|
{
|
|
struct ast_http_uri fake_urih = {
|
|
.data = ws_server,
|
|
};
|
|
ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params,
|
|
headers);
|
|
}
|