mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-18 18:58:22 +00:00
Shuffle RESTful URL's around.
This patch moves the RESTful URL's around to more appropriate locations for release. The /stasis URL's are moved to /ari, since Asterisk REST Interface was a more appropriate name than Stasis-HTTP. (Most of the code still has stasis_http references, but they will be cleaned up after there are no more outstanding branches that would have merge conflicts with such a change). A larger change was moving the ARI events WebSocket off of the shared /ws URL to its permanent home on /ari/events. The Swagger code generator was extended to handle "upgrade: websocket" and "websocketProtocol:" attributes on an operation. The WebSocket module was modified to better handle WebSocket servers that have a single registered protocol handler. If a client connections does not specify the Sec-WebSocket-Protocol header, and the server has a single protocol handler registered, the WebSocket server will go ahead and accept the client for that subprotocol. (closes issue ASTERISK-21857) Review: https://reviewboard.asterisk.org/r/2621/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393528 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
216
configure
vendored
216
configure
vendored
@@ -1,5 +1,5 @@
|
||||
#! /bin/sh
|
||||
# From configure.ac Revision: 388770 .
|
||||
# From configure.ac Revision: 392970 .
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for asterisk trunk.
|
||||
#
|
||||
@@ -1063,6 +1063,10 @@ PBX_CRYPTO
|
||||
CRYPTO_DIR
|
||||
CRYPTO_INCLUDE
|
||||
CRYPTO_LIB
|
||||
PBX_CRYPT
|
||||
CRYPT_DIR
|
||||
CRYPT_INCLUDE
|
||||
CRYPT_LIB
|
||||
PBX_CURSES
|
||||
CURSES_DIR
|
||||
CURSES_INCLUDE
|
||||
@@ -1245,6 +1249,7 @@ with_bluetooth
|
||||
with_cap
|
||||
with_cpg
|
||||
with_curses
|
||||
with_crypt
|
||||
with_crypto
|
||||
with_dahdi
|
||||
with_avcodec
|
||||
@@ -1976,6 +1981,7 @@ Optional Packages:
|
||||
--with-cap=PATH use POSIX 1.e capabilities files in PATH
|
||||
--with-cpg=PATH use Corosync files in PATH
|
||||
--with-curses=PATH use curses files in PATH
|
||||
--with-crypt=PATH use password and data encryption files in PATH
|
||||
--with-crypto=PATH use OpenSSL Cryptography files in PATH
|
||||
--with-dahdi=PATH use DAHDI files in PATH
|
||||
--with-avcodec=PATH use Ffmpeg and avcodec files in PATH
|
||||
@@ -8384,6 +8390,38 @@ fi
|
||||
|
||||
|
||||
|
||||
CRYPT_DESCRIP="password and data encryption"
|
||||
CRYPT_OPTION="crypt"
|
||||
PBX_CRYPT=0
|
||||
|
||||
# Check whether --with-crypt was given.
|
||||
if test "${with_crypt+set}" = set; then :
|
||||
withval=$with_crypt;
|
||||
case ${withval} in
|
||||
n|no)
|
||||
USE_CRYPT=no
|
||||
# -1 is a magic value used by menuselect to know that the package
|
||||
# was disabled, other than 'not found'
|
||||
PBX_CRYPT=-1
|
||||
;;
|
||||
y|ye|yes)
|
||||
ac_mandatory_list="${ac_mandatory_list} CRYPT"
|
||||
;;
|
||||
*)
|
||||
CRYPT_DIR="${withval}"
|
||||
ac_mandatory_list="${ac_mandatory_list} CRYPT"
|
||||
;;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
CRYPTO_DESCRIP="OpenSSL Cryptography"
|
||||
CRYPTO_OPTION="crypto"
|
||||
PBX_CRYPTO=0
|
||||
@@ -27776,6 +27814,182 @@ $as_echo "$as_me: WARNING: *** Please install the SQLite3 development package."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find crypt support
|
||||
# * -lcrypt on *NIX
|
||||
# * in libsystem on OS X
|
||||
|
||||
if test "x${PBX_LIBCRYPT}" != "x1" -a "${USE_LIBCRYPT}" != "no"; then
|
||||
pbxlibdir=""
|
||||
# if --with-LIBCRYPT=DIR has been specified, use it.
|
||||
if test "x${LIBCRYPT_DIR}" != "x"; then
|
||||
if test -d ${LIBCRYPT_DIR}/lib; then
|
||||
pbxlibdir="-L${LIBCRYPT_DIR}/lib"
|
||||
else
|
||||
pbxlibdir="-L${LIBCRYPT_DIR}"
|
||||
fi
|
||||
fi
|
||||
pbxfuncname="crypt"
|
||||
if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers
|
||||
AST_LIBCRYPT_FOUND=yes
|
||||
else
|
||||
ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
|
||||
CFLAGS="${CFLAGS} "
|
||||
as_ac_Lib=`$as_echo "ac_cv_lib_crypt_${pbxfuncname}" | $as_tr_sh`
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lcrypt" >&5
|
||||
$as_echo_n "checking for ${pbxfuncname} in -lcrypt... " >&6; }
|
||||
if eval \${$as_ac_Lib+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lcrypt ${pbxlibdir} $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
char ${pbxfuncname} ();
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return ${pbxfuncname} ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"; then :
|
||||
eval "$as_ac_Lib=yes"
|
||||
else
|
||||
eval "$as_ac_Lib=no"
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
eval ac_res=\$$as_ac_Lib
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
|
||||
$as_echo "$ac_res" >&6; }
|
||||
if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
|
||||
AST_LIBCRYPT_FOUND=yes
|
||||
else
|
||||
AST_LIBCRYPT_FOUND=no
|
||||
fi
|
||||
|
||||
CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
|
||||
fi
|
||||
|
||||
# now check for the header.
|
||||
if test "${AST_LIBCRYPT_FOUND}" = "yes"; then
|
||||
LIBCRYPT_LIB="${pbxlibdir} -lcrypt "
|
||||
# if --with-LIBCRYPT=DIR has been specified, use it.
|
||||
if test "x${LIBCRYPT_DIR}" != "x"; then
|
||||
LIBCRYPT_INCLUDE="-I${LIBCRYPT_DIR}/include"
|
||||
fi
|
||||
LIBCRYPT_INCLUDE="${LIBCRYPT_INCLUDE} "
|
||||
if test "xcrypt.h" = "x" ; then # no header, assume found
|
||||
LIBCRYPT_HEADER_FOUND="1"
|
||||
else # check for the header
|
||||
ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
|
||||
CPPFLAGS="${CPPFLAGS} ${LIBCRYPT_INCLUDE}"
|
||||
ac_fn_c_check_header_mongrel "$LINENO" "crypt.h" "ac_cv_header_crypt_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_crypt_h" = xyes; then :
|
||||
LIBCRYPT_HEADER_FOUND=1
|
||||
else
|
||||
LIBCRYPT_HEADER_FOUND=0
|
||||
fi
|
||||
|
||||
|
||||
CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
|
||||
fi
|
||||
if test "x${LIBCRYPT_HEADER_FOUND}" = "x0" ; then
|
||||
LIBCRYPT_LIB=""
|
||||
LIBCRYPT_INCLUDE=""
|
||||
else
|
||||
if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library
|
||||
LIBCRYPT_LIB=""
|
||||
fi
|
||||
PBX_LIBCRYPT=1
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_LIBCRYPT 1
|
||||
_ACEOF
|
||||
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
ac_fn_c_check_func "$LINENO" "crypt" "ac_cv_func_crypt"
|
||||
if test "x$ac_cv_func_crypt" = xyes; then :
|
||||
SYSCRYPT=true
|
||||
else
|
||||
SYSCRYPT=""
|
||||
fi
|
||||
|
||||
|
||||
if test "x$LIBCRYPT_LIB" != "x" ; then
|
||||
CRYPT_LIB="$LIBCRYPT_LIB"
|
||||
CRYPT_INCLUDE="$LIBCRYPT_INCLUDE"
|
||||
|
||||
$as_echo "#define HAVE_CRYPT 1" >>confdefs.h
|
||||
|
||||
elif test "x$SYSCRYPT" != "x" ; then
|
||||
CRYPT_LIB=""
|
||||
CRYPT_INCLUDE=""
|
||||
|
||||
$as_echo "#define HAVE_CRYPT 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# Find crypt_r support
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for crypt_r in -lcrypt" >&5
|
||||
$as_echo_n "checking for crypt_r in -lcrypt... " >&6; }
|
||||
if ${ac_cv_lib_crypt_crypt_r+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lcrypt $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
char crypt_r ();
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return crypt_r ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"; then :
|
||||
ac_cv_lib_crypt_crypt_r=yes
|
||||
else
|
||||
ac_cv_lib_crypt_crypt_r=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_crypt_crypt_r" >&5
|
||||
$as_echo "$ac_cv_lib_crypt_crypt_r" >&6; }
|
||||
if test "x$ac_cv_lib_crypt_crypt_r" = xyes; then :
|
||||
|
||||
$as_echo "#define HAVE_CRYPT_R 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if test "x${PBX_CRYPTO}" != "x1" -a "${USE_CRYPTO}" != "no"; then
|
||||
pbxlibdir=""
|
||||
|
24
configure.ac
24
configure.ac
@@ -388,6 +388,7 @@ AST_EXT_LIB_SETUP([CAP], [POSIX 1.e capabilities], [cap])
|
||||
AST_EXT_LIB_SETUP([COROSYNC], [Corosync], [cpg])
|
||||
AST_EXT_LIB_SETUP_OPTIONAL([COROSYNC_CFG_STATE_TRACK], [A callback only in corosync 1.x], [COROSYNC], [cfg])
|
||||
AST_EXT_LIB_SETUP([CURSES], [curses], [curses])
|
||||
AST_EXT_LIB_SETUP([CRYPT], [password and data encryption], [crypt])
|
||||
AST_EXT_LIB_SETUP([CRYPTO], [OpenSSL Cryptography], [crypto])
|
||||
AST_EXT_LIB_SETUP_OPTIONAL([OPENSSL_SRTP], [OpenSSL SRTP Extension Support], [CRYPTO], [crypto])
|
||||
AST_EXT_LIB_SETUP([DAHDI], [DAHDI], [dahdi])
|
||||
@@ -2148,6 +2149,29 @@ if test "${PBX_SQLITE3}" != 1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find crypt support
|
||||
# * -lcrypt on *NIX
|
||||
# * in libsystem on OS X
|
||||
AST_EXT_LIB_CHECK([LIBCRYPT], [crypt], [crypt], [crypt.h])
|
||||
AC_CHECK_FUNC([crypt], [SYSCRYPT=true], [SYSCRYPT=""])
|
||||
|
||||
if test "x$LIBCRYPT_LIB" != "x" ; then
|
||||
CRYPT_LIB="$LIBCRYPT_LIB"
|
||||
CRYPT_INCLUDE="$LIBCRYPT_INCLUDE"
|
||||
AC_DEFINE([HAVE_CRYPT], [1], [Define to 1 if you have the `crypt' function.])
|
||||
elif test "x$SYSCRYPT" != "x" ; then
|
||||
CRYPT_LIB=""
|
||||
CRYPT_INCLUDE=""
|
||||
AC_DEFINE([HAVE_CRYPT], [1], [Define to 1 if you have the `crypt' function.])
|
||||
fi
|
||||
|
||||
AC_SUBST(CRYPT_LIB)
|
||||
AC_SUBST(CRYPT_INCLUDE)
|
||||
|
||||
# Find crypt_r support
|
||||
AC_CHECK_LIB([crypt], [crypt_r],
|
||||
[AC_DEFINE([HAVE_CRYPT_R], [1], [Define to 1 if you have the `crypt_r' function.])])
|
||||
|
||||
AST_EXT_LIB_CHECK([CRYPTO], [crypto], [AES_encrypt], [openssl/aes.h])
|
||||
|
||||
if test "$PBX_CRYPTO" = "1";
|
||||
|
@@ -149,9 +149,15 @@
|
||||
/* Define to 1 if you have the `cosl' function. */
|
||||
#undef HAVE_COSL
|
||||
|
||||
/* Define to 1 if you have the `crypt' function. */
|
||||
#undef HAVE_CRYPT
|
||||
|
||||
/* Define to 1 if you have the OpenSSL Cryptography library. */
|
||||
#undef HAVE_CRYPTO
|
||||
|
||||
/* Define to 1 if you have the `crypt_r' function. */
|
||||
#undef HAVE_CRYPT_R
|
||||
|
||||
/* Define to 1 if you have a functional curl library. */
|
||||
#undef HAVE_CURL
|
||||
|
||||
|
@@ -112,6 +112,26 @@ struct ast_http_uri {
|
||||
/*! \brief Get cookie from Request headers */
|
||||
struct ast_variable *ast_http_get_cookies(struct ast_variable *headers);
|
||||
|
||||
/*! \brief HTTP authentication information. */
|
||||
struct ast_http_auth {
|
||||
/*! Provided userid. */
|
||||
char *userid;
|
||||
/*! For Basic auth, the provided password. */
|
||||
char *password;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Get HTTP authentication information from headers.
|
||||
*
|
||||
* The returned object is AO2 managed, so clean up with ao2_cleanup().
|
||||
*
|
||||
* \param headers HTTP request headers.
|
||||
* \return HTTP auth structure.
|
||||
* \return \c NULL if no supported HTTP auth headers present.
|
||||
* \since 12
|
||||
*/
|
||||
struct ast_http_auth *ast_http_get_auth(struct ast_variable *headers);
|
||||
|
||||
/*! \brief Register a URI handler */
|
||||
int ast_http_uri_link(struct ast_http_uri *urihandler);
|
||||
|
||||
|
@@ -53,13 +53,17 @@ typedef void (*stasis_rest_callback)(struct ast_variable *get_params,
|
||||
struct stasis_rest_handlers {
|
||||
/*! Path segement to handle */
|
||||
const char *path_segment;
|
||||
/*! If true (non-zero), path_segment is a wildcard, and will match all values.
|
||||
/*! If true (non-zero), path_segment is a wildcard, and will match all
|
||||
* values.
|
||||
*
|
||||
* Value of the segement will be passed into the \a path_vars parameter of the callback.
|
||||
* Value of the segement will be passed into the \a path_vars parameter
|
||||
* of the callback.
|
||||
*/
|
||||
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;
|
||||
/*! Number of children in the children array */
|
||||
size_t num_children;
|
||||
/*! Handlers for sub-paths */
|
||||
@@ -78,7 +82,9 @@ struct stasis_http_response {
|
||||
* See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html */
|
||||
int response_code;
|
||||
/*! Corresponding text for the response code */
|
||||
const char *response_text; // Shouldn't http.c handle this?
|
||||
const char *response_text; /* Shouldn't http.c handle this? */
|
||||
/*! Flag to indicate that no further response is needed */
|
||||
int no_response:1;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -104,14 +110,17 @@ int stasis_http_remove_handler(struct stasis_rest_handlers *handler);
|
||||
* Only call from res_stasis_http and test_stasis_http. Only public to allow
|
||||
* for unit testing.
|
||||
*
|
||||
* \param ser TCP/TLS connection.
|
||||
* \param uri HTTP URI, relative to the API path.
|
||||
* \param method HTTP method.
|
||||
* \param get_params HTTP \c GET parameters.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response RESTful HTTP response.
|
||||
*/
|
||||
void stasis_http_invoke(const char *uri, enum ast_http_method method, struct ast_variable *get_params,
|
||||
struct ast_variable *headers, struct stasis_http_response *response);
|
||||
void stasis_http_invoke(struct ast_tcptls_session_instance *ser,
|
||||
const char *uri, enum ast_http_method method,
|
||||
struct ast_variable *get_params, struct ast_variable *headers,
|
||||
struct stasis_http_response *response);
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
@@ -126,14 +135,49 @@ void stasis_http_invoke(const char *uri, enum ast_http_method method, struct ast
|
||||
*/
|
||||
void stasis_http_get_docs(const char *uri, struct ast_variable *headers, struct stasis_http_response *response);
|
||||
|
||||
/*! \brief Abstraction for reading/writing JSON to a WebSocket */
|
||||
struct ari_websocket_session;
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Stasis WebSocket connection handler
|
||||
* \param session WebSocket session.
|
||||
* \param parameters HTTP \c GET parameters.
|
||||
* \param headers HTTP headers.
|
||||
* \brief Create an ARI WebSocket session.
|
||||
*
|
||||
* \param ws_session Underlying WebSocket session.
|
||||
* \return New ARI WebSocket session.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
void stasis_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers);
|
||||
struct ari_websocket_session *ari_websocket_session_create(
|
||||
struct ast_websocket *ws_session);
|
||||
|
||||
/*!
|
||||
* \brief Read a message from an ARI WebSocket.
|
||||
*
|
||||
* \param session Session to read from.
|
||||
* \return Message received.
|
||||
* \return \c NULL if WebSocket could not be read.
|
||||
*/
|
||||
struct ast_json *ari_websocket_session_read(
|
||||
struct ari_websocket_session *session);
|
||||
|
||||
/*!
|
||||
* \brief Send a message to an ARI WebSocket.
|
||||
*
|
||||
* \param session Session to write to.
|
||||
* \param message Message to send.
|
||||
* \return 0 on success.
|
||||
* \return Non-zero on error.
|
||||
*/
|
||||
int ari_websocket_session_write(struct ari_websocket_session *session,
|
||||
struct ast_json *message);
|
||||
|
||||
/*!
|
||||
* \brief The stock message to return when out of memory.
|
||||
*
|
||||
* The refcount is NOT bumped on this object, so ast_json_ref() if you want to
|
||||
* keep the reference.
|
||||
*
|
||||
* \return JSON message specifying an out-of-memory error.
|
||||
*/
|
||||
struct ast_json *ari_oom_json(void);
|
||||
|
||||
/*!
|
||||
* \brief Fill in an error \a stasis_http_response.
|
||||
|
@@ -938,4 +938,43 @@ char *ast_utils_which(const char *binary, char *fullpath, size_t fullpath_size);
|
||||
void _dtor_ ## varname (vartype * v) { dtor(*v); } \
|
||||
vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)
|
||||
|
||||
/*!
|
||||
* \brief Asterisk wrapper around crypt(3).
|
||||
*
|
||||
* The interpretation of the salt (which determines the password hashing
|
||||
* algorithm) is system specific. Application code should prefer to use
|
||||
* ast_crypt_encrypt() or ast_crypt_validate().
|
||||
*
|
||||
* The returned string is heap allocated, and should be freed with ast_free().
|
||||
*
|
||||
* \param key User's password to crypt.
|
||||
* \param salt Salt to crypt with.
|
||||
* \return Crypted password.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
char *ast_crypt(const char *key, const char *salt);
|
||||
|
||||
/*
|
||||
* \brief Asterisk wrapper around crypt(3) for encrypting passwords.
|
||||
*
|
||||
* This function will generate a random salt and encrypt the given password.
|
||||
*
|
||||
* The returned string is heap allocated, and should be freed with ast_free().
|
||||
*
|
||||
* \param key User's password to crypt.
|
||||
* \return Crypted password.
|
||||
* \return \c NULL on error.
|
||||
*/
|
||||
char *ast_crypt_encrypt(const char *key);
|
||||
|
||||
/*
|
||||
* \brief Asterisk wrapper around crypt(3) for validating passwords.
|
||||
*
|
||||
* \param key User's password to validate.
|
||||
* \param expected Expected result from crypt.
|
||||
* \return True (non-zero) if \a key matches \a expected.
|
||||
* \return False (zero) if \a key doesn't match.
|
||||
*/
|
||||
int ast_crypt_validate(const char *key, const char *expected);
|
||||
|
||||
#endif /* _ASTERISK_UTILS_H */
|
||||
|
@@ -80,5 +80,8 @@ clean::
|
||||
$(if $(filter res_parking,$(EMBEDDED_MODS)),modules.link,res_parking.so): $(subst .c,.o,$(wildcard parking/*.c))
|
||||
$(subst .c,.o,$(wildcard parking/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_parking)
|
||||
|
||||
res_stasis_http.so: stasis_http/ari_websockets.o
|
||||
stasis_http/ari_websockets.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_asterisk)
|
||||
|
||||
# Dependencies for res_stasis_http_*.so are generated, so they're in this file
|
||||
include stasis_http.make
|
||||
|
@@ -282,7 +282,7 @@ void AST_OPTIONAL_API_NAME(ast_websocket_ref)(struct ast_websocket *session)
|
||||
|
||||
void AST_OPTIONAL_API_NAME(ast_websocket_unref)(struct ast_websocket *session)
|
||||
{
|
||||
ao2_ref(session, -1);
|
||||
ao2_cleanup(session);
|
||||
}
|
||||
|
||||
int AST_OPTIONAL_API_NAME(ast_websocket_fd)(struct ast_websocket *session)
|
||||
@@ -497,6 +497,21 @@ int AST_OPTIONAL_API_NAME(ast_websocket_read)(struct ast_websocket *session, cha
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief If the server has exactly one configured protocol, return it.
|
||||
*/
|
||||
static struct websocket_protocol *one_protocol(
|
||||
struct ast_websocket_server *server)
|
||||
{
|
||||
SCOPED_AO2LOCK(lock, server->protocols);
|
||||
|
||||
if (ao2_container_count(server->protocols) != 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ao2_callback(server->protocols, OBJ_NOLOCK, NULL, NULL);
|
||||
}
|
||||
|
||||
int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
|
||||
{
|
||||
struct ast_variable *v;
|
||||
@@ -541,11 +556,18 @@ int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct a
|
||||
ast_http_error(ser, 426, "Upgrade Required", NULL);
|
||||
return -1;
|
||||
} else if (ast_strlen_zero(requested_protocols)) {
|
||||
ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n",
|
||||
ast_sockaddr_stringify(&ser->remote_address));
|
||||
fputs("HTTP/1.1 400 Bad Request\r\n"
|
||||
"Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
|
||||
return -1;
|
||||
/* If there's only a single protocol registered, and the
|
||||
* client doesn't specify what protocol it's using, go ahead
|
||||
* and accept the connection */
|
||||
protocol_handler = one_protocol(server);
|
||||
if (!protocol_handler) {
|
||||
/* Multiple registered subprotocols; client must specify */
|
||||
ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n",
|
||||
ast_sockaddr_stringify(&ser->remote_address));
|
||||
fputs("HTTP/1.1 400 Bad Request\r\n"
|
||||
"Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
|
||||
return -1;
|
||||
}
|
||||
} else if (key1 && key2) {
|
||||
/* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and
|
||||
* http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- not currently supported*/
|
||||
@@ -557,10 +579,8 @@ int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct a
|
||||
}
|
||||
|
||||
/* Iterate through the requested protocols trying to find one that we have a handler for */
|
||||
while ((protocol = strsep(&requested_protocols, ","))) {
|
||||
if ((protocol_handler = ao2_find(server->protocols, ast_strip(protocol), OBJ_KEY))) {
|
||||
break;
|
||||
}
|
||||
while (!protocol_handler && (protocol = strsep(&requested_protocols, ","))) {
|
||||
protocol_handler = ao2_find(server->protocols, ast_strip(protocol), OBJ_KEY);
|
||||
}
|
||||
|
||||
/* If no protocol handler exists bump this back to the requester */
|
||||
|
@@ -13,6 +13,10 @@
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_remote_address;
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_is_secure;
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_set_nonblock;
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_uri_cb;
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_server_create;
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_server_add_protocol;
|
||||
LINKER_SYMBOL_PREFIX*ast_websocket_server_remove_protocol;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
@@ -72,6 +72,7 @@
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend type="module">res_http_websocket</depend>
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
@@ -211,7 +212,12 @@ static ast_mutex_t root_handler_lock;
|
||||
static struct stasis_rest_handlers *root_handler;
|
||||
|
||||
/*! Pre-defined message for allocation failures. */
|
||||
static struct ast_json *alloc_failed_message;
|
||||
static struct ast_json *oom_json;
|
||||
|
||||
struct ast_json *ari_oom_json(void)
|
||||
{
|
||||
return oom_json;
|
||||
}
|
||||
|
||||
int stasis_http_add_handler(struct stasis_rest_handlers *handler)
|
||||
{
|
||||
@@ -286,7 +292,7 @@ static struct stasis_rest_handlers *root_handler_create(void)
|
||||
if (!handler) {
|
||||
return NULL;
|
||||
}
|
||||
handler->path_segment = "stasis";
|
||||
handler->path_segment = "ari";
|
||||
|
||||
ao2_ref(handler, +1);
|
||||
return handler;
|
||||
@@ -325,7 +331,7 @@ void stasis_http_response_no_content(struct stasis_http_response *response)
|
||||
|
||||
void stasis_http_response_alloc_failed(struct stasis_http_response *response)
|
||||
{
|
||||
response->message = ast_json_ref(alloc_failed_message);
|
||||
response->message = ast_json_ref(oom_json);
|
||||
response->response_code = 500;
|
||||
response->response_text = "Internal Server Error";
|
||||
}
|
||||
@@ -495,11 +501,10 @@ static void handle_options(struct stasis_rest_handlers *handler,
|
||||
}
|
||||
}
|
||||
|
||||
void stasis_http_invoke(const char *uri,
|
||||
enum ast_http_method method,
|
||||
struct ast_variable *get_params,
|
||||
struct ast_variable *headers,
|
||||
struct stasis_http_response *response)
|
||||
void stasis_http_invoke(struct ast_tcptls_session_instance *ser,
|
||||
const char *uri, enum ast_http_method method,
|
||||
struct ast_variable *get_params, struct ast_variable *headers,
|
||||
struct stasis_http_response *response)
|
||||
{
|
||||
RAII_VAR(char *, response_text, NULL, ast_free);
|
||||
RAII_VAR(struct stasis_rest_handlers *, root, NULL, ao2_cleanup);
|
||||
@@ -559,6 +564,19 @@ void stasis_http_invoke(const char *uri,
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler->ws_server && method == AST_HTTP_GET) {
|
||||
/* WebSocket! */
|
||||
struct ast_http_uri fake_urih = {
|
||||
.data = handler->ws_server,
|
||||
};
|
||||
ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params,
|
||||
headers);
|
||||
/* Since the WebSocket code handles the connection, we shouldn't
|
||||
* do anything else; setting no_response */
|
||||
response->no_response = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
callback = handler->callbacks[method];
|
||||
if (callback == NULL) {
|
||||
add_allow_header(handler, response);
|
||||
@@ -686,7 +704,7 @@ void stasis_http_get_docs(const char *uri, struct ast_variable *headers,
|
||||
if (host != NULL) {
|
||||
ast_json_object_set(
|
||||
obj, "basePath",
|
||||
ast_json_stringf("http://%s/stasis", host->value));
|
||||
ast_json_stringf("http://%s/ari", host->value));
|
||||
} else {
|
||||
/* Without the host, we don't have the basePath */
|
||||
ast_json_object_del(obj, "basePath");
|
||||
@@ -719,7 +737,7 @@ static void remove_trailing_slash(const char *uri,
|
||||
* is probably our best bet.
|
||||
*/
|
||||
stasis_http_response_error(response, 404, "Not Found",
|
||||
"ARI URLs do not end with a slash. Try /%s", slashless);
|
||||
"ARI URLs do not end with a slash. Try /ari/%s", slashless);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -831,7 +849,14 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
|
||||
}
|
||||
} else {
|
||||
/* Other RESTful resources */
|
||||
stasis_http_invoke(uri, method, get_params, headers, &response);
|
||||
stasis_http_invoke(ser, uri, method, get_params, headers,
|
||||
&response);
|
||||
}
|
||||
|
||||
if (response.no_response) {
|
||||
/* The handler indicates no further response is necessary.
|
||||
* Probably because it already handled it */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Leaving message unset is only allowed for 204 (No Content).
|
||||
@@ -873,7 +898,7 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
|
||||
static struct ast_http_uri http_uri = {
|
||||
.callback = stasis_http_callback,
|
||||
.description = "Asterisk RESTful API",
|
||||
.uri = "stasis",
|
||||
.uri = "ari",
|
||||
|
||||
.has_subtree = 1,
|
||||
.data = NULL,
|
||||
@@ -883,6 +908,14 @@ static struct ast_http_uri http_uri = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
oom_json = ast_json_pack(
|
||||
"{s: s}", "error", "AllocationFailed");
|
||||
|
||||
if (!oom_json) {
|
||||
/* Ironic */
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
|
||||
ast_mutex_init(&root_handler_lock);
|
||||
|
||||
root_handler = root_handler_create();
|
||||
@@ -905,9 +938,6 @@ static int load_module(void)
|
||||
return AST_MODULE_LOAD_DECLINE;
|
||||
}
|
||||
|
||||
alloc_failed_message = ast_json_pack(
|
||||
"{s: s}", "message", "Allocation failed");
|
||||
|
||||
if (is_enabled()) {
|
||||
ast_http_uri_link(&http_uri);
|
||||
}
|
||||
@@ -917,8 +947,8 @@ static int load_module(void)
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
ast_json_unref(alloc_failed_message);
|
||||
alloc_failed_message = NULL;
|
||||
ast_json_unref(oom_json);
|
||||
oom_json = NULL;
|
||||
|
||||
if (is_enabled()) {
|
||||
ast_http_uri_unlink(&http_uri);
|
||||
@@ -951,9 +981,10 @@ static int reload_module(void)
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Stasis HTTP bindings",
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk RESTful Interface",
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.reload = reload_module,
|
||||
.nonoptreq = "res_stasis,res_http_websocket",
|
||||
.load_pri = AST_MODPRI_APP_DEPEND,
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
global:
|
||||
LINKER_SYMBOL_PREFIXstasis_http_*;
|
||||
LINKER_SYMBOL_PREFIXari_*;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
@@ -88,8 +88,10 @@ static struct stasis_rest_handlers asterisk = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&asterisk);
|
||||
res |= stasis_http_add_handler(&asterisk);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -276,8 +276,10 @@ static struct stasis_rest_handlers bridges = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&bridges);
|
||||
res |= stasis_http_add_handler(&bridges);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -528,8 +528,10 @@ static struct stasis_rest_handlers channels = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&channels);
|
||||
res |= stasis_http_add_handler(&channels);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -139,8 +139,10 @@ static struct stasis_rest_handlers endpoints = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&endpoints);
|
||||
res |= stasis_http_add_handler(&endpoints);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -45,17 +45,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
#include "asterisk/stasis_app.h"
|
||||
#include "stasis_http/resource_events.h"
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /events.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void stasis_http_event_websocket_cb(
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct stasis_http_response *response)
|
||||
static void stasis_http_event_websocket_ws_cb(struct ast_websocket *ws_session,
|
||||
struct ast_variable *get_params, struct ast_variable *headers)
|
||||
{
|
||||
RAII_VAR(struct ast_websocket *, s, ws_session, ast_websocket_unref);
|
||||
RAII_VAR(struct ari_websocket_session *, session, NULL, ao2_cleanup);
|
||||
struct ast_event_websocket_args args = {};
|
||||
struct ast_variable *i;
|
||||
|
||||
@@ -65,14 +59,18 @@ static void stasis_http_event_websocket_cb(
|
||||
} else
|
||||
{}
|
||||
}
|
||||
stasis_http_event_websocket(headers, &args, response);
|
||||
session = ari_websocket_session_create(ws_session);
|
||||
if (!session) {
|
||||
ast_log(LOG_ERROR, "Failed to create ARI session\n");
|
||||
return;
|
||||
}
|
||||
ari_websocket_event_websocket(session, headers, &args);
|
||||
}
|
||||
|
||||
/*! \brief REST handler for /api-docs/events.{format} */
|
||||
static struct stasis_rest_handlers events = {
|
||||
.path_segment = "events",
|
||||
.callbacks = {
|
||||
[AST_HTTP_GET] = stasis_http_event_websocket_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
@@ -80,13 +78,23 @@ static struct stasis_rest_handlers events = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
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", stasis_http_event_websocket_ws_cb);
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&events);
|
||||
res |= stasis_http_add_handler(&events);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
stasis_http_remove_handler(&events);
|
||||
ao2_cleanup(events.ws_server);
|
||||
events.ws_server = NULL;
|
||||
stasis_app_unref();
|
||||
return 0;
|
||||
}
|
||||
|
@@ -149,8 +149,10 @@ static struct stasis_rest_handlers playback = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&playback);
|
||||
res |= stasis_http_add_handler(&playback);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -383,8 +383,10 @@ static struct stasis_rest_handlers recordings = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&recordings);
|
||||
res |= stasis_http_add_handler(&recordings);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -115,8 +115,10 @@ static struct stasis_rest_handlers sounds = {
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&sounds);
|
||||
res |= stasis_http_add_handler(&sounds);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
|
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2012 - 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.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief HTTP binding for the Stasis API
|
||||
*
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<depend type="module">res_stasis</depend>
|
||||
<depend type="module">res_http_websocket</depend>
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/astobj2.h"
|
||||
#include "asterisk/http_websocket.h"
|
||||
#include "asterisk/json.h"
|
||||
#include "asterisk/module.h"
|
||||
#include "asterisk/stasis_app.h"
|
||||
#include "asterisk/strings.h"
|
||||
#include "asterisk/utils.h"
|
||||
|
||||
/*! WebSocket protocol for Stasis */
|
||||
static const char * const ws_protocol = "stasis";
|
||||
|
||||
/*! Message to send when out of memory */
|
||||
static struct ast_json *oom_json;
|
||||
|
||||
/*! Number of buckets for the Stasis application hash table. Remember to keep it
|
||||
* a prime number!
|
||||
*/
|
||||
#define APPS_NUM_BUCKETS 7
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Helper to write a JSON object to a WebSocket.
|
||||
* \param session WebSocket session.
|
||||
* \param message JSON message.
|
||||
* \return 0 on success.
|
||||
* \return -1 on error.
|
||||
*/
|
||||
static int websocket_write_json(struct ast_websocket *session,
|
||||
struct ast_json *message)
|
||||
{
|
||||
RAII_VAR(char *, str, ast_json_dump_string(message), ast_free);
|
||||
|
||||
if (str == NULL) {
|
||||
ast_log(LOG_ERROR, "Failed to encode JSON object\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ast_websocket_write(session, AST_WEBSOCKET_OPCODE_TEXT, str,
|
||||
strlen(str));
|
||||
}
|
||||
|
||||
struct stasis_ws_session_info {
|
||||
struct ast_websocket *ws_session;
|
||||
struct ao2_container *websocket_apps;
|
||||
};
|
||||
|
||||
static void session_dtor(void *obj)
|
||||
{
|
||||
#ifdef AST_DEVMODE /* Avoid unused variable warning */
|
||||
struct stasis_ws_session_info *session = obj;
|
||||
#endif
|
||||
|
||||
/* session_shutdown should have been called before */
|
||||
ast_assert(session->ws_session == NULL);
|
||||
ast_assert(session->websocket_apps == NULL);
|
||||
}
|
||||
|
||||
static struct stasis_ws_session_info *session_create(
|
||||
struct ast_websocket *ws_session)
|
||||
{
|
||||
RAII_VAR(struct stasis_ws_session_info *, session, NULL, ao2_cleanup);
|
||||
|
||||
session = ao2_alloc(sizeof(*session), session_dtor);
|
||||
|
||||
session->ws_session = ws_session;
|
||||
session->websocket_apps =
|
||||
ast_str_container_alloc(APPS_NUM_BUCKETS);
|
||||
|
||||
if (!session->websocket_apps) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ao2_ref(session, +1);
|
||||
return session;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Explicitly shutdown a session.
|
||||
*
|
||||
* An explicit shutdown is necessary, since stasis-app has a reference to this
|
||||
* session. We also need to be sure to null out the \c ws_session field, since
|
||||
* the websocket is about to go away.
|
||||
*
|
||||
* \param session Session info struct.
|
||||
*/
|
||||
static void session_shutdown(struct stasis_ws_session_info *session)
|
||||
{
|
||||
struct ao2_iterator i;
|
||||
char *app;
|
||||
SCOPED_AO2LOCK(lock, session);
|
||||
|
||||
i = ao2_iterator_init(session->websocket_apps, 0);
|
||||
while ((app = ao2_iterator_next(&i))) {
|
||||
stasis_app_unregister(app);
|
||||
ao2_cleanup(app);
|
||||
}
|
||||
ao2_iterator_destroy(&i);
|
||||
ao2_cleanup(session->websocket_apps);
|
||||
|
||||
session->websocket_apps = NULL;
|
||||
session->ws_session = NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Callback handler for Stasis application messages.
|
||||
*/
|
||||
static void app_handler(void *data, const char *app_name,
|
||||
struct ast_json *message)
|
||||
{
|
||||
struct stasis_ws_session_info *session = data;
|
||||
int res;
|
||||
|
||||
res = ast_json_object_set(message, "application",
|
||||
ast_json_string_create(app_name));
|
||||
if(res != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ao2_lock(session);
|
||||
if (session->ws_session) {
|
||||
websocket_write_json(session->ws_session, message);
|
||||
}
|
||||
ao2_unlock(session);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Register for all of the apps given.
|
||||
* \param session Session info struct.
|
||||
* \param app_list Comma seperated list of app names to register.
|
||||
*/
|
||||
static int session_register_apps(struct stasis_ws_session_info *session,
|
||||
const char *app_list)
|
||||
{
|
||||
RAII_VAR(char *, to_free, NULL, ast_free);
|
||||
char *apps, *app_name;
|
||||
SCOPED_AO2LOCK(lock, session);
|
||||
|
||||
ast_assert(session->ws_session != NULL);
|
||||
ast_assert(session->websocket_apps != NULL);
|
||||
|
||||
to_free = apps = ast_strdup(app_list);
|
||||
if (!apps) {
|
||||
websocket_write_json(session->ws_session, oom_json);
|
||||
return -1;
|
||||
}
|
||||
while ((app_name = strsep(&apps, ","))) {
|
||||
if (ast_str_container_add(session->websocket_apps, app_name)) {
|
||||
websocket_write_json(session->ws_session, oom_json);
|
||||
return -1;
|
||||
}
|
||||
|
||||
stasis_app_register(app_name, app_handler, session);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void websocket_callback(struct ast_websocket *ws_session,
|
||||
struct ast_variable *parameters,
|
||||
struct ast_variable *headers)
|
||||
{
|
||||
RAII_VAR(struct stasis_ws_session_info *, stasis_session, NULL, ao2_cleanup);
|
||||
struct ast_variable *param = NULL;
|
||||
int res;
|
||||
|
||||
ast_debug(3, "Stasis web socket connection\n");
|
||||
|
||||
if (ast_websocket_set_nonblock(ws_session) != 0) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Stasis web socket failed to set nonblock; closing\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
stasis_session = session_create(ws_session);
|
||||
|
||||
if (!stasis_session) {
|
||||
websocket_write_json(ws_session, oom_json);
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (param = parameters; param; param = param->next) {
|
||||
if (strcmp(param->name, "app") == 0) {
|
||||
int ret = session_register_apps(
|
||||
stasis_session, param->value);
|
||||
if (ret != 0) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ao2_container_count(stasis_session->websocket_apps) == 0) {
|
||||
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||
|
||||
msg = ast_json_pack("{s: s, s: [s]}",
|
||||
"error", "MissingParams",
|
||||
"params", "app");
|
||||
if (msg) {
|
||||
websocket_write_json(ws_session, msg);
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
|
||||
while ((res = ast_wait_for_input(ast_websocket_fd(ws_session), -1)) > 0) {
|
||||
char *payload;
|
||||
uint64_t payload_len;
|
||||
enum ast_websocket_opcode opcode;
|
||||
int fragmented;
|
||||
int read = ast_websocket_read(ws_session, &payload, &payload_len,
|
||||
&opcode, &fragmented);
|
||||
|
||||
if (read) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Stasis WebSocket read error; closing\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
session_shutdown(stasis_session);
|
||||
ast_websocket_unref(ws_session);
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int r = 0;
|
||||
|
||||
stasis_app_ref();
|
||||
oom_json = ast_json_pack("{s: s}",
|
||||
"error", "OutOfMemory");
|
||||
if (!oom_json) {
|
||||
/* ironic */
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
r |= ast_websocket_add_protocol(ws_protocol, websocket_callback);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
int r = 0;
|
||||
|
||||
stasis_app_unref();
|
||||
ast_json_unref(oom_json);
|
||||
oom_json = NULL;
|
||||
r |= ast_websocket_remove_protocol(ws_protocol, websocket_callback);
|
||||
return r;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Stasis HTTP bindings",
|
||||
.load = load_module,
|
||||
.unload = unload_module,
|
||||
.nonoptreq = "res_stasis,res_http_websocket",
|
||||
.load_pri = AST_MODPRI_APP_DEPEND,
|
||||
);
|
124
res/stasis_http/ari_websockets.c
Normal file
124
res/stasis_http/ari_websockets.c
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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/astobj2.h"
|
||||
#include "asterisk/stasis_http.h"
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief WebSocket support for RESTful API's.
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
*/
|
||||
|
||||
struct ari_websocket_session {
|
||||
struct ast_websocket *ws_session;
|
||||
};
|
||||
|
||||
static void websocket_session_dtor(void *obj)
|
||||
{
|
||||
struct ari_websocket_session *session = obj;
|
||||
|
||||
ast_websocket_unref(session->ws_session);
|
||||
session->ws_session = NULL;
|
||||
}
|
||||
|
||||
struct ari_websocket_session *ari_websocket_session_create(
|
||||
struct ast_websocket *ws_session)
|
||||
{
|
||||
RAII_VAR(struct ari_websocket_session *, session, NULL, ao2_cleanup);
|
||||
|
||||
if (ws_session == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ast_websocket_set_nonblock(ws_session) != 0) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Stasis web socket failed to set nonblock; closing\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
session = ao2_alloc(sizeof(*session), websocket_session_dtor);
|
||||
if (!session) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ao2_ref(ws_session, +1);
|
||||
session->ws_session = ws_session;
|
||||
|
||||
ao2_ref(session, +1);
|
||||
return session;
|
||||
}
|
||||
|
||||
struct ast_json *ari_websocket_session_read(
|
||||
struct 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) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
res = ast_websocket_read(session->ws_session, &payload,
|
||||
&payload_len, &opcode, &fragmented);
|
||||
|
||||
if (res != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
case AST_WEBSOCKET_OPCODE_CLOSE:
|
||||
return NULL;
|
||||
case AST_WEBSOCKET_OPCODE_TEXT:
|
||||
message = ast_json_load_buf(payload, payload_len, NULL);
|
||||
break;
|
||||
default:
|
||||
/* Ignore all other message types */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ast_json_ref(message);
|
||||
}
|
||||
|
||||
int ari_websocket_session_write(struct ari_websocket_session *session,
|
||||
struct ast_json *message)
|
||||
{
|
||||
RAII_VAR(char *, str, ast_json_dump_string(message), ast_free);
|
||||
|
||||
if (str == NULL) {
|
||||
ast_log(LOG_ERROR, "Failed to encode JSON object\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ast_websocket_write(session->ws_session,
|
||||
AST_WEBSOCKET_OPCODE_TEXT, str, strlen(str));
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
/* -*- C -*-
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2012 - 2013, Digium, Inc.
|
||||
@@ -18,23 +18,188 @@
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Implementation for stasis-http stubs.
|
||||
* \brief /api-docs/events.{format} implementation- WebSocket resource
|
||||
*
|
||||
* \author David M. Lee, II <dlee@digium.com>
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<support_level>core</support_level>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include "asterisk/astobj2.h"
|
||||
#include "asterisk/stasis_app.h"
|
||||
#include "resource_events.h"
|
||||
|
||||
void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response)
|
||||
/*! Number of buckets for the Stasis application hash table. Remember to keep it
|
||||
* a prime number!
|
||||
*/
|
||||
#define APPS_NUM_BUCKETS 7
|
||||
|
||||
/*! \brief A connection to the event WebSocket */
|
||||
struct event_session {
|
||||
struct ari_websocket_session *ws_session;
|
||||
struct ao2_container *websocket_apps;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Explicitly shutdown a session.
|
||||
*
|
||||
* An explicit shutdown is necessary, since stasis-app has a reference to this
|
||||
* session. We also need to be sure to null out the \c ws_session field, since
|
||||
* the websocket is about to go away.
|
||||
*
|
||||
* \param session Session info struct.
|
||||
*/
|
||||
static void session_shutdown(struct event_session *session)
|
||||
{
|
||||
/* TODO: This should promote this socket to a websocket connection */
|
||||
ast_log(LOG_ERROR, "TODO: stasis_http_event_websocket\n");
|
||||
struct ao2_iterator i;
|
||||
char *app;
|
||||
SCOPED_AO2LOCK(lock, session);
|
||||
|
||||
i = ao2_iterator_init(session->websocket_apps, 0);
|
||||
while ((app = ao2_iterator_next(&i))) {
|
||||
stasis_app_unregister(app);
|
||||
ao2_cleanup(app);
|
||||
}
|
||||
ao2_iterator_destroy(&i);
|
||||
ao2_cleanup(session->websocket_apps);
|
||||
|
||||
session->websocket_apps = NULL;
|
||||
session->ws_session = NULL;
|
||||
}
|
||||
|
||||
static void session_dtor(void *obj)
|
||||
{
|
||||
#ifdef AST_DEVMODE /* Avoid unused variable warning */
|
||||
struct event_session *session = obj;
|
||||
#endif
|
||||
|
||||
/* session_shutdown should have been called before */
|
||||
ast_assert(session->ws_session == NULL);
|
||||
ast_assert(session->websocket_apps == NULL);
|
||||
}
|
||||
|
||||
static void session_cleanup(struct event_session *session)
|
||||
{
|
||||
session_shutdown(session);
|
||||
ao2_cleanup(session);
|
||||
}
|
||||
|
||||
static struct event_session *session_create(
|
||||
struct ari_websocket_session *ws_session)
|
||||
{
|
||||
RAII_VAR(struct event_session *, session, NULL, ao2_cleanup);
|
||||
|
||||
session = ao2_alloc(sizeof(*session), session_dtor);
|
||||
|
||||
session->ws_session = ws_session;
|
||||
session->websocket_apps =
|
||||
ast_str_container_alloc(APPS_NUM_BUCKETS);
|
||||
|
||||
if (!session->websocket_apps) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ao2_ref(session, +1);
|
||||
return session;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Callback handler for Stasis application messages.
|
||||
*/
|
||||
static void app_handler(void *data, const char *app_name,
|
||||
struct ast_json *message)
|
||||
{
|
||||
struct event_session *session = data;
|
||||
int res;
|
||||
|
||||
res = ast_json_object_set(message, "application",
|
||||
ast_json_string_create(app_name));
|
||||
if(res != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ao2_lock(session);
|
||||
if (session->ws_session) {
|
||||
ari_websocket_session_write(session->ws_session, message);
|
||||
}
|
||||
ao2_unlock(session);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Register for all of the apps given.
|
||||
* \param session Session info struct.
|
||||
* \param app_list Comma seperated list of app names to register.
|
||||
*/
|
||||
static int session_register_apps(struct event_session *session,
|
||||
const char *app_list)
|
||||
{
|
||||
RAII_VAR(char *, to_free, NULL, ast_free);
|
||||
char *apps, *app_name;
|
||||
SCOPED_AO2LOCK(lock, session);
|
||||
|
||||
ast_assert(session->ws_session != NULL);
|
||||
ast_assert(session->websocket_apps != NULL);
|
||||
|
||||
if (!app_list) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
to_free = apps = ast_strdup(app_list);
|
||||
if (!apps) {
|
||||
ari_websocket_session_write(session->ws_session, ari_oom_json());
|
||||
return -1;
|
||||
}
|
||||
while ((app_name = strsep(&apps, ","))) {
|
||||
if (ast_str_container_add(session->websocket_apps, app_name)) {
|
||||
ari_websocket_session_write(session->ws_session, ari_oom_json());
|
||||
return -1;
|
||||
}
|
||||
|
||||
stasis_app_register(app_name, app_handler, session);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ari_websocket_event_websocket(struct ari_websocket_session *ws_session,
|
||||
struct ast_variable *headers,
|
||||
struct ast_event_websocket_args *args)
|
||||
{
|
||||
RAII_VAR(struct event_session *, session, NULL, session_cleanup);
|
||||
struct ast_json *msg;
|
||||
int res;
|
||||
|
||||
ast_debug(3, "/events WebSocket connection\n");
|
||||
|
||||
session = session_create(ws_session);
|
||||
if (!session) {
|
||||
ari_websocket_session_write(ws_session, ari_oom_json());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args->app) {
|
||||
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||
|
||||
msg = ast_json_pack("{s: s, s: [s]}",
|
||||
"error", "MissingParams",
|
||||
"params", "app");
|
||||
if (!msg) {
|
||||
msg = ast_json_ref(ari_oom_json());
|
||||
}
|
||||
|
||||
ari_websocket_session_write(session->ws_session, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
res = session_register_apps(session, args->app);
|
||||
if (res != 0) {
|
||||
ari_websocket_session_write(ws_session, ari_oom_json());
|
||||
return;
|
||||
}
|
||||
|
||||
/* We don't process any input, but we'll consume it waiting for EOF */
|
||||
while ((msg = ari_websocket_session_read(ws_session))) {
|
||||
ast_json_unref(msg);
|
||||
}
|
||||
}
|
||||
|
@@ -43,16 +43,14 @@
|
||||
struct ast_event_websocket_args {
|
||||
/*! \brief Comma seperated list of applications to subscribe to. */
|
||||
const char *app;
|
||||
/*! \brief RFC6455 header for upgrading a connection to a websocket. */
|
||||
const char *upgrade;
|
||||
};
|
||||
/*!
|
||||
* \brief WebSocket connection for events.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
* \param session ARI WebSocket.
|
||||
* \param headers HTTP headers.
|
||||
* \param args Swagger parameters.
|
||||
*/
|
||||
void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response);
|
||||
void ari_websocket_event_websocket(struct ari_websocket_session *session, struct ast_variable *headers, struct ast_event_websocket_args *args);
|
||||
|
||||
#endif /* _ASTERISK_RESOURCE_EVENTS_H */
|
||||
|
@@ -144,6 +144,7 @@ class AsteriskProcessor(SwaggerPostProcessor):
|
||||
segment = resource_api.root_path.get_child(api.path.split('/'))
|
||||
for operation in api.operations:
|
||||
segment.operations.append(operation)
|
||||
api.full_name = segment.full_name
|
||||
resource_api.api_declaration.has_events = False
|
||||
for model in resource_api.api_declaration.models:
|
||||
if model.id == "Event":
|
||||
|
45
rest-api-templates/param_parsing.mustache
Normal file
45
rest-api-templates/param_parsing.mustache
Normal file
@@ -0,0 +1,45 @@
|
||||
{{!
|
||||
* 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.
|
||||
}}
|
||||
{{!
|
||||
* Snippet for decoding parameters into an _args struct.
|
||||
}}
|
||||
struct ast_{{c_nickname}}_args args = {};
|
||||
{{#has_parameters}}
|
||||
struct ast_variable *i;
|
||||
|
||||
{{#has_query_parameters}}
|
||||
for (i = get_params; i; i = i->next) {
|
||||
{{#query_parameters}}
|
||||
if (strcmp(i->name, "{{name}}") == 0) {
|
||||
args.{{c_name}} = {{c_convert}}(i->value);
|
||||
} else
|
||||
{{/query_parameters}}
|
||||
{}
|
||||
}
|
||||
{{/has_query_parameters}}
|
||||
{{#has_path_parameters}}
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
{{#path_parameters}}
|
||||
if (strcmp(i->name, "{{name}}") == 0) {
|
||||
args.{{c_name}} = {{c_convert}}(i->value);
|
||||
} else
|
||||
{{/path_parameters}}
|
||||
{}
|
||||
}
|
||||
{{/has_path_parameters}}
|
||||
{{/has_parameters}}
|
@@ -52,6 +52,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
{{#is_req}}
|
||||
/*!
|
||||
* \brief Parameter parsing callback for {{path}}.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
@@ -63,33 +64,31 @@ static void stasis_http_{{c_nickname}}_cb(
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct stasis_http_response *response)
|
||||
{
|
||||
struct ast_{{c_nickname}}_args args = {};
|
||||
{{#has_parameters}}
|
||||
struct ast_variable *i;
|
||||
|
||||
{{#has_query_parameters}}
|
||||
for (i = get_params; i; i = i->next) {
|
||||
{{#query_parameters}}
|
||||
if (strcmp(i->name, "{{name}}") == 0) {
|
||||
args.{{c_name}} = {{c_convert}}(i->value);
|
||||
} else
|
||||
{{/query_parameters}}
|
||||
{}
|
||||
}
|
||||
{{/has_query_parameters}}
|
||||
{{#has_path_parameters}}
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
{{#path_parameters}}
|
||||
if (strcmp(i->name, "{{name}}") == 0) {
|
||||
args.{{c_name}} = {{c_convert}}(i->value);
|
||||
} else
|
||||
{{/path_parameters}}
|
||||
{}
|
||||
}
|
||||
{{/has_path_parameters}}
|
||||
{{/has_parameters}}
|
||||
{{> param_parsing}}
|
||||
stasis_http_{{c_nickname}}(headers, &args, response);
|
||||
}
|
||||
{{/is_req}}
|
||||
{{#is_websocket}}
|
||||
static void stasis_http_{{c_nickname}}_ws_cb(struct ast_websocket *ws_session,
|
||||
struct ast_variable *get_params, struct ast_variable *headers)
|
||||
{
|
||||
RAII_VAR(struct ast_websocket *, s, ws_session, ast_websocket_unref);
|
||||
RAII_VAR(struct ari_websocket_session *, session, NULL, ao2_cleanup);
|
||||
{{#has_path_parameters}}
|
||||
/* TODO: It's not immediately obvious how to pass path params through
|
||||
* the websocket code to this callback. Not needed right now, so we'll
|
||||
* just punt. */
|
||||
struct ast_variable *path_vars = NULL;
|
||||
{{/has_path_parameters}}
|
||||
{{> param_parsing}}
|
||||
session = ari_websocket_session_create(ws_session);
|
||||
if (!session) {
|
||||
ast_log(LOG_ERROR, "Failed to create ARI session\n");
|
||||
return;
|
||||
}
|
||||
ari_websocket_{{c_nickname}}(session, headers, &args);
|
||||
}
|
||||
{{/is_websocket}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
|
||||
@@ -100,13 +99,35 @@ static void stasis_http_{{c_nickname}}_cb(
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
int res = 0;
|
||||
{{#apis}}
|
||||
{{#has_websocket}}
|
||||
{{full_name}}.ws_server = ast_websocket_server_create();
|
||||
if (!{{full_name}}.ws_server) {
|
||||
return AST_MODULE_LOAD_FAILURE;
|
||||
}
|
||||
{{/has_websocket}}
|
||||
{{#operations}}
|
||||
{{#is_websocket}}
|
||||
res |= ast_websocket_server_add_protocol({{full_name}}.ws_server,
|
||||
"{{websocket_protocol}}", stasis_http_{{c_nickname}}_ws_cb);
|
||||
{{/is_websocket}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
stasis_app_ref();
|
||||
return stasis_http_add_handler(&{{root_full_name}});
|
||||
res |= stasis_http_add_handler(&{{root_full_name}});
|
||||
return res;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
stasis_http_remove_handler(&{{root_full_name}});
|
||||
{{#apis}}
|
||||
{{#has_websocket}}
|
||||
ao2_cleanup({{full_name}}.ws_server);
|
||||
{{full_name}}.ws_server = NULL;
|
||||
{{/has_websocket}}
|
||||
{{/apis}}
|
||||
stasis_app_unref();
|
||||
return 0;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{{! -*- C -*-
|
||||
{{!
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2013, Digium, Inc.
|
||||
@@ -30,7 +30,9 @@ static struct stasis_rest_handlers {{full_name}} = {
|
||||
{{/is_wildcard}}
|
||||
.callbacks = {
|
||||
{{#operations}}
|
||||
{{#is_req}}
|
||||
[{{c_http_method}}] = stasis_http_{{c_nickname}}_cb,
|
||||
{{/is_req}}
|
||||
{{/operations}}
|
||||
},
|
||||
.num_children = {{num_children}},
|
||||
|
@@ -32,10 +32,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
{{#apis}}
|
||||
{{#operations}}
|
||||
void stasis_http_{{c_nickname}}(struct ast_variable *headers, struct ast_{{c_nickname}}_args *args, struct stasis_http_response *response)
|
||||
{{#is_req}}
|
||||
void stasis_http_{{c_nickname}}(struct ast_variable *headers,
|
||||
struct ast_{{c_nickname}}_args *args,
|
||||
struct stasis_http_response *response)
|
||||
{
|
||||
ast_log(LOG_ERROR, "TODO: stasis_http_{{c_nickname}}\n");
|
||||
}
|
||||
{{/is_req}}
|
||||
{{#is_websocket}}
|
||||
void ari_websocket_{{c_nickname}}(struct ari_websocket_session *session,
|
||||
struct ast_variable *headers,
|
||||
struct ast_{{c_nickname}}_args *args)
|
||||
{
|
||||
ast_log(LOG_ERROR, "TODO: ari_websocket_{{c_nickname}}\n");
|
||||
}
|
||||
{{/is_websocket}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
{{/api_declaration}}
|
||||
|
@@ -49,6 +49,7 @@ struct ast_{{c_nickname}}_args {
|
||||
{{c_data_type}}{{c_space}}{{c_name}};
|
||||
{{/parameters}}
|
||||
};
|
||||
{{#is_req}}
|
||||
/*!
|
||||
* \brief {{summary}}
|
||||
{{#notes}}
|
||||
@@ -61,6 +62,21 @@ struct ast_{{c_nickname}}_args {
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void stasis_http_{{c_nickname}}(struct ast_variable *headers, struct ast_{{c_nickname}}_args *args, struct stasis_http_response *response);
|
||||
{{/is_req}}
|
||||
{{#is_websocket}}
|
||||
/*!
|
||||
* \brief {{summary}}
|
||||
{{#notes}}
|
||||
*
|
||||
* {{{notes}}}
|
||||
{{/notes}}
|
||||
*
|
||||
* \param session ARI WebSocket.
|
||||
* \param headers HTTP headers.
|
||||
* \param args Swagger parameters.
|
||||
*/
|
||||
void ari_websocket_{{c_nickname}}(struct ari_websocket_session *session, struct ast_variable *headers, struct ast_{{c_nickname}}_args *args);
|
||||
{{/is_websocket}}
|
||||
{{/operations}}
|
||||
{{/apis}}
|
||||
|
||||
|
@@ -218,6 +218,16 @@ class Operation(Stringify):
|
||||
self.http_method = op_json.get('httpMethod')
|
||||
self.nickname = op_json.get('nickname')
|
||||
self.response_class = op_json.get('responseClass')
|
||||
# Specifying WebSocket URL's is our own extension
|
||||
self.is_websocket = op_json.get('upgrade') == 'websocket'
|
||||
self.is_req = not self.is_websocket
|
||||
|
||||
if self.is_websocket:
|
||||
self.websocket_protocol = op_json.get('websocketProtocol')
|
||||
if self.http_method != 'GET':
|
||||
raise ValueError(
|
||||
"upgrade: websocket is only valid on GET operations")
|
||||
|
||||
params_json = op_json.get('parameters') or []
|
||||
self.parameters = [
|
||||
Parameter().load(j, processor, context) for j in params_json]
|
||||
@@ -262,6 +272,8 @@ class Api(Stringify):
|
||||
op_json = api_json.get('operations')
|
||||
self.operations = [
|
||||
Operation().load(j, processor, context) for j in op_json]
|
||||
self.has_websocket = \
|
||||
filter(lambda op: op.is_websocket, self.operations) != []
|
||||
return self
|
||||
|
||||
|
||||
@@ -353,7 +365,8 @@ class ApiDeclaration(Stringify):
|
||||
.replace(".json", ".{format}")
|
||||
|
||||
if self.resource_path != expected_resource_path:
|
||||
print "%s != %s" % (self.resource_path, expected_resource_path)
|
||||
print >> sys.stderr, \
|
||||
"%s != %s" % (self.resource_path, expected_resource_path)
|
||||
raise SwaggerError("resourcePath has incorrect value", context)
|
||||
|
||||
return self
|
||||
|
@@ -13,6 +13,8 @@
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "GET",
|
||||
"upgrade": "websocket",
|
||||
"websocketProtocol": "ari",
|
||||
"summary": "WebSocket connection for events.",
|
||||
"nickname": "eventWebsocket",
|
||||
"responseClass": "Event",
|
||||
@@ -24,19 +26,6 @@
|
||||
"required": true,
|
||||
"allowMultiple": true,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "Upgrade",
|
||||
"description": "RFC6455 header for upgrading a connection to a websocket.",
|
||||
"paramType": "header",
|
||||
"required": true,
|
||||
"dataType": "string",
|
||||
"allowableValues": {
|
||||
"valueType": "LIST",
|
||||
"values": [
|
||||
"websocket"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -223,7 +223,7 @@ AST_TEST_DEFINE(get_docs)
|
||||
basePathJson = ast_json_object_get(response->message, "basePath");
|
||||
ast_test_validate(test, NULL != basePathJson);
|
||||
basePath = ast_json_string_get(basePathJson);
|
||||
ast_test_validate(test, 0 == strcmp("http://stasis.asterisk.org/stasis", basePath));
|
||||
ast_test_validate(test, 0 == strcmp("http://stasis.asterisk.org/ari", basePath));
|
||||
|
||||
return AST_TEST_PASS;
|
||||
}
|
||||
@@ -343,7 +343,7 @@ AST_TEST_DEFINE(invoke_get)
|
||||
"head2", "head-two",
|
||||
"path_vars");
|
||||
|
||||
stasis_http_invoke("foo", AST_HTTP_GET, get_params, headers, response);
|
||||
stasis_http_invoke(NULL, "foo", AST_HTTP_GET, get_params, headers, response);
|
||||
|
||||
ast_test_validate(test, 1 == invocation_count);
|
||||
ast_test_validate(test, 200 == response->response_code);
|
||||
@@ -380,7 +380,7 @@ AST_TEST_DEFINE(invoke_wildcard)
|
||||
"path_vars",
|
||||
"bam", "foshizzle");
|
||||
|
||||
stasis_http_invoke("foo/foshizzle", AST_HTTP_GET, get_params, headers, response);
|
||||
stasis_http_invoke(NULL, "foo/foshizzle", AST_HTTP_GET, get_params, headers, response);
|
||||
|
||||
ast_test_validate(test, 1 == invocation_count);
|
||||
ast_test_validate(test, 200 == response->response_code);
|
||||
@@ -417,7 +417,7 @@ AST_TEST_DEFINE(invoke_delete)
|
||||
"path_vars",
|
||||
"bam", "foshizzle");
|
||||
|
||||
stasis_http_invoke("foo/foshizzle/bang", AST_HTTP_DELETE, get_params, headers, response);
|
||||
stasis_http_invoke(NULL, "foo/foshizzle/bang", AST_HTTP_DELETE, get_params, headers, response);
|
||||
|
||||
ast_test_validate(test, 1 == invocation_count);
|
||||
ast_test_validate(test, 204 == response->response_code);
|
||||
@@ -467,7 +467,7 @@ AST_TEST_DEFINE(invoke_post)
|
||||
"head2", "head-two",
|
||||
"path_vars");
|
||||
|
||||
stasis_http_invoke("foo/bar", AST_HTTP_POST, get_params, headers, response);
|
||||
stasis_http_invoke(NULL, "foo/bar", AST_HTTP_POST, get_params, headers, response);
|
||||
|
||||
ast_test_validate(test, 1 == invocation_count);
|
||||
ast_test_validate(test, 200 == response->response_code);
|
||||
@@ -496,7 +496,7 @@ AST_TEST_DEFINE(invoke_bad_post)
|
||||
|
||||
fixture = setup_invocation_test();
|
||||
response = response_alloc();
|
||||
stasis_http_invoke("foo", AST_HTTP_POST, get_params, headers, response);
|
||||
stasis_http_invoke(NULL, "foo", AST_HTTP_POST, get_params, headers, response);
|
||||
|
||||
ast_test_validate(test, 0 == invocation_count);
|
||||
ast_test_validate(test, 405 == response->response_code);
|
||||
@@ -524,7 +524,7 @@ AST_TEST_DEFINE(invoke_not_found)
|
||||
|
||||
fixture = setup_invocation_test();
|
||||
response = response_alloc();
|
||||
stasis_http_invoke("foo/fizzle/i-am-not-a-resource", AST_HTTP_GET, get_params, headers, response);
|
||||
stasis_http_invoke(NULL, "foo/fizzle/i-am-not-a-resource", AST_HTTP_GET, get_params, headers, response);
|
||||
|
||||
ast_test_validate(test, 0 == invocation_count);
|
||||
ast_test_validate(test, 404 == response->response_code);
|
||||
|
@@ -421,6 +421,42 @@ AST_TEST_DEFINE(agi_loaded_test)
|
||||
return res;
|
||||
}
|
||||
|
||||
AST_TEST_DEFINE(crypt_test)
|
||||
{
|
||||
RAII_VAR(char *, password_crypted, NULL, ast_free);
|
||||
RAII_VAR(char *, blank_crypted, NULL, ast_free);
|
||||
const char *password = "Passw0rd";
|
||||
const char *not_a_password = "not-a-password";
|
||||
|
||||
switch (cmd) {
|
||||
case TEST_INIT:
|
||||
info->name = "crypt_test";
|
||||
info->category = "/main/utils/";
|
||||
info->summary = "Test ast_crypt wrappers";
|
||||
info->description = "Verifies that the ast_crypt wrappers work as expected.";
|
||||
return AST_TEST_NOT_RUN;
|
||||
case TEST_EXECUTE:
|
||||
break;
|
||||
}
|
||||
|
||||
password_crypted = ast_crypt_encrypt(password);
|
||||
ast_test_validate(test, NULL != password_crypted);
|
||||
ast_test_validate(test, 0 != strcmp(password, password_crypted));
|
||||
ast_test_validate(test, ast_crypt_validate(password, password_crypted));
|
||||
ast_test_validate(test,
|
||||
!ast_crypt_validate(not_a_password, password_crypted));
|
||||
|
||||
blank_crypted = ast_crypt_encrypt("");
|
||||
ast_test_validate(test, NULL != blank_crypted);
|
||||
ast_test_validate(test, 0 != strcmp(blank_crypted, ""));
|
||||
ast_test_validate(test, ast_crypt_validate("", blank_crypted));
|
||||
ast_test_validate(test,
|
||||
!ast_crypt_validate(not_a_password, blank_crypted));
|
||||
|
||||
return AST_TEST_PASS;
|
||||
}
|
||||
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
AST_TEST_UNREGISTER(uri_encode_decode_test);
|
||||
@@ -431,6 +467,7 @@ static int unload_module(void)
|
||||
AST_TEST_UNREGISTER(crypto_loaded_test);
|
||||
AST_TEST_UNREGISTER(adsi_loaded_test);
|
||||
AST_TEST_UNREGISTER(agi_loaded_test);
|
||||
AST_TEST_UNREGISTER(crypt_test);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -444,6 +481,7 @@ static int load_module(void)
|
||||
AST_TEST_REGISTER(crypto_loaded_test);
|
||||
AST_TEST_REGISTER(adsi_loaded_test);
|
||||
AST_TEST_REGISTER(agi_loaded_test);
|
||||
AST_TEST_REGISTER(crypt_test);
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user