diff options
63 files changed, 8603 insertions, 57 deletions
@@ -453,6 +453,9 @@ datafiles: _all doc/core-en_US.xml $(INSTALL) -m 644 $$x "$(DESTDIR)$(ASTDATADIR)/images" ; \ done $(MAKE) -C sounds install + find rest-api -name "*.json" | while read x; do \ + $(INSTALL) -m 644 $$x "$(DESTDIR)$(ASTDATADIR)/rest-api" ; \ + done ifneq ($(GREP),) XML_core_en_US = $(foreach dir,$(MOD_SUBDIRS),$(shell $(GREP) -l "language=\"en_US\"" $(dir)/*.c $(dir)/*.cc 2>/dev/null)) @@ -537,8 +540,8 @@ INSTALLDIRS="$(ASTLIBDIR)" "$(ASTMODDIR)" "$(ASTSBINDIR)" "$(ASTETCDIR)" "$(ASTV "$(ASTLOGDIR)/cel-custom" "$(ASTDATADIR)" "$(ASTDATADIR)/documentation" \ "$(ASTDATADIR)/documentation/thirdparty" "$(ASTDATADIR)/firmware" \ "$(ASTDATADIR)/firmware/iax" "$(ASTDATADIR)/images" "$(ASTDATADIR)/keys" \ - "$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/static-http" "$(ASTDATADIR)/sounds" \ - "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)" + "$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/rest-api" "$(ASTDATADIR)/static-http" \ + "$(ASTDATADIR)/sounds" "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)" installdirs: @for i in $(INSTALLDIRS); do \ @@ -958,6 +961,19 @@ menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(di @cat sounds/sounds.xml >> $@ @echo "</menu>" >> $@ +# We don't want to require Python or Pystache for every build, so this is its +# own target. +stasis-stubs: +ifeq ($(PYTHON),:) + @echo "--------------------------------------------------------------------------" + @echo "--- Please install python to build Stasis HTTP stubs ---" + @echo "--------------------------------------------------------------------------" + @false +else + $(PYTHON) rest-api-templates/make_stasis_http_stubs.py \ + rest-api/resources.json res/ +endif + .PHONY: menuselect .PHONY: main .PHONY: sounds @@ -977,6 +993,7 @@ menuselect-tree: $(foreach dir,$(filter-out main,$(MOD_SUBDIRS)),$(wildcard $(di .PHONY: installdirs .PHONY: validate-docs .PHONY: _clean +.PHONY: stasis-stubs .PHONY: $(SUBDIRS_INSTALL) .PHONY: $(SUBDIRS_DIST_CLEAN) .PHONY: $(SUBDIRS_CLEAN) diff --git a/configs/stasis_http.conf.sample b/configs/stasis_http.conf.sample new file mode 100644 index 000000000..1527a32be --- /dev/null +++ b/configs/stasis_http.conf.sample @@ -0,0 +1,25 @@ +[general] +enabled = yes ; When set to no, stasis-http support is disabled +;pretty = no ; When set to yes, responses from stasis-http are +; ; formatted to be human readable +;allowed_origins = ; Comma separated list of allowed origins, for +; ; Cross-Origin Resource Sharing. May be set to * to allow +; ; all origins. + +;[user-username] +;read_only = no ; When set to yes, user is only authorized for +; ; read-only requests +; +; If a password is specified, user must authenticate using HTTP Basic +; authentication. If no password is specified, then the user may authenticate +; simply by adding ?api_key=username to their requests. +; +;password = ; Crypted or plaintext password (see crypt_password) +; +; crypt_password may be set to crypt (the default) or plain. When set to crypt, +; crypt(3) is used to encrypt the password. A crypted password can be generated +; using mkpasswd -m sha-512. +; +; When set to plain, the password is in plaintext +; +;crypt_password = plain diff --git a/include/asterisk/http.h b/include/asterisk/http.h index 3400240a1..db424d3d3 100644 --- a/include/asterisk/http.h +++ b/include/asterisk/http.h @@ -58,7 +58,10 @@ enum ast_http_method { AST_HTTP_GET = 0, AST_HTTP_POST, AST_HTTP_HEAD, - AST_HTTP_PUT, /*!< Not supported in Asterisk */ + AST_HTTP_PUT, + AST_HTTP_DELETE, + AST_HTTP_OPTIONS, + AST_HTTP_MAX_METHOD, /*!< Last entry in ast_http_method enum */ }; struct ast_http_uri; diff --git a/include/asterisk/json.h b/include/asterisk/json.h index 62e21293b..d06416f58 100644 --- a/include/asterisk/json.h +++ b/include/asterisk/json.h @@ -586,16 +586,33 @@ int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter /*!@{*/ /*! + * \brief Encoding format type. + * \since 12.0.0 + */ +enum ast_json_encoding_format +{ + /*! Compact format, low human readability */ + AST_JSON_COMPACT, + /*! Formatted for human readability */ + AST_JSON_PRETTY, +}; + +#define ast_json_dump_string(root) ast_json_dump_string_format(root, AST_JSON_COMPACT) + +/*! * \brief Encode a JSON value to a string. * \since 12.0.0 * * Returned string must be freed by calling ast_free(). * - * \param JSON value. + * \param root JSON value. + * \param format encoding format type. * \return String encoding of \a root. * \return \c NULL on error. */ -char *ast_json_dump_string(struct ast_json *root); +char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format); + +#define ast_json_dump_str(root, dst) ast_json_dump_str_format(root, dst, AST_JSON_COMPACT) /*! * \brief Encode a JSON value to an \ref ast_str. @@ -605,10 +622,13 @@ char *ast_json_dump_string(struct ast_json *root); * * \param root JSON value. * \param dst \ref ast_str to store JSON encoding. + * \param format encoding format type. * \return 0 on success. * \return -1 on error. The contents of \a dst are undefined. */ -int ast_json_dump_str(struct ast_json *root, struct ast_str **dst); +int ast_json_dump_str_format(struct ast_json *root, struct ast_str **dst, enum ast_json_encoding_format format); + +#define ast_json_dump_file(root, output) ast_json_dump_file_format(root, output, AST_JSON_COMPACT) /*! * \brief Encode a JSON value to a \c FILE. @@ -616,10 +636,13 @@ int ast_json_dump_str(struct ast_json *root, struct ast_str **dst); * * \param root JSON value. * \param output File to write JSON encoding to. + * \param format encoding format type. * \return 0 on success. * \return -1 on error. The contents of \a output are undefined. */ -int ast_json_dump_file(struct ast_json *root, FILE *output); +int ast_json_dump_file_format(struct ast_json *root, FILE *output, enum ast_json_encoding_format format); + +#define ast_json_dump_new_file(root, path) ast_json_dump_new_file_format(root, path, AST_JSON_COMPACT) /*! * \brief Encode a JSON value to a file at the given location. @@ -627,10 +650,11 @@ int ast_json_dump_file(struct ast_json *root, FILE *output); * * \param root JSON value. * \param path Path to file to write JSON encoding to. + * \param format encoding format type. * \return 0 on success. * \return -1 on error. The contents of \a output are undefined. */ -int ast_json_dump_new_file(struct ast_json *root, const char *path); +int ast_json_dump_new_file_format(struct ast_json *root, const char *path, enum ast_json_encoding_format format); #define AST_JSON_ERROR_TEXT_LENGTH 160 #define AST_JSON_ERROR_SOURCE_LENGTH 80 diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index caec19bbc..a789e4012 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -66,7 +66,7 @@ struct ast_channel_snapshot; * \param argv Arguments for the application. */ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, - char *argv[]); + char *argv[]); /*! @} */ @@ -126,22 +126,50 @@ int stasis_app_send(const char *app_name, struct ast_json *message); struct stasis_app_control; /*! - * \brief Returns the handler for the given channel + * \brief Returns the handler for the given channel. * \param chan Channel to handle. - * \return NULL channel not in Stasis application - * \return Pointer to stasis handler. + * \return NULL channel not in Stasis application. + * \return Pointer to \c res_stasis handler. */ struct stasis_app_control *stasis_app_control_find_by_channel( const struct ast_channel *chan); /*! - * \brief Exit \c app_stasis and continue execution in the dialplan. + * \brief Returns the handler for the channel with the given id. + * \param channel_id Uniqueid of the channel. + * \return NULL channel not in Stasis application, or channel does not exist. + * \return Pointer to \c res_stasis handler. + */ +struct stasis_app_control *stasis_app_control_find_by_channel_id( + const char *channel_id); + +/*! + * \brief Exit \c res_stasis and continue execution in the dialplan. * - * If the channel is no longer in \c app_stasis, this function does nothing. + * If the channel is no longer in \c res_stasis, this function does nothing. * - * \param handler Handler for \c app_stasis + * \param control Control for \c res_stasis + */ +void stasis_app_control_continue(struct stasis_app_control *control); + +/*! + * \brief Answer the channel associated with this control. + * \param control Control for \c res_stasis. + * \return 0 for success. + * \return -1 for error. + */ +int stasis_app_control_answer(struct stasis_app_control *control); + +/*! @} */ + +/*! @{ */ + +/*! + * \brief Build a JSON object from a \ref ast_channel_snapshot. + * \return JSON object representing channel snapshot. + * \return \c NULL on error */ -void stasis_app_control_continue(struct stasis_app_control *handler); +struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot); /*! @} */ diff --git a/include/asterisk/stasis_http.h b/include/asterisk/stasis_http.h new file mode 100644 index 000000000..cc0ceeee4 --- /dev/null +++ b/include/asterisk/stasis_http.h @@ -0,0 +1,171 @@ +/* + * 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. + */ + +#ifndef _ASTERISK_STASIS_HTTP_H +#define _ASTERISK_STASIS_HTTP_H + +/*! \file + * + * \brief Stasis RESTful API hooks. + * + * This header file is used mostly as glue code between generated declarations + * and res_stasis_http.c. + * + * \author David M. Lee, II <dlee@digium.com> + */ + +#include "asterisk/http.h" +#include "asterisk/json.h" +#include "asterisk/http_websocket.h" + +struct stasis_http_response; + +/*! + * \brief Callback type for RESTful method handlers. + * \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. + * \param[out] response The RESTful response. + */ +typedef void (*stasis_rest_callback)(struct ast_variable *get_params, + struct ast_variable *path_vars, + struct ast_variable *headers, + struct stasis_http_response *response); + +/*! + * \brief Handler for a single RESTful path segment. + */ +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. + * + * 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]; + /*! Number of children in the children array */ + size_t num_children; + /*! Handlers for sub-paths */ + struct stasis_rest_handlers *children[]; +}; + +/*! + * Response type for RESTful requests + */ +struct stasis_http_response { + /*! Response message */ + struct ast_json *message; + /*! \r\n seperated response headers */ + struct ast_str *headers; + /*! HTTP response code. + * 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? +}; + +/*! + * Add a resource for REST handling. + * \param handler Handler to add. + * \return 0 on success. + * \return non-zero on failure. + */ +int stasis_http_add_handler(struct stasis_rest_handlers *handler); + +/*! + * Remove a resource for REST handling. + * \param handler Handler to add. + * \return 0 on success. + * \return non-zero on failure. + */ +int stasis_http_remove_handler(struct stasis_rest_handlers *handler); + +/*! + * \internal + * \brief Stasis RESTful invocation handler. + * + * Only call from res_stasis_http and test_stasis_http. Only public to allow + * for unit testing. + * + * \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); + +/*! + * \internal + * \brief Service function for API declarations. + * + * Only call from res_stasis_http and test_stasis_http. Only public to allow + * for unit testing. + * + * \param uri Requested URI, relative to the docs path. + * \param headers HTTP headers. + * \param[out] response RESTful HTTP response. + */ +void stasis_http_get_docs(const char *uri, struct ast_variable *headers, struct stasis_http_response *response); + +/*! + * \internal + * \brief Stasis WebSocket connection handler + * \param session WebSocket session. + * \param parameters HTTP \c GET parameters. + * \param headers HTTP headers. + */ +void stasis_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers); + +/*! + * \brief Fill in an error \a stasis_http_response. + * \param response Response to fill in. + * \param response_code HTTP response code. + * \param response_text Text corresponding to the HTTP response code. + * \param message_fmt Error message format string. + */ +void stasis_http_response_error(struct stasis_http_response *response, + int response_code, + const char *response_text, + const char *message_fmt, ...) +__attribute__((format(printf, 4, 5))); + +/*! + * \brief Fill in an \c OK (200) \a stasis_http_response. + * \param response Response to fill in. + * \param message JSON response. This reference is stolen, so just \ref + * ast_json_incref if you need to keep a reference to it. + */ +void stasis_http_response_ok(struct stasis_http_response *response, + struct ast_json *message); + +/*! + * \brief Fill in a <tt>No Content</tt> (204) \a stasis_http_response. + */ +void stasis_http_response_no_content(struct stasis_http_response *response); + +/*! + * \brief Fill in \a response with a 500 message for allocation failures. + * \param response Response to fill in. + */ +void stasis_http_response_alloc_failed(struct stasis_http_response *response); + +#endif /* _ASTERISK_STASIS_HTTP_H */ diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h index d16e9f7bd..967eb82a0 100644 --- a/include/asterisk/strings.h +++ b/include/asterisk/strings.h @@ -82,6 +82,48 @@ static force_inline int attribute_pure ast_strlen_zero(const char *s) */ #define S_COR(a, b, c) ({typeof(&((b)[0])) __x = (b); (a) && !ast_strlen_zero(__x) ? (__x) : (c);}) +/* + \brief Checks whether a string begins with another. + \since 12.0.0 + \param str String to check. + \param prefix Prefix to look for. + \param 1 if \a str begins with \a prefix, 0 otherwise. + */ +static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix) +{ + ast_assert(str != NULL); + ast_assert(prefix != NULL); + while (*str == *prefix && *prefix != '\0') { + ++str; + ++prefix; + } + return *prefix == '\0'; +} + +/* + \brief Checks whether a string ends with another. + \since 12.0.0 + \param str String to check. + \param suffix Suffix to look for. + \param 1 if \a str ends with \a suffix, 0 otherwise. + */ +static int force_inline attribute_pure ast_ends_with(const char *str, const char *suffix) +{ + size_t str_len; + size_t suffix_len; + + ast_assert(str != NULL); + ast_assert(suffix != NULL); + str_len = strlen(str); + suffix_len = strlen(suffix); + + if (suffix_len > str_len) { + return 0; + } + + return strcmp(str + str_len - suffix_len, suffix) == 0; +} + /*! * \brief return Yes or No depending on the argument. * diff --git a/main/http.c b/main/http.c index aff38c3aa..ec9517f2c 100644 --- a/main/http.c +++ b/main/http.c @@ -153,6 +153,8 @@ static const struct ast_cfhttp_methods_text { { AST_HTTP_POST, "POST" }, { AST_HTTP_HEAD, "HEAD" }, { AST_HTTP_PUT, "PUT" }, + { AST_HTTP_DELETE, "DELETE" }, + { AST_HTTP_OPTIONS, "OPTIONS" }, }; const char *ast_get_http_method(enum ast_http_method method) @@ -897,6 +899,10 @@ static void *httpd_helper_thread(void *data) http_method = AST_HTTP_HEAD; } else if (!strcasecmp(method,"PUT")) { http_method = AST_HTTP_PUT; + } else if (!strcasecmp(method,"DELETE")) { + http_method = AST_HTTP_DELETE; + } else if (!strcasecmp(method,"OPTIONS")) { + http_method = AST_HTTP_OPTIONS; } uri = ast_skip_blanks(uri); /* Skip white space */ diff --git a/main/json.c b/main/json.c index 2aa53875b..1ff563871 100644 --- a/main/json.c +++ b/main/json.c @@ -338,20 +338,15 @@ int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter /*! * \brief Default flags for JSON encoding. */ -static size_t dump_flags(void) +static size_t dump_flags(enum ast_json_encoding_format format) { - /* There's a chance this could become a runtime flag */ - int flags = JSON_COMPACT; -#ifdef AST_DEVMODE - /* In dev mode, write readable JSON */ - flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER; -#endif - return flags; + return format == AST_JSON_PRETTY ? + JSON_INDENT(2) | JSON_PRESERVE_ORDER : JSON_COMPACT; } -char *ast_json_dump_string(struct ast_json *root) +char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format) { - return json_dumps((json_t *)root, dump_flags()); + return json_dumps((json_t *)root, dump_flags(format)); } static int write_to_ast_str(const char *buffer, size_t size, void *data) @@ -385,25 +380,25 @@ static int write_to_ast_str(const char *buffer, size_t size, void *data) return 0; } -int ast_json_dump_str(struct ast_json *root, struct ast_str **dst) +int ast_json_dump_str_format(struct ast_json *root, struct ast_str **dst, enum ast_json_encoding_format format) { - return json_dump_callback((json_t *)root, write_to_ast_str, dst, dump_flags()); + return json_dump_callback((json_t *)root, write_to_ast_str, dst, dump_flags(format)); } -int ast_json_dump_file(struct ast_json *root, FILE *output) +int ast_json_dump_file_format(struct ast_json *root, FILE *output, enum ast_json_encoding_format format) { if (!root || !output) { return -1; } - return json_dumpf((json_t *)root, output, dump_flags()); + return json_dumpf((json_t *)root, output, dump_flags(format)); } -int ast_json_dump_new_file(struct ast_json *root, const char *path) +int ast_json_dump_new_file_format(struct ast_json *root, const char *path, enum ast_json_encoding_format format) { if (!root || !path) { return -1; } - return json_dump_file((json_t *)root, path, dump_flags()); + return json_dump_file((json_t *)root, path, dump_flags(format)); } /*! diff --git a/res/Makefile b/res/Makefile index 53aeeaffd..fec20a2e0 100644 --- a/res/Makefile +++ b/res/Makefile @@ -67,4 +67,7 @@ endif ael/pval.o: ael/pval.c clean:: - rm -f snmp/*.o snmp/*.i ael/*.o ael/*.i ais/*.o ais/*.i + rm -f snmp/*.[oi] ael/*.[oi] ais/*.[oi] stasis_http/*.[oi] + +# Dependencies for res_stasis_http_*.so are generated, so they're in this file +include stasis_http.make diff --git a/res/res_stasis.c b/res/res_stasis.c index 3527adaa5..045362546 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -40,6 +40,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_channels.h" #include "asterisk/strings.h" +/*! Time to wait for a frame in the application */ +#define MAX_WAIT_MS 200 + /*! * \brief Number of buckets for the Stasis application hash table. Remember to * keep it a prime number! @@ -147,7 +150,67 @@ static void app_send(struct app *app, struct ast_json *message) app->handler(app->data, app->name, message); } +typedef void* (*stasis_app_command_cb)(struct stasis_app_control *control, + struct ast_channel *chan, + void *data); + +struct stasis_app_command { + ast_mutex_t lock; + ast_cond_t condition; + stasis_app_command_cb callback; + void *data; + void *retval; + int is_done:1; +}; + +static void command_dtor(void *obj) +{ + struct stasis_app_command *command = obj; + ast_mutex_destroy(&command->lock); + ast_cond_destroy(&command->condition); +} + +static struct stasis_app_command *command_create(stasis_app_command_cb callback, + void *data) +{ + RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup); + + command = ao2_alloc(sizeof(*command), command_dtor); + if (!command) { + return NULL; + } + + ast_mutex_init(&command->lock); + ast_cond_init(&command->condition, 0); + command->callback = callback; + command->data = data; + + ao2_ref(command, +1); + return command; +} + +static void command_complete(struct stasis_app_command *command, void *retval) +{ + SCOPED_MUTEX(lock, &command->lock); + + command->is_done = 1; + command->retval = retval; + ast_cond_signal(&command->condition); +} + +static void *wait_for_command(struct stasis_app_command *command) +{ + SCOPED_MUTEX(lock, &command->lock); + while (!command->is_done) { + ast_cond_wait(&command->condition, &command->lock); + } + + return command->retval; +} + struct stasis_app_control { + /*! Queue of commands to dispatch on the channel */ + struct ao2_container *command_queue; /*! * When set, /c app_stasis should exit and continue in the dialplan. */ @@ -167,11 +230,24 @@ static struct stasis_app_control *control_create(const char *uniqueid) return NULL; } + control->command_queue = ao2_container_alloc_list(0, 0, NULL, NULL); + strncpy(control->channel_id, uniqueid, size - sizeof(*control)); return control; } +static void *exec_command(struct stasis_app_control *control, + struct stasis_app_command *command) +{ + ao2_lock(control); + ao2_ref(command, +1); + ao2_link(control->command_queue, command); + ao2_unlock(control); + + return wait_for_command(command); +} + /*! AO2 hash function for \ref stasis_app_control */ static int control_hash(const void *obj, const int flags) { @@ -199,13 +275,20 @@ static int control_compare(void *lhs, void *rhs, int flags) struct stasis_app_control *stasis_app_control_find_by_channel( const struct ast_channel *chan) { - RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); if (chan == NULL) { return NULL; } + return stasis_app_control_find_by_channel_id( + ast_channel_uniqueid(chan)); +} + +struct stasis_app_control *stasis_app_control_find_by_channel_id( + const char *channel_id) +{ + RAII_VAR(struct ao2_container *, controls, NULL, ao2_cleanup); controls = app_controls(); - return ao2_find(controls, ast_channel_uniqueid(chan), OBJ_KEY); + return ao2_find(controls, channel_id, OBJ_KEY); } /*! @@ -233,6 +316,33 @@ void stasis_app_control_continue(struct stasis_app_control *control) control->continue_to_dialplan = 1; } +static int OK = 0; +static int FAIL = -1; + +static void *__app_control_answer(struct stasis_app_control *control, + struct ast_channel *chan, void *data) +{ + ast_debug(3, "%s: Answering", control->channel_id); + return __ast_answer(chan, 0, 1) == 0 ? &OK : &FAIL; +} + +int stasis_app_control_answer(struct stasis_app_control *control) +{ + RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup); + int *retval; + + ast_debug(3, "%s: Sending answer command\n", control->channel_id); + + command = command_create(__app_control_answer, NULL); + retval = exec_command(control, command); + + if (*retval != 0) { + ast_log(LOG_WARNING, "Failed to answer channel"); + } + + return *retval; +} + static struct ast_json *app_event_create( const char *event_name, const struct ast_channel_snapshot *snapshot, @@ -410,6 +520,26 @@ static void control_unlink(struct stasis_app_control *control) ao2_cleanup(control); } +static void dispatch_commands(struct stasis_app_control *control, + struct ast_channel *chan) +{ + struct ao2_iterator i; + void *obj; + + SCOPED_AO2LOCK(lock, control); + + i = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK); + + while ((obj = ao2_iterator_next(&i))) { + RAII_VAR(struct stasis_app_command *, command, obj, ao2_cleanup); + void *retval = command->callback(control, chan, command->data); + command_complete(command, retval); + } + + ao2_iterator_destroy(&i); +} + + /*! /brief Stasis dialplan application callback */ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, char *argv[]) @@ -458,8 +588,38 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, return res; } - while (!hungup && !control_continue_test_and_reset(control) && ast_waitfor(chan, -1) > -1) { - RAII_VAR(struct ast_frame *, f, ast_read(chan), ast_frame_dtor); + while (1) { + RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor); + int r; + + if (hungup) { + ast_debug(3, "%s: Hangup\n", + ast_channel_uniqueid(chan)); + break; + } + + if (control_continue_test_and_reset(control)) { + ast_debug(3, "%s: Continue\n", + ast_channel_uniqueid(chan)); + break; + } + + r = ast_waitfor(chan, MAX_WAIT_MS); + + if (r < 0) { + ast_debug(3, "%s: Poll error\n", + ast_channel_uniqueid(chan)); + break; + } + + dispatch_commands(control, chan); + + if (r == 0) { + /* Timeout */ + continue; + } + + f = ast_read(chan); if (!f) { ast_debug(3, "%s: No more frames. Must be done, I guess.\n", ast_channel_uniqueid(chan)); break; @@ -468,8 +628,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, switch (f->frametype) { case AST_FRAME_CONTROL: if (f->subclass.integer == AST_CONTROL_HANGUP) { - ast_debug(3, "%s: Received hangup\n", - ast_channel_uniqueid(chan)); hungup = 1; } break; diff --git a/res/res_stasis_http.c b/res/res_stasis_http.c new file mode 100644 index 000000000..aa9c8a8cd --- /dev/null +++ b/res/res_stasis_http.c @@ -0,0 +1,932 @@ +/* + * 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> + * + * The API itself is documented using <a + * href="https://developers.helloreverb.com/swagger/">Swagger</a>, a lightweight + * mechanism for documenting RESTful API's using JSON. This allows us to use <a + * href="https://github.com/wordnik/swagger-ui">swagger-ui</a> to provide + * executable documentation for the API, generate client bindings in different + * <a href="https://github.com/asterisk/asterisk_rest_libraries">languages</a>, + * and generate a lot of the boilerplate code for implementing the RESTful + * bindings. The API docs live in the \c rest-api/ directory. + * + * The RESTful bindings are generated from the Swagger API docs using a set of + * <a href="http://mustache.github.io/mustache.5.html">Mustache</a> templates. + * The code generator is written in Python, and uses the Python implementation + * <a href="https://github.com/defunkt/pystache">pystache</a>. Pystache has no + * dependencies, and be installed easily using \c pip. Code generation code + * lives in \c rest-api-templates/. + * + * The generated code reduces a lot of boilerplate when it comes to handling + * HTTP requests. It also helps us have greater consistency in the REST API. + * + * The structure of the generated code is: + * + * - res/stasis_http/resource_{resource}.h + * - For each operation in the resouce, a generated argument structure + * (holding the parsed arguments from the request) and function + * declarations (to implement in res/stasis_http/resource_{resource}.c) + * - res_stasis_http_{resource}.c + * - A set of \ref stasis_rest_callback functions, which glue the two + * together. They parse out path variables and request parameters to + * populate a specific \c *_args which is passed to the specific request + * handler (in res/stasis_http/resource_{resource}.c) + * - A tree of \ref stasis_rest_handlers for routing requests to its + * \ref stasis_rest_callback + * + * The basic flow of an HTTP request is: + * + * - stasis_http_callback() + * 1. Initial request validation + * 2. Routes as either a doc request (stasis_http_get_docs) or API + * request (stasis_http_invoke) + * - stasis_http_invoke() + * 1. Further request validation + * 2. Routes the request through the tree of generated + * \ref stasis_rest_handlers. + * 3. Dispatch to the generated callback + * - \c stasis_http_*_cb + * 1. Populate \c *_args struct with path and get params + * 2. Invoke the request handler + * 3. Validates and sends response + */ + +/*** MODULEINFO + <depend type="module">app_stasis</depend> + <support_level>core</support_level> + ***/ + +/*** DOCUMENTATION + <configInfo name="res_stasis_http" language="en_US"> + <synopsis>HTTP binding for the Stasis API</synopsis> + <configFile name="stasis_http.conf"> + <configObject name="global"> + <synopsis>Global configuration settings</synopsis> + <configOption name="enabled"> + <synopsis>Enable/disable the stasis-http module</synopsis> + </configOption> + <configOption name="pretty"> + <synopsis>Responses from stasis-http are formatted to be human readable</synopsis> + </configOption> + </configObject> + </configFile> + </configInfo> +***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/paths.h" +#include "asterisk/stasis_http.h" +#include "asterisk/config_options.h" + +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +/*! \brief Global configuration options for stasis http. */ +struct conf_global_options { + /*! Enabled by default, disabled if false. */ + int enabled:1; + /*! Encoding format used during output (default compact). */ + enum ast_json_encoding_format format; +}; + +/*! \brief All configuration options for stasis http. */ +struct conf { + /*! The general section configuration options. */ + struct conf_global_options *global; +}; + +/*! \brief Locking container for safe configuration access. */ +static AO2_GLOBAL_OBJ_STATIC(confs); + +/*! \brief Mapping of the stasis http conf struct's globals to the + * general context in the config file. */ +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .name = "global", + .item_offset = offsetof(struct conf, global), + .category = "^general$", + .category_match = ACO_WHITELIST +}; + +static struct aco_type *global_options[] = ACO_TYPES(&global_option); + +/*! \brief Disposes of the stasis http conf object */ +static void conf_destructor(void *obj) +{ + struct conf *cfg = obj; + ao2_cleanup(cfg->global); +} + +/*! \brief Creates the statis http conf object. */ +static void *conf_alloc(void) +{ + struct conf *cfg; + + if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) { + return NULL; + } + + if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) { + ao2_ref(cfg, -1); + return NULL; + } + return cfg; +} + +/*! \brief The conf file that's processed for the module. */ +static struct aco_file conf_file = { + /*! The config file name. */ + .filename = "stasis_http.conf", + /*! The mapping object types to be processed. */ + .types = ACO_TYPES(&global_option), +}; + +CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc, + .files = ACO_FILES(&conf_file)); + +/*! \brief Bitfield handler since it is not possible to take address. */ +static int conf_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct conf_global_options *global = obj; + + if (!strcasecmp(var->name, "enabled")) { + global->enabled = ast_true(var->value); + } else { + return -1; + } + + return 0; +} + +/*! \brief Encoding format handler converts from boolean to enum. */ +static int encoding_format_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct conf_global_options *global = obj; + + if (!strcasecmp(var->name, "pretty")) { + global->format = ast_true(var->value) ? AST_JSON_PRETTY : AST_JSON_COMPACT; + } else { + return -1; + } + + return 0; +} + +/*! \brief Helper function to check if module is enabled. */ +static char is_enabled(void) +{ + RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup); + + return cfg->global->enabled; +} + +/*! Lock for \ref root_handler */ +static ast_mutex_t root_handler_lock; + +/*! Handler for root RESTful resource. */ +static struct stasis_rest_handlers *root_handler; + +/*! Pre-defined message for allocation failures. */ +static struct ast_json *alloc_failed_message; + +int stasis_http_add_handler(struct stasis_rest_handlers *handler) +{ + RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup); + size_t old_size, new_size; + + SCOPED_MUTEX(lock, &root_handler_lock); + + old_size = sizeof(*new_handler) + + root_handler->num_children * sizeof(handler); + new_size = old_size + sizeof(handler); + + new_handler = ao2_alloc(new_size, NULL); + if (!new_handler) { + return -1; + } + memcpy(new_handler, root_handler, old_size); + new_handler->children[new_handler->num_children++] = handler; + + ao2_cleanup(root_handler); + ao2_ref(new_handler, +1); + root_handler = new_handler; + return 0; +} + +int stasis_http_remove_handler(struct stasis_rest_handlers *handler) +{ + RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup); + size_t old_size, new_size, i, j; + + SCOPED_MUTEX(lock, &root_handler_lock); + old_size = sizeof(*new_handler) + + root_handler->num_children * sizeof(handler); + new_size = old_size - sizeof(handler); + + new_handler = ao2_alloc(new_size, NULL); + if (!new_handler) { + return -1; + } + memcpy(new_handler, root_handler, sizeof(*new_handler)); + + for (i = 0, j = 0; i < root_handler->num_children; ++i) { + if (root_handler->children[i] == handler) { + continue; + } + new_handler->children[j++] = root_handler->children[i]; + } + new_handler->num_children = j; + + ao2_cleanup(root_handler); + ao2_ref(new_handler, +1); + root_handler = new_handler; + return 0; +} + +static struct stasis_rest_handlers *get_root_handler(void) +{ + SCOPED_MUTEX(lock, &root_handler_lock); + ao2_ref(root_handler, +1); + return root_handler; +} + +static struct stasis_rest_handlers *root_handler_create(void) +{ + RAII_VAR(struct stasis_rest_handlers *, handler, NULL, ao2_cleanup); + + handler = ao2_alloc(sizeof(*handler), NULL); + if (!handler) { + return NULL; + } + handler->path_segment = "stasis"; + + ao2_ref(handler, +1); + return handler; +} + +void stasis_http_response_error(struct stasis_http_response *response, + int response_code, + const char *response_text, + const char *message_fmt, ...) +{ + RAII_VAR(struct ast_json *, message, NULL, ast_json_unref); + va_list ap; + + va_start(ap, message_fmt); + message = ast_json_vstringf(message_fmt, ap); + response->message = ast_json_pack("{s: o}", + "message", ast_json_ref(message)); + response->response_code = response_code; + response->response_text = response_text; +} + +void stasis_http_response_ok(struct stasis_http_response *response, + struct ast_json *message) +{ + response->message = message; + response->response_code = 200; + response->response_text = "OK"; +} + +void stasis_http_response_no_content(struct stasis_http_response *response) +{ + response->message = NULL; + response->response_code = 204; + response->response_text = "No Content"; +} + +void stasis_http_response_alloc_failed(struct stasis_http_response *response) +{ + response->message = ast_json_ref(alloc_failed_message); + response->response_code = 500; + response->response_text = "Internal Server Error"; +} + +static void add_allow_header(struct stasis_rest_handlers *handler, + struct stasis_http_response *response) +{ + enum ast_http_method m; + ast_str_append(&response->headers, 0, + "Allow: OPTIONS"); + for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) { + if (handler->callbacks[m] != NULL) { + ast_str_append(&response->headers, 0, + ",%s", ast_get_http_method(m)); + } + } + ast_str_append(&response->headers, 0, "\r\n"); +} + +#define ACR_METHOD "Access-Control-Request-Method" +#define ACR_HEADERS "Access-Control-Request-Headers" +#define ACA_METHODS "Access-Control-Allow-Methods" +#define ACA_HEADERS "Access-Control-Allow-Headers" + +/*! + * \brief Handle OPTIONS request, mainly for CORS preflight requests. + * + * Some browsers will send this prior to non-simple methods (i.e. DELETE). + * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.2. + */ +static void handle_options(struct stasis_rest_handlers *handler, + struct ast_variable *headers, + struct stasis_http_response *response) +{ + struct ast_variable *header; + char const *acr_method = NULL; + char const *acr_headers = NULL; + char const *origin = NULL; + + RAII_VAR(struct ast_str *, allow, NULL, ast_free); + enum ast_http_method m; + int allowed = 0; + + /* Regular OPTIONS response */ + add_allow_header(handler, response); + response->response_code = 204; + response->response_text = "No Content"; + response->message = NULL; + + /* Parse CORS headers */ + for (header = headers; header != NULL; header = header->next) { + if (strcmp(ACR_METHOD, header->name) == 0) { + acr_method = header->value; + } else if (strcmp(ACR_HEADERS, header->name) == 0) { + acr_headers = header->value; + } else if (strcmp("Origin", header->name) == 0) { + origin = header->value; + } + } + + /* CORS 6.2, #1 - "If the Origin header is not present terminate this + * set of steps. + */ + if (origin == NULL) { + return; + } + + /* CORS 6.2, #2 - "If the value of the Origin header is not a + * case-sensitive match for any of the values in list of origins do not + * set any additional headers and terminate this set of steps. + * + * "Always matching is acceptable since the list of origins can be + * unbounded. + * + * "The Origin header can only contain a single origin as the user agent + * will not follow redirects. + * + * TODO - pull list of allowed origins from config + */ + + /* CORS 6.2, #3 - "If there is no Access-Control-Request-Method header + * or if parsing failed, do not set any additional headers and terminate + * this set of steps." + */ + if (acr_method == NULL) { + return; + } + + /* CORS 6.2, #4 - "If there are no Access-Control-Request-Headers + * headers let header field-names be the empty list." + */ + if (acr_headers == NULL) { + acr_headers = ""; + } + + /* CORS 6.2, #5 - "If method is not a case-sensitive match for any of + * the values in list of methods do not set any additional headers and + * terminate this set of steps." + */ + allow = ast_str_create(20); + + if (!allow) { + stasis_http_response_alloc_failed(response); + return; + } + + /* Go ahead and build the ACA_METHODS header at the same time */ + for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) { + if (handler->callbacks[m] != NULL) { + char const *m_str = ast_get_http_method(m); + if (strcmp(m_str, acr_method) == 0) { + allowed = 1; + } + ast_str_append(&allow, 0, ",%s", m_str); + } + } + + if (!allowed) { + return; + } + + /* CORS 6.2 #6 - "If any of the header field-names is not a ASCII + * case-insensitive match for any of the values in list of headers do + * not set any additional headers and terminate this set of steps. + * + * "Note: Always matching is acceptable since the list of headers can be + * unbounded." + */ + + /* CORS 6.2 #7 - "If the resource supports credentials add a single + * Access-Control-Allow-Origin header, with the value of the Origin + * header as value, and add a single Access-Control-Allow-Credentials + * header with the case-sensitive string "true" as value." + * + * Added by process_cors_request() earlier in the request. + */ + + /* CORS 6.2 #8 - "Optionally add a single Access-Control-Max-Age + * header..." + */ + + /* CORS 6.2 #9 - "Add one or more Access-Control-Allow-Methods headers + * consisting of (a subset of) the list of methods." + */ + ast_str_append(&response->headers, 0, "%s: OPTIONS,%s\r\n", + ACA_METHODS, ast_str_buffer(allow)); + + + /* CORS 6.2, #10 - "Add one or more Access-Control-Allow-Headers headers + * consisting of (a subset of) the list of headers. + * + * "Since the list of headers can be unbounded simply returning headers + * can be enough." + */ + if (!ast_strlen_zero(acr_headers)) { + ast_str_append(&response->headers, 0, "%s: %s\r\n", + ACA_HEADERS, acr_headers); + } +} + +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) +{ + RAII_VAR(char *, response_text, NULL, ast_free); + RAII_VAR(struct stasis_rest_handlers *, root, NULL, ao2_cleanup); + struct stasis_rest_handlers *handler; + struct ast_variable *path_vars = NULL; + char *path = ast_strdupa(uri); + const char *path_segment; + stasis_rest_callback callback; + + root = handler = get_root_handler(); + ast_assert(root != NULL); + + while ((path_segment = strsep(&path, "/")) && (strlen(path_segment) > 0)) { + struct stasis_rest_handlers *found_handler = NULL; + int i; + ast_debug(3, "Finding handler for %s\n", path_segment); + for (i = 0; found_handler == NULL && i < handler->num_children; ++i) { + struct stasis_rest_handlers *child = handler->children[i]; + + ast_debug(3, " Checking %s\n", child->path_segment); + if (child->is_wildcard) { + /* Record the path variable */ + struct ast_variable *path_var = ast_variable_new(child->path_segment, path_segment, __FILE__); + path_var->next = path_vars; + path_vars = path_var; + found_handler = child; + } else if (strcmp(child->path_segment, path_segment) == 0) { + found_handler = child; + } + } + + if (found_handler == NULL) { + /* resource not found */ + ast_debug(3, " Handler not found\n"); + stasis_http_response_error( + response, 404, "Not Found", + "Resource not found"); + return; + } else { + ast_debug(3, " Got it!\n"); + handler = found_handler; + } + } + + ast_assert(handler != NULL); + if (method == AST_HTTP_OPTIONS) { + handle_options(handler, headers, response); + return; + } + + if (method < 0 || method >= AST_HTTP_MAX_METHOD) { + add_allow_header(handler, response); + stasis_http_response_error( + response, 405, "Method Not Allowed", + "Invalid method"); + return; + } + + callback = handler->callbacks[method]; + if (callback == NULL) { + add_allow_header(handler, response); + stasis_http_response_error( + response, 405, "Method Not Allowed", + "Invalid method"); + return; + } + + callback(get_params, path_vars, headers, response); + if (response->message == NULL && response->response_code == 0) { + /* Really should not happen */ + ast_assert(0); + stasis_http_response_error( + response, 418, "I'm a teapot", + "Method not implemented"); + } +} + +void stasis_http_get_docs(const char *uri, struct ast_variable *headers, + struct stasis_http_response *response) +{ + RAII_VAR(struct ast_str *, absolute_path_builder, NULL, ast_free); + RAII_VAR(char *, absolute_api_dirname, NULL, free); + RAII_VAR(char *, absolute_filename, NULL, free); + struct ast_json *obj = NULL; + struct ast_variable *host = NULL; + struct ast_json_error error = {}; + struct stat file_stat; + + ast_debug(3, "%s(%s)\n", __func__, uri); + + absolute_path_builder = ast_str_create(80); + if (absolute_path_builder == NULL) { + stasis_http_response_alloc_failed(response); + return; + } + + /* absolute path to the rest-api directory */ + ast_str_append(&absolute_path_builder, 0, "%s", ast_config_AST_DATA_DIR); + ast_str_append(&absolute_path_builder, 0, "/rest-api/"); + absolute_api_dirname = realpath(ast_str_buffer(absolute_path_builder), NULL); + if (absolute_api_dirname == NULL) { + ast_log(LOG_ERROR, "Error determining real directory for rest-api\n"); + stasis_http_response_error( + response, 500, "Internal Server Error", + "Cannot find rest-api directory"); + return; + } + + /* absolute path to the requested file */ + ast_str_append(&absolute_path_builder, 0, "%s", uri); + absolute_filename = realpath(ast_str_buffer(absolute_path_builder), NULL); + if (absolute_filename == NULL) { + switch (errno) { + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + stasis_http_response_error( + response, 404, "Not Found", + "Resource not found"); + break; + case EACCES: + stasis_http_response_error( + response, 403, "Forbidden", + "Permission denied"); + break; + default: + ast_log(LOG_ERROR, + "Error determining real path for uri '%s': %s\n", + uri, strerror(errno)); + stasis_http_response_error( + response, 500, "Internal Server Error", + "Cannot find file"); + break; + } + return; + } + + if (!ast_begins_with(absolute_filename, absolute_api_dirname)) { + /* HACKERZ! */ + ast_log(LOG_ERROR, + "Invalid attempt to access '%s' (not in %s)\n", + absolute_filename, absolute_api_dirname); + stasis_http_response_error( + response, 404, "Not Found", + "Resource not found"); + return; + } + + if (stat(absolute_filename, &file_stat) == 0) { + if (!(file_stat.st_mode & S_IFREG)) { + /* Not a file */ + stasis_http_response_error( + response, 403, "Forbidden", + "Invalid access"); + return; + } + } else { + /* Does not exist */ + stasis_http_response_error( + response, 404, "Not Found", + "Resource not found"); + return; + } + + /* Load resource object from file */ + obj = ast_json_load_new_file(absolute_filename, &error); + if (obj == NULL) { + ast_log(LOG_ERROR, "Error parsing resource file: %s:%d(%d) %s\n", + error.source, error.line, error.column, error.text); + stasis_http_response_error( + response, 500, "Internal Server Error", + "Yikes! Cannot parse resource"); + return; + } + + /* Update the basePath properly */ + if (ast_json_object_get(obj, "basePath") != NULL) { + for (host = headers; host; host = host->next) { + if (strcasecmp(host->name, "Host") == 0) { + break; + } + } + if (host != NULL) { + ast_json_object_set( + obj, "basePath", + ast_json_stringf("http://%s/stasis", host->value)); + } else { + /* Without the host, we don't have the basePath */ + ast_json_object_del(obj, "basePath"); + } + } + + stasis_http_response_ok(response, obj); +} + +static void remove_trailing_slash(const char *uri, + struct stasis_http_response *response) +{ + char *slashless = ast_strdupa(uri); + slashless[strlen(slashless) - 1] = '\0'; + + ast_str_append(&response->headers, 0, + "Location: /stasis/%s\r\n", slashless); + stasis_http_response_error(response, 302, "Found", + "Redirecting to %s", slashless); +} + +/*! + * \brief Handle CORS headers for simple requests. + * + * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.1. + */ +static void process_cors_request(struct ast_variable *headers, + struct stasis_http_response *response) +{ + char const *origin = NULL; + struct ast_variable *header; + + /* Parse CORS headers */ + for (header = headers; header != NULL; header = header->next) { + if (strcmp("Origin", header->name) == 0) { + origin = header->value; + } + } + + /* CORS 6.1, #1 - "If the Origin header is not present terminate this + * set of steps." + */ + if (origin == NULL) { + return; + } + + /* CORS 6.1, #2 - "If the value of the Origin header is not a + * case-sensitive match for any of the values in list of origins, do not + * set any additional headers and terminate this set of steps. + * + * "Note: Always matching is acceptable since the list of origins can be + * unbounded." + * + * TODO - pull list of allowed origins from config + */ + + /* CORS 6.1, #3 - "If the resource supports credentials add a single + * Access-Control-Allow-Origin header, with the value of the Origin + * header as value, and add a single Access-Control-Allow-Credentials + * header with the case-sensitive string "true" as value. + * + * "Otherwise, add a single Access-Control-Allow-Origin header, with + * either the value of the Origin header or the string "*" as value." + * + * TODO - when we add authentication, this will change to + * Access-Control-Allow-Credentials. + */ + ast_str_append(&response->headers, 0, + "Access-Control-Allow-Origin: %s\r\n", origin); + + /* CORS 6.1, #4 - "If the list of exposed headers is not empty add one + * or more Access-Control-Expose-Headers headers, with as values the + * header field names given in the list of exposed headers." + * + * No exposed headers; skipping + */ +} + + +/*! + * \internal + * \brief Stasis HTTP handler. + * + * This handler takes the HTTP request and turns it into the appropriate + * RESTful request (conversion to JSON, routing, etc.) + * + * \param ser TCP session. + * \param urih URI handler. + * \param uri URI requested. + * \param method HTTP method. + * \param get_params HTTP \c GET params. + * \param headers HTTP headers. + */ +static int stasis_http_callback(struct ast_tcptls_session_instance *ser, + const struct ast_http_uri *urih, + const char *uri, + enum ast_http_method method, + struct ast_variable *get_params, + struct ast_variable *headers) +{ + RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup); + RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free); + RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free); + struct stasis_http_response response = {}; + int ret = 0; + + if (!response_headers || !response_body) { + return -1; + } + + response.headers = ast_str_create(40); + + process_cors_request(headers, &response); + + if (ast_ends_with(uri, "/")) { + remove_trailing_slash(uri, &response); + } else if (ast_begins_with(uri, "api-docs/")) { + /* Serving up API docs */ + if (method != AST_HTTP_GET) { + response.message = + ast_json_pack("{s: s}", + "message", "Unsupported method"); + response.response_code = 405; + response.response_text = "Method Not Allowed"; + } else { + /* Skip the api-docs prefix */ + stasis_http_get_docs(strchr(uri, '/') + 1, headers, &response); + } + } else { + /* Other RESTful resources */ + stasis_http_invoke(uri, method, get_params, headers, &response); + } + + /* Leaving message unset is only allowed for 204 (No Content). + * If you explicitly want to have no content for a different return + * code, set message to ast_json_null(). + */ + ast_assert(response.response_code == 204 || response.message != NULL); + ast_assert(response.response_code > 0); + + ast_str_append(&response_headers, 0, "%s", ast_str_buffer(response.headers)); + + /* response.message could be NULL, in which case the empty response_body + * is correct + */ + if (response.message && !ast_json_is_null(response.message)) { + ast_str_append(&response_headers, 0, + "Content-type: application/json\r\n"); + if (ast_json_dump_str_format(response.message, &response_body, cfg->global->format) != 0) { + /* Error encoding response */ + response.response_code = 500; + response.response_text = "Internal Server Error"; + ast_str_set(&response_body, 0, "%s", ""); + ast_str_set(&response_headers, 0, "%s", ""); + ret = -1; + } + } + + ast_http_send(ser, method, response.response_code, + response.response_text, response_headers, response_body, + 0, 0); + /* ast_http_send takes ownership, so we don't have to free them */ + response_headers = NULL; + response_body = NULL; + + ast_json_unref(response.message); + return ret; +} + +static struct ast_http_uri http_uri = { + .callback = stasis_http_callback, + .description = "Asterisk RESTful API", + .uri = "stasis", + + .has_subtree = 1, + .data = NULL, + .key = __FILE__, +}; + +static int load_module(void) +{ + ast_mutex_init(&root_handler_lock); + root_handler = root_handler_create(); + if (!root_handler) { + return AST_MODULE_LOAD_FAILURE; + } + + if (aco_info_init(&cfg_info)) { + aco_info_destroy(&cfg_info); + return AST_MODULE_LOAD_DECLINE; + } + + aco_option_register_custom(&cfg_info, "enabled", ACO_EXACT, global_options, + "yes", conf_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "pretty", ACO_EXACT, global_options, + "no", encoding_format_handler, 0); + + if (aco_process_config(&cfg_info, 0)) { + aco_info_destroy(&cfg_info); + 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); + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_json_unref(alloc_failed_message); + alloc_failed_message = NULL; + + if (is_enabled()) { + ast_http_uri_unlink(&http_uri); + } + + aco_info_destroy(&cfg_info); + ao2_global_obj_release(confs); + + ao2_cleanup(root_handler); + ast_mutex_destroy(&root_handler_lock); + + return 0; +} + +static int reload_module(void) +{ + char was_enabled = is_enabled(); + + if (aco_process_config(&cfg_info, 1)) { + return AST_MODULE_LOAD_DECLINE; + } + + if (was_enabled && !is_enabled()) { + ast_http_uri_unlink(&http_uri); + } else if (!was_enabled && is_enabled()) { + ast_http_uri_link(&http_uri); + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, + AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, + "Stasis HTTP bindings", + .load = load_module, + .unload = unload_module, + .reload = reload_module, + .nonoptreq = "app_stasis", + .load_pri = AST_MODPRI_APP_DEPEND, + ); diff --git a/res/res_stasis_http.exports.in b/res/res_stasis_http.exports.in new file mode 100644 index 000000000..726a86424 --- /dev/null +++ b/res/res_stasis_http.exports.in @@ -0,0 +1,6 @@ +{ + global: + LINKER_SYMBOL_PREFIXstasis_http_*; + local: + *; +}; diff --git a/res/res_stasis_http_asterisk.c b/res/res_stasis_http_asterisk.c new file mode 100644 index 000000000..333c66b5c --- /dev/null +++ b/res/res_stasis_http_asterisk.c @@ -0,0 +1,103 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Asterisk resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_asterisk.h" + +/*! + * \brief Parameter parsing callback for /asterisk/info. + * \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_get_asterisk_info_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_asterisk_info_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "only") == 0) { + args.only = (i->value); + } else + {} + } + stasis_http_get_asterisk_info(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/asterisk.{format} */ +static struct stasis_rest_handlers asterisk_info = { + .path_segment = "info", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_asterisk_info_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/asterisk.{format} */ +static struct stasis_rest_handlers asterisk = { + .path_segment = "asterisk", + .callbacks = { + }, + .num_children = 1, + .children = { &asterisk_info, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&asterisk); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&asterisk); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Asterisk resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_bridges.c b/res/res_stasis_http_bridges.c new file mode 100644 index 000000000..48d5b7277 --- /dev/null +++ b/res/res_stasis_http_bridges.c @@ -0,0 +1,291 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Bridge resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_bridges.h" + +/*! + * \brief Parameter parsing callback for /bridges. + * \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_get_bridges_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_bridges_args args = {}; + stasis_http_get_bridges(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /bridges. + * \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_new_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_new_bridge_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "type") == 0) { + args.type = (i->value); + } else + {} + } + stasis_http_new_bridge(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}. + * \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_get_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_bridge_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + stasis_http_get_bridge(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}. + * \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_delete_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_delete_bridge_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + stasis_http_delete_bridge(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/addChannel. + * \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_add_channel_to_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_add_channel_to_bridge_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "channel") == 0) { + args.channel = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + stasis_http_add_channel_to_bridge(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/removeChannel. + * \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_remove_channel_from_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_remove_channel_from_bridge_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "channel") == 0) { + args.channel = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + stasis_http_remove_channel_from_bridge(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/record. + * \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_record_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_record_bridge_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "name") == 0) { + args.name = (i->value); + } else + if (strcmp(i->name, "maxDurationSeconds") == 0) { + args.max_duration_seconds = atoi(i->value); + } else + if (strcmp(i->name, "maxSilenceSeconds") == 0) { + args.max_silence_seconds = atoi(i->value); + } else + if (strcmp(i->name, "append") == 0) { + args.append = atoi(i->value); + } else + if (strcmp(i->name, "beep") == 0) { + args.beep = atoi(i->value); + } else + if (strcmp(i->name, "terminateOn") == 0) { + args.terminate_on = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + stasis_http_record_bridge(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges_bridgeId_addChannel = { + .path_segment = "addChannel", + .callbacks = { + [AST_HTTP_POST] = stasis_http_add_channel_to_bridge_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges_bridgeId_removeChannel = { + .path_segment = "removeChannel", + .callbacks = { + [AST_HTTP_POST] = stasis_http_remove_channel_from_bridge_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges_bridgeId_record = { + .path_segment = "record", + .callbacks = { + [AST_HTTP_POST] = stasis_http_record_bridge_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges_bridgeId = { + .path_segment = "bridgeId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_bridge_cb, + [AST_HTTP_DELETE] = stasis_http_delete_bridge_cb, + }, + .num_children = 3, + .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_record, } +}; +/*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges = { + .path_segment = "bridges", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_bridges_cb, + [AST_HTTP_POST] = stasis_http_new_bridge_cb, + }, + .num_children = 1, + .children = { &bridges_bridgeId, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&bridges); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&bridges); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Bridge resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_channels.c b/res/res_stasis_http_channels.c new file mode 100644 index 000000000..c21bc1073 --- /dev/null +++ b/res/res_stasis_http_channels.c @@ -0,0 +1,504 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Channel resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_channels.h" + +/*! + * \brief Parameter parsing callback for /channels. + * \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_get_channels_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_channels_args args = {}; + stasis_http_get_channels(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels. + * \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_originate_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_originate_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "endpoint") == 0) { + args.endpoint = (i->value); + } else + if (strcmp(i->name, "extension") == 0) { + args.extension = (i->value); + } else + if (strcmp(i->name, "context") == 0) { + args.context = (i->value); + } else + {} + } + stasis_http_originate(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}. + * \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_get_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_channel_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_get_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}. + * \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_delete_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_delete_channel_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_delete_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/dial. + * \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_dial_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_dial_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "endpoint") == 0) { + args.endpoint = (i->value); + } else + if (strcmp(i->name, "extension") == 0) { + args.extension = (i->value); + } else + if (strcmp(i->name, "context") == 0) { + args.context = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_dial(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/continue. + * \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_continue_in_dialplan_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_continue_in_dialplan_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_continue_in_dialplan(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/answer. + * \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_answer_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_answer_channel_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_answer_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/mute. + * \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_mute_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_mute_channel_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "direction") == 0) { + args.direction = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_mute_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/unmute. + * \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_unmute_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_unmute_channel_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "direction") == 0) { + args.direction = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_unmute_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/hold. + * \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_hold_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_hold_channel_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_hold_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/unhold. + * \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_unhold_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_unhold_channel_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_unhold_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/play. + * \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_play_on_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_play_on_channel_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "media") == 0) { + args.media = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_play_on_channel(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /channels/{channelId}/record. + * \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_record_channel_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_record_channel_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "name") == 0) { + args.name = (i->value); + } else + if (strcmp(i->name, "format") == 0) { + args.format = (i->value); + } else + if (strcmp(i->name, "maxDurationSeconds") == 0) { + args.max_duration_seconds = atoi(i->value); + } else + if (strcmp(i->name, "maxSilenceSeconds") == 0) { + args.max_silence_seconds = atoi(i->value); + } else + if (strcmp(i->name, "append") == 0) { + args.append = atoi(i->value); + } else + if (strcmp(i->name, "beep") == 0) { + args.beep = atoi(i->value); + } else + if (strcmp(i->name, "terminateOn") == 0) { + args.terminate_on = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "channelId") == 0) { + args.channel_id = (i->value); + } else + {} + } + stasis_http_record_channel(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_dial = { + .path_segment = "dial", + .callbacks = { + [AST_HTTP_POST] = stasis_http_dial_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_continue = { + .path_segment = "continue", + .callbacks = { + [AST_HTTP_POST] = stasis_http_continue_in_dialplan_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_answer = { + .path_segment = "answer", + .callbacks = { + [AST_HTTP_POST] = stasis_http_answer_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_mute = { + .path_segment = "mute", + .callbacks = { + [AST_HTTP_POST] = stasis_http_mute_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_unmute = { + .path_segment = "unmute", + .callbacks = { + [AST_HTTP_POST] = stasis_http_unmute_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_hold = { + .path_segment = "hold", + .callbacks = { + [AST_HTTP_POST] = stasis_http_hold_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_unhold = { + .path_segment = "unhold", + .callbacks = { + [AST_HTTP_POST] = stasis_http_unhold_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_play = { + .path_segment = "play", + .callbacks = { + [AST_HTTP_POST] = stasis_http_play_on_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId_record = { + .path_segment = "record", + .callbacks = { + [AST_HTTP_POST] = stasis_http_record_channel_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels_channelId = { + .path_segment = "channelId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_channel_cb, + [AST_HTTP_DELETE] = stasis_http_delete_channel_cb, + }, + .num_children = 9, + .children = { &channels_channelId_dial,&channels_channelId_continue,&channels_channelId_answer,&channels_channelId_mute,&channels_channelId_unmute,&channels_channelId_hold,&channels_channelId_unhold,&channels_channelId_play,&channels_channelId_record, } +}; +/*! \brief REST handler for /api-docs/channels.{format} */ +static struct stasis_rest_handlers channels = { + .path_segment = "channels", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_channels_cb, + [AST_HTTP_POST] = stasis_http_originate_cb, + }, + .num_children = 1, + .children = { &channels_channelId, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&channels); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&channels); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Channel resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_endpoints.c b/res/res_stasis_http_endpoints.c new file mode 100644 index 000000000..a420d4ede --- /dev/null +++ b/res/res_stasis_http_endpoints.c @@ -0,0 +1,127 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Endpoint resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_endpoints.h" + +/*! + * \brief Parameter parsing callback for /endpoints. + * \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_get_endpoints_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_endpoints_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "withType") == 0) { + args.with_type = (i->value); + } else + {} + } + stasis_http_get_endpoints(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /endpoints/{endpointId}. + * \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_get_endpoint_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_endpoint_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "endpointId") == 0) { + args.endpoint_id = (i->value); + } else + {} + } + stasis_http_get_endpoint(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/endpoints.{format} */ +static struct stasis_rest_handlers endpoints_endpointId = { + .path_segment = "endpointId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_endpoint_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/endpoints.{format} */ +static struct stasis_rest_handlers endpoints = { + .path_segment = "endpoints", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_endpoints_cb, + }, + .num_children = 1, + .children = { &endpoints_endpointId, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&endpoints); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&endpoints); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Endpoint resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_events.c b/res/res_stasis_http_events.c new file mode 100644 index 000000000..62ed44f96 --- /dev/null +++ b/res/res_stasis_http_events.c @@ -0,0 +1,95 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief WebSocket resource + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.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) +{ + struct ast_event_websocket_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "app") == 0) { + args.app = (i->value); + } else + {} + } + stasis_http_event_websocket(headers, &args, response); +} + +/*! \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 = { } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&events); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&events); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - WebSocket resource", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_playback.c b/res/res_stasis_http_playback.c new file mode 100644 index 000000000..77dcee41c --- /dev/null +++ b/res/res_stasis_http_playback.c @@ -0,0 +1,164 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Playback control resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_playback.h" + +/*! + * \brief Parameter parsing callback for /playback/{playbackId}. + * \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_get_playback_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_playback_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "playbackId") == 0) { + args.playback_id = (i->value); + } else + {} + } + stasis_http_get_playback(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /playback/{playbackId}. + * \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_stop_playback_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_stop_playback_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "playbackId") == 0) { + args.playback_id = (i->value); + } else + {} + } + stasis_http_stop_playback(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /playback/{playbackId}/control. + * \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_control_playback_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_control_playback_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "operation") == 0) { + args.operation = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "playbackId") == 0) { + args.playback_id = (i->value); + } else + {} + } + stasis_http_control_playback(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/playback.{format} */ +static struct stasis_rest_handlers playback_playbackId_control = { + .path_segment = "control", + .callbacks = { + [AST_HTTP_POST] = stasis_http_control_playback_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/playback.{format} */ +static struct stasis_rest_handlers playback_playbackId = { + .path_segment = "playbackId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_playback_cb, + [AST_HTTP_DELETE] = stasis_http_stop_playback_cb, + }, + .num_children = 1, + .children = { &playback_playbackId_control, } +}; +/*! \brief REST handler for /api-docs/playback.{format} */ +static struct stasis_rest_handlers playback = { + .path_segment = "playback", + .callbacks = { + }, + .num_children = 1, + .children = { &playback_playbackId, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&playback); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&playback); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Playback control resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_recordings.c b/res/res_stasis_http_recordings.c new file mode 100644 index 000000000..b6a3b418e --- /dev/null +++ b/res/res_stasis_http_recordings.c @@ -0,0 +1,398 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Recording resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_recordings.h" + +/*! + * \brief Parameter parsing callback for /recordings. + * \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_get_recordings_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_recordings_args args = {}; + stasis_http_get_recordings(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/stored. + * \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_get_stored_recordings_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_stored_recordings_args args = {}; + stasis_http_get_stored_recordings(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/stored/{recordingId}. + * \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_get_stored_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_stored_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_get_stored_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/stored/{recordingId}. + * \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_delete_stored_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_delete_stored_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_delete_stored_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live. + * \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_get_live_recordings_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_live_recordings_args args = {}; + stasis_http_get_live_recordings(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}. + * \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_get_live_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_live_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_get_live_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}. + * \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_cancel_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_cancel_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_cancel_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}/stop. + * \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_stop_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_stop_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_stop_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}/pause. + * \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_pause_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_pause_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_pause_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}/unpause. + * \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_unpause_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_unpause_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_unpause_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}/mute. + * \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_mute_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_mute_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_mute_recording(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /recordings/live/{recordingId}/unmute. + * \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_unmute_recording_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_unmute_recording_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "recordingId") == 0) { + args.recording_id = (i->value); + } else + {} + } + stasis_http_unmute_recording(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_stored_recordingId = { + .path_segment = "recordingId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_stored_recording_cb, + [AST_HTTP_DELETE] = stasis_http_delete_stored_recording_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_stored = { + .path_segment = "stored", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_stored_recordings_cb, + }, + .num_children = 1, + .children = { &recordings_stored_recordingId, } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live_recordingId_stop = { + .path_segment = "stop", + .callbacks = { + [AST_HTTP_POST] = stasis_http_stop_recording_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live_recordingId_pause = { + .path_segment = "pause", + .callbacks = { + [AST_HTTP_POST] = stasis_http_pause_recording_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live_recordingId_unpause = { + .path_segment = "unpause", + .callbacks = { + [AST_HTTP_POST] = stasis_http_unpause_recording_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live_recordingId_mute = { + .path_segment = "mute", + .callbacks = { + [AST_HTTP_POST] = stasis_http_mute_recording_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live_recordingId_unmute = { + .path_segment = "unmute", + .callbacks = { + [AST_HTTP_POST] = stasis_http_unmute_recording_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live_recordingId = { + .path_segment = "recordingId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_live_recording_cb, + [AST_HTTP_DELETE] = stasis_http_cancel_recording_cb, + }, + .num_children = 5, + .children = { &recordings_live_recordingId_stop,&recordings_live_recordingId_pause,&recordings_live_recordingId_unpause,&recordings_live_recordingId_mute,&recordings_live_recordingId_unmute, } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings_live = { + .path_segment = "live", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_live_recordings_cb, + }, + .num_children = 1, + .children = { &recordings_live_recordingId, } +}; +/*! \brief REST handler for /api-docs/recordings.{format} */ +static struct stasis_rest_handlers recordings = { + .path_segment = "recordings", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_recordings_cb, + }, + .num_children = 2, + .children = { &recordings_stored,&recordings_live, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&recordings); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&recordings); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Recording resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/res_stasis_http_sounds.c b/res/res_stasis_http_sounds.c new file mode 100644 index 000000000..39ad71e52 --- /dev/null +++ b/res/res_stasis_http_sounds.c @@ -0,0 +1,130 @@ +/* + * 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. + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief Sound resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_sounds.h" + +/*! + * \brief Parameter parsing callback for /sounds. + * \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_get_sounds_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_sounds_args args = {}; + struct ast_variable *i; + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "lang") == 0) { + args.lang = (i->value); + } else + if (strcmp(i->name, "format") == 0) { + args.format = (i->value); + } else + {} + } + stasis_http_get_sounds(headers, &args, response); +} +/*! + * \brief Parameter parsing callback for /sounds/{soundId}. + * \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_get_stored_sound_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct stasis_http_response *response) +{ + struct ast_get_stored_sound_args args = {}; + struct ast_variable *i; + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "soundId") == 0) { + args.sound_id = (i->value); + } else + {} + } + stasis_http_get_stored_sound(headers, &args, response); +} + +/*! \brief REST handler for /api-docs/sounds.{format} */ +static struct stasis_rest_handlers sounds_soundId = { + .path_segment = "soundId", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_stored_sound_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/sounds.{format} */ +static struct stasis_rest_handlers sounds = { + .path_segment = "sounds", + .callbacks = { + [AST_HTTP_GET] = stasis_http_get_sounds_cb, + }, + .num_children = 1, + .children = { &sounds_soundId, } +}; + +static int load_module(void) +{ + return stasis_http_add_handler(&sounds); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&sounds); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - Sound resources", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/res/stasis_http.make b/res/stasis_http.make new file mode 100644 index 000000000..3d80e5e88 --- /dev/null +++ b/res/stasis_http.make @@ -0,0 +1,51 @@ +# +# Asterisk -- A telephony toolkit for Linux. +# +# Generated Makefile for res_stasis_http dependencies. +# +# Copyright (C) 2013, Digium, Inc. +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !!!!! DO NOT EDIT !!!!! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# This file is generated by a template. Please see the original template at +# rest-api-templates/stasis_http.make.mustache +# + +res_stasis_http_asterisk.so: stasis_http/resource_asterisk.o + +stasis_http/resource_asterisk.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_asterisk) + +res_stasis_http_endpoints.so: stasis_http/resource_endpoints.o + +stasis_http/resource_endpoints.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_endpoints) + +res_stasis_http_channels.so: stasis_http/resource_channels.o + +stasis_http/resource_channels.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_channels) + +res_stasis_http_bridges.so: stasis_http/resource_bridges.o + +stasis_http/resource_bridges.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_bridges) + +res_stasis_http_recordings.so: stasis_http/resource_recordings.o + +stasis_http/resource_recordings.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_recordings) + +res_stasis_http_sounds.so: stasis_http/resource_sounds.o + +stasis_http/resource_sounds.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_sounds) + +res_stasis_http_playback.so: stasis_http/resource_playback.o + +stasis_http/resource_playback.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_playback) + +res_stasis_http_events.so: stasis_http/resource_events.o + +stasis_http/resource_events.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_events) + diff --git a/res/stasis_http/resource_asterisk.c b/res/stasis_http/resource_asterisk.c new file mode 100644 index 000000000..b5e8b0f54 --- /dev/null +++ b/res/stasis_http/resource_asterisk.c @@ -0,0 +1,39 @@ +/* -*- C -*- + * 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 Implementation for stasis-http stubs. + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_asterisk.h" + +void stasis_http_get_asterisk_info(struct ast_variable *headers, struct ast_get_asterisk_info_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_asterisk_info\n"); +} diff --git a/res/stasis_http/resource_asterisk.h b/res/stasis_http/resource_asterisk.h new file mode 100644 index 000000000..0d373ccb2 --- /dev/null +++ b/res/stasis_http/resource_asterisk.h @@ -0,0 +1,56 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_asterisk.c + * + * Asterisk resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_ASTERISK_H +#define _ASTERISK_RESOURCE_ASTERISK_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_asterisk_info() */ +struct ast_get_asterisk_info_args { + /*! \brief Filter information returned */ + const char *only; +}; +/*! + * \brief Gets Asterisk system information. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_asterisk_info(struct ast_variable *headers, struct ast_get_asterisk_info_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_ASTERISK_H */ diff --git a/res/stasis_http/resource_bridges.c b/res/stasis_http/resource_bridges.c new file mode 100644 index 000000000..ca48ee725 --- /dev/null +++ b/res/stasis_http/resource_bridges.c @@ -0,0 +1,63 @@ +/* -*- C -*- + * 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 Implementation for stasis-http stubs. + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_bridges.h" + +void stasis_http_add_channel_to_bridge(struct ast_variable *headers, struct ast_add_channel_to_bridge_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_add_channel_to_bridge\n"); +} +void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_remove_channel_from_bridge\n"); +} +void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_record_bridge\n"); +} +void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_bridge\n"); +} +void stasis_http_delete_bridge(struct ast_variable *headers, struct ast_delete_bridge_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_delete_bridge\n"); +} +void stasis_http_get_bridges(struct ast_variable *headers, struct ast_get_bridges_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_bridges\n"); +} +void stasis_http_new_bridge(struct ast_variable *headers, struct ast_new_bridge_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_new_bridge\n"); +} diff --git a/res/stasis_http/resource_bridges.h b/res/stasis_http/resource_bridges.h new file mode 100644 index 000000000..db6db205b --- /dev/null +++ b/res/stasis_http/resource_bridges.h @@ -0,0 +1,154 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_bridges.c + * + * Bridge resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_BRIDGES_H +#define _ASTERISK_RESOURCE_BRIDGES_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_bridges() */ +struct ast_get_bridges_args { +}; +/*! + * \brief List active bridges. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_bridges(struct ast_variable *headers, struct ast_get_bridges_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_new_bridge() */ +struct ast_new_bridge_args { + /*! \brief Type of bridge to create. */ + const char *type; +}; +/*! + * \brief Create a new bridge. + * + * This bridge persists until it has been shut down, or Asterisk has been shut down. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_new_bridge(struct ast_variable *headers, struct ast_new_bridge_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_bridge() */ +struct ast_get_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; +}; +/*! + * \brief Get bridge details. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_delete_bridge() */ +struct ast_delete_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; +}; +/*! + * \brief Shut down a bridge bridge. + * + * If any channels are in this bridge, they will be removed and resume whatever they were doing beforehand. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_delete_bridge(struct ast_variable *headers, struct ast_delete_bridge_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_add_channel_to_bridge() */ +struct ast_add_channel_to_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; + /*! \brief Channel's id */ + const char *channel; +}; +/*! + * \brief Add a channel to a bridge. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_add_channel_to_bridge(struct ast_variable *headers, struct ast_add_channel_to_bridge_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_remove_channel_from_bridge() */ +struct ast_remove_channel_from_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; + /*! \brief Channel's id */ + const char *channel; +}; +/*! + * \brief Remove a channel from a bridge. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_record_bridge() */ +struct ast_record_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; + /*! \brief Recording's filename */ + const char *name; + /*! \brief Maximum duration of the recording, in seconds. 0 for no limit. */ + int max_duration_seconds; + /*! \brief Maximum duration of silence, in seconds. 0 for no limit. */ + int max_silence_seconds; + /*! \brief If true, and recording already exists, append to recording. */ + int append; + /*! \brief Play beep when recording begins */ + int beep; + /*! \brief DTMF input to terminate recording. */ + const char *terminate_on; +}; +/*! + * \brief Start a recording. + * + * This records the mixed audio from all channels participating in this bridge. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_record_bridge(struct ast_variable *headers, struct ast_record_bridge_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_BRIDGES_H */ diff --git a/res/stasis_http/resource_channels.c b/res/stasis_http/resource_channels.c new file mode 100644 index 000000000..3cc97c511 --- /dev/null +++ b/res/stasis_http/resource_channels.c @@ -0,0 +1,251 @@ +/* -*- C -*- + * 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 Implementation for stasis-http stubs. + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/stasis_app.h" +#include "asterisk/stasis_channels.h" +#include "resource_channels.h" + +/*! + * \brief Finds the control object for a channel, filling the response with an + * error, if appropriate. + * \param[out] response Response to fill with an error if control is not found. + * \param channel_id ID of the channel to lookup. + * \return Channel control object. + * \return \c NULL if control object does not exist. + */ +static struct stasis_app_control *find_control( + struct stasis_http_response *response, + const char *channel_id) +{ + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + + ast_assert(response != NULL); + + control = stasis_app_control_find_by_channel_id(channel_id); + if (control == NULL) { + /* Distinguish between 404 and 409 errors */ + RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup); + chan = ast_channel_get_by_name(channel_id); + if (chan == NULL) { + stasis_http_response_error(response, 404, "Not Found", + "Channel not found"); + return NULL; + } + + stasis_http_response_error(response, 409, "Conflict", + "Channel not in Stasis application"); + return NULL; + } + + ao2_ref(control, +1); + return control; +} + +void stasis_http_dial(struct ast_variable *headers, struct ast_dial_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_dial\n"); +} + +void stasis_http_continue_in_dialplan( + struct ast_variable *headers, + struct ast_continue_in_dialplan_args *args, + struct stasis_http_response *response) +{ + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + + ast_assert(response != NULL); + + control = find_control(response, args->channel_id); + if (control == NULL) { + return; + } + + stasis_app_control_continue(control); + stasis_http_response_no_content(response); +} + +void stasis_http_answer_channel(struct ast_variable *headers, + struct ast_answer_channel_args *args, + struct stasis_http_response *response) +{ + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + + control = find_control(response, args->channel_id); + if (control == NULL) { + return; + } + + if (stasis_app_control_answer(control) != 0) { + stasis_http_response_error( + response, 500, "Internal Server Error", + "Failed to answer channel"); + return; + } + + stasis_http_response_no_content(response); +} + +void stasis_http_mute_channel(struct ast_variable *headers, struct ast_mute_channel_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_mute_channel\n"); +} +void stasis_http_unmute_channel(struct ast_variable *headers, struct ast_unmute_channel_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_unmute_channel\n"); +} +void stasis_http_hold_channel(struct ast_variable *headers, struct ast_hold_channel_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_hold_channel\n"); +} +void stasis_http_unhold_channel(struct ast_variable *headers, struct ast_unhold_channel_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_unhold_channel\n"); +} +void stasis_http_play_on_channel(struct ast_variable *headers, struct ast_play_on_channel_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_play_on_channel\n"); +} +void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_record_channel\n"); +} +void stasis_http_get_channel(struct ast_variable *headers, + struct ast_get_channel_args *args, + struct stasis_http_response *response) +{ + RAII_VAR(struct stasis_caching_topic *, caching_topic, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + struct ast_channel_snapshot *snapshot; + + caching_topic = ast_channel_topic_all_cached(); + if (!caching_topic) { + stasis_http_response_error( + response, 500, "Internal Server Error", + "Message bus not initialized"); + return; + } + ao2_ref(caching_topic, +1); + + msg = stasis_cache_get(caching_topic, ast_channel_snapshot_type(), + args->channel_id); + if (!msg) { + stasis_http_response_error( + response, 404, "Not Found", + "Channel not found"); + return; + } + + snapshot = stasis_message_data(msg); + ast_assert(snapshot != NULL); + + stasis_http_response_ok(response, + ast_channel_snapshot_to_json(snapshot)); +} + +void stasis_http_delete_channel(struct ast_variable *headers, + struct ast_delete_channel_args *args, + struct stasis_http_response *response) +{ + RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup); + + chan = ast_channel_get_by_name(args->channel_id); + if (chan == NULL) { + stasis_http_response_error( + response, 404, "Not Found", + "Channel not found"); + return; + } + + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + + stasis_http_response_no_content(response); +} + +void stasis_http_get_channels(struct ast_variable *headers, + struct ast_get_channels_args *args, + struct stasis_http_response *response) +{ + RAII_VAR(struct stasis_caching_topic *, caching_topic, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, snapshots, NULL, ao2_cleanup); + RAII_VAR(struct ast_json *, json, NULL, ast_json_unref); + struct ao2_iterator i; + void *obj; + + caching_topic = ast_channel_topic_all_cached(); + if (!caching_topic) { + stasis_http_response_error( + response, 500, "Internal Server Error", + "Message bus not initialized"); + return; + } + ao2_ref(caching_topic, +1); + + snapshots = stasis_cache_dump(caching_topic, ast_channel_snapshot_type()); + if (!snapshots) { + stasis_http_response_alloc_failed(response); + return; + } + + json = ast_json_array_create(); + if (!json) { + stasis_http_response_alloc_failed(response); + return; + } + + i = ao2_iterator_init(snapshots, 0); + while ((obj = ao2_iterator_next(&i))) { + RAII_VAR(struct stasis_message *, msg, obj, ao2_cleanup); + struct ast_channel_snapshot *snapshot = stasis_message_data(msg); + int r = ast_json_array_append( + json, ast_channel_snapshot_to_json(snapshot)); + if (r != 0) { + stasis_http_response_alloc_failed(response); + return; + } + } + ao2_iterator_destroy(&i); + + stasis_http_response_ok(response, ast_json_ref(json)); +} + +void stasis_http_originate(struct ast_variable *headers, + struct ast_originate_args *args, + struct stasis_http_response *response) +{ + if (args->endpoint) { + ast_log(LOG_DEBUG, "Dialing specific endpoint %s\n", args->endpoint); + } + + ast_log(LOG_DEBUG, "Dialing %s@%s\n", args->extension, args->context); + /* ast_pbx_outgoing_app - originates a channel, putting it into an application */ +} diff --git a/res/stasis_http/resource_channels.h b/res/stasis_http/resource_channels.h new file mode 100644 index 000000000..2c78589b7 --- /dev/null +++ b/res/stasis_http/resource_channels.h @@ -0,0 +1,244 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_channels.c + * + * Channel resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_CHANNELS_H +#define _ASTERISK_RESOURCE_CHANNELS_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_channels() */ +struct ast_get_channels_args { +}; +/*! + * \brief List active channels. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_channels(struct ast_variable *headers, struct ast_get_channels_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_originate() */ +struct ast_originate_args { + /*! \brief Endpoint to call. If not specified, originate is routed via dialplan */ + const char *endpoint; + /*! \brief Extension to dial */ + const char *extension; + /*! \brief When routing via dialplan, the context use. If omitted, uses 'default' */ + const char *context; +}; +/*! + * \brief Create a new channel (originate). + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_originate(struct ast_variable *headers, struct ast_originate_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_channel() */ +struct ast_get_channel_args { + /*! \brief Channel's id */ + const char *channel_id; +}; +/*! + * \brief Channel details. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_channel(struct ast_variable *headers, struct ast_get_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_delete_channel() */ +struct ast_delete_channel_args { + /*! \brief Channel's id */ + const char *channel_id; +}; +/*! + * \brief Delete (i.e. hangup) a channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_delete_channel(struct ast_variable *headers, struct ast_delete_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_dial() */ +struct ast_dial_args { + /*! \brief Channel's id */ + const char *channel_id; + /*! \brief Endpoint to call. If not specified, dial is routed via dialplan */ + const char *endpoint; + /*! \brief Extension to dial */ + const char *extension; + /*! \brief When routing via dialplan, the context use. If omitted, uses 'default' */ + const char *context; +}; +/*! + * \brief Create a new channel (originate) and bridge to this channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_dial(struct ast_variable *headers, struct ast_dial_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_continue_in_dialplan() */ +struct ast_continue_in_dialplan_args { + /*! \brief Channel's id */ + const char *channel_id; +}; +/*! + * \brief Exit application; continue execution in the dialplan. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_continue_in_dialplan(struct ast_variable *headers, struct ast_continue_in_dialplan_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_answer_channel() */ +struct ast_answer_channel_args { + /*! \brief Channel's id */ + const char *channel_id; +}; +/*! + * \brief Answer a channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_answer_channel(struct ast_variable *headers, struct ast_answer_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_mute_channel() */ +struct ast_mute_channel_args { + /*! \brief Channel's id */ + const char *channel_id; + /*! \brief Direction in which to mute audio */ + const char *direction; +}; +/*! + * \brief Mute a channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_mute_channel(struct ast_variable *headers, struct ast_mute_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_unmute_channel() */ +struct ast_unmute_channel_args { + /*! \brief Channel's id */ + const char *channel_id; + /*! \brief Direction in which to unmute audio */ + const char *direction; +}; +/*! + * \brief Unmute a channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_unmute_channel(struct ast_variable *headers, struct ast_unmute_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_hold_channel() */ +struct ast_hold_channel_args { + /*! \brief Channel's id */ + const char *channel_id; +}; +/*! + * \brief Hold a channel. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_hold_channel(struct ast_variable *headers, struct ast_hold_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_unhold_channel() */ +struct ast_unhold_channel_args { + /*! \brief Channel's id */ + const char *channel_id; +}; +/*! + * \brief Remove a channel from hold. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_unhold_channel(struct ast_variable *headers, struct ast_unhold_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_play_on_channel() */ +struct ast_play_on_channel_args { + /*! \brief Channel's id */ + const char *channel_id; + /*! \brief Media's URI to play. */ + const char *media; +}; +/*! + * \brief Start playback of media. + * + * The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.) + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_play_on_channel(struct ast_variable *headers, struct ast_play_on_channel_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_record_channel() */ +struct ast_record_channel_args { + /*! \brief Channel's id */ + const char *channel_id; + /*! \brief Recording's filename */ + const char *name; + /*! \brief Format to encode audio in */ + const char *format; + /*! \brief Maximum duration of the recording, in seconds. 0 for no limit */ + int max_duration_seconds; + /*! \brief Maximum duration of silence, in seconds. 0 for no limit */ + int max_silence_seconds; + /*! \brief If true, and recording already exists, append to recording */ + int append; + /*! \brief Play beep when recording begins */ + int beep; + /*! \brief DTMF input to terminate recording */ + const char *terminate_on; +}; +/*! + * \brief Start a recording. + * + * Record audio from a channel. Note that this will not capture audio sent to the channel. The bridge itself has a record feature if that's what you want. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_record_channel(struct ast_variable *headers, struct ast_record_channel_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_CHANNELS_H */ diff --git a/res/stasis_http/resource_endpoints.c b/res/stasis_http/resource_endpoints.c new file mode 100644 index 000000000..b2611bad8 --- /dev/null +++ b/res/stasis_http/resource_endpoints.c @@ -0,0 +1,43 @@ +/* -*- C -*- + * 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 Implementation for stasis-http stubs. + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_endpoints.h" + +void stasis_http_get_endpoint(struct ast_variable *headers, struct ast_get_endpoint_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_endpoint\n"); +} +void stasis_http_get_endpoints(struct ast_variable *headers, struct ast_get_endpoints_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_endpoints\n"); +} diff --git a/res/stasis_http/resource_endpoints.h b/res/stasis_http/resource_endpoints.h new file mode 100644 index 000000000..57f4b91ba --- /dev/null +++ b/res/stasis_http/resource_endpoints.h @@ -0,0 +1,69 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_endpoints.c + * + * Endpoint resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_ENDPOINTS_H +#define _ASTERISK_RESOURCE_ENDPOINTS_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_endpoints() */ +struct ast_get_endpoints_args { + /*! \brief Filter endpoints by type (sip,iax2,dhadi,...) */ + const char *with_type; +}; +/*! + * \brief List available endoints. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_endpoints(struct ast_variable *headers, struct ast_get_endpoints_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_endpoint() */ +struct ast_get_endpoint_args { + /*! \brief ID of the endpoint */ + const char *endpoint_id; +}; +/*! + * \brief Details for an endpoint. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_endpoint(struct ast_variable *headers, struct ast_get_endpoint_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */ diff --git a/res/stasis_http/resource_events.c b/res/stasis_http/resource_events.c new file mode 100644 index 000000000..34563fe6e --- /dev/null +++ b/res/stasis_http/resource_events.c @@ -0,0 +1,40 @@ +/* -*- C -*- + * 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 Implementation for stasis-http stubs. + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_events.h" + +void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response) +{ + /* TODO: This should promote this socket to a websocket connection */ + ast_log(LOG_ERROR, "TODO: stasis_http_event_websocket\n"); +} diff --git a/res/stasis_http/resource_events.h b/res/stasis_http/resource_events.h new file mode 100644 index 000000000..0f58476df --- /dev/null +++ b/res/stasis_http/resource_events.h @@ -0,0 +1,58 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_events.c + * + * WebSocket resource + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_EVENTS_H +#define _ASTERISK_RESOURCE_EVENTS_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_event_websocket() */ +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 + */ +void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_EVENTS_H */ diff --git a/res/stasis_http/resource_playback.c b/res/stasis_http/resource_playback.c new file mode 100644 index 000000000..99f2e09eb --- /dev/null +++ b/res/stasis_http/resource_playback.c @@ -0,0 +1,43 @@ +/* + * 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 /api-docs/playback.{format} implementation- Playback control resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_playback.h" + +void stasis_http_get_playback(struct ast_variable *headers, struct ast_get_playback_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_playback\n"); +} +void stasis_http_stop_playback(struct ast_variable *headers, struct ast_stop_playback_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_stop_playback\n"); +} +void stasis_http_control_playback(struct ast_variable *headers, struct ast_control_playback_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_control_playback\n"); +} diff --git a/res/stasis_http/resource_playback.h b/res/stasis_http/resource_playback.h new file mode 100644 index 000000000..36b05bc04 --- /dev/null +++ b/res/stasis_http/resource_playback.h @@ -0,0 +1,84 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_playback.c + * + * Playback control resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_PLAYBACK_H +#define _ASTERISK_RESOURCE_PLAYBACK_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_playback() */ +struct ast_get_playback_args { + /*! \brief Playback's id */ + const char *playback_id; +}; +/*! + * \brief Get a playback's details. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_playback(struct ast_variable *headers, struct ast_get_playback_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_stop_playback() */ +struct ast_stop_playback_args { + /*! \brief Playback's id */ + const char *playback_id; +}; +/*! + * \brief Stop a playback. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_stop_playback(struct ast_variable *headers, struct ast_stop_playback_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_control_playback() */ +struct ast_control_playback_args { + /*! \brief Playback's id */ + const char *playback_id; + /*! \brief Operation to perform on the playback. */ + const char *operation; +}; +/*! + * \brief Get a playback's details. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_control_playback(struct ast_variable *headers, struct ast_control_playback_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_PLAYBACK_H */ diff --git a/res/stasis_http/resource_recordings.c b/res/stasis_http/resource_recordings.c new file mode 100644 index 000000000..2400a6876 --- /dev/null +++ b/res/stasis_http/resource_recordings.c @@ -0,0 +1,79 @@ +/* + * 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 /api-docs/recordings.{format} implementation- Recording resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_recordings.h" + +void stasis_http_get_recordings(struct ast_variable *headers, struct ast_get_recordings_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_recordings\n"); +} +void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_recordings\n"); +} +void stasis_http_get_stored_recording(struct ast_variable *headers, struct ast_get_stored_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_recording\n"); +} +void stasis_http_delete_stored_recording(struct ast_variable *headers, struct ast_delete_stored_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_delete_stored_recording\n"); +} +void stasis_http_get_live_recordings(struct ast_variable *headers, struct ast_get_live_recordings_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_live_recordings\n"); +} +void stasis_http_get_live_recording(struct ast_variable *headers, struct ast_get_live_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_live_recording\n"); +} +void stasis_http_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_cancel_recording\n"); +} +void stasis_http_stop_recording(struct ast_variable *headers, struct ast_stop_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_stop_recording\n"); +} +void stasis_http_pause_recording(struct ast_variable *headers, struct ast_pause_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_pause_recording\n"); +} +void stasis_http_unpause_recording(struct ast_variable *headers, struct ast_unpause_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_unpause_recording\n"); +} +void stasis_http_mute_recording(struct ast_variable *headers, struct ast_mute_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_mute_recording\n"); +} +void stasis_http_unmute_recording(struct ast_variable *headers, struct ast_unmute_recording_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_unmute_recording\n"); +} diff --git a/res/stasis_http/resource_recordings.h b/res/stasis_http/resource_recordings.h new file mode 100644 index 000000000..ee48e43b7 --- /dev/null +++ b/res/stasis_http/resource_recordings.h @@ -0,0 +1,193 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_recordings.c + * + * Recording resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_RECORDINGS_H +#define _ASTERISK_RESOURCE_RECORDINGS_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_recordings() */ +struct ast_get_recordings_args { +}; +/*! + * \brief List all recordings. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_recordings(struct ast_variable *headers, struct ast_get_recordings_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_stored_recordings() */ +struct ast_get_stored_recordings_args { +}; +/*! + * \brief List recordings that are complete. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_stored_recordings(struct ast_variable *headers, struct ast_get_stored_recordings_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_stored_recording() */ +struct ast_get_stored_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Get a stored recording's details. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_stored_recording(struct ast_variable *headers, struct ast_get_stored_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_delete_stored_recording() */ +struct ast_delete_stored_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Delete a stored recording. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_delete_stored_recording(struct ast_variable *headers, struct ast_delete_stored_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_live_recordings() */ +struct ast_get_live_recordings_args { +}; +/*! + * \brief List libe recordings. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_live_recordings(struct ast_variable *headers, struct ast_get_live_recordings_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_live_recording() */ +struct ast_get_live_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief List live recordings. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_live_recording(struct ast_variable *headers, struct ast_get_live_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_cancel_recording() */ +struct ast_cancel_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Stop a live recording and discard it. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_cancel_recording(struct ast_variable *headers, struct ast_cancel_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_stop_recording() */ +struct ast_stop_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Stop a live recording and store it. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_stop_recording(struct ast_variable *headers, struct ast_stop_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_pause_recording() */ +struct ast_pause_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Pause a live recording. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_pause_recording(struct ast_variable *headers, struct ast_pause_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_unpause_recording() */ +struct ast_unpause_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Unpause a live recording. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_unpause_recording(struct ast_variable *headers, struct ast_unpause_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_mute_recording() */ +struct ast_mute_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Mute a live recording. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_mute_recording(struct ast_variable *headers, struct ast_mute_recording_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_unmute_recording() */ +struct ast_unmute_recording_args { + /*! \brief Recording's id */ + const char *recording_id; +}; +/*! + * \brief Unmute a live recording. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_unmute_recording(struct ast_variable *headers, struct ast_unmute_recording_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_RECORDINGS_H */ diff --git a/res/stasis_http/resource_sounds.c b/res/stasis_http/resource_sounds.c new file mode 100644 index 000000000..a1808a159 --- /dev/null +++ b/res/stasis_http/resource_sounds.c @@ -0,0 +1,39 @@ +/* + * 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 /api-docs/sounds.{format} implementation- Sound resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_sounds.h" + +void stasis_http_get_sounds(struct ast_variable *headers, struct ast_get_sounds_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_sounds\n"); +} +void stasis_http_get_stored_sound(struct ast_variable *headers, struct ast_get_stored_sound_args *args, struct stasis_http_response *response) +{ + ast_log(LOG_ERROR, "TODO: stasis_http_get_stored_sound\n"); +} diff --git a/res/stasis_http/resource_sounds.h b/res/stasis_http/resource_sounds.h new file mode 100644 index 000000000..f3010a920 --- /dev/null +++ b/res/stasis_http/resource_sounds.h @@ -0,0 +1,69 @@ +/* + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_sounds.c + * + * Sound resources + * + * \author David M. Lee, II <dlee@digium.com> + */ + +/* + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_SOUNDS_H +#define _ASTERISK_RESOURCE_SOUNDS_H + +#include "asterisk/stasis_http.h" + +/*! \brief Argument struct for stasis_http_get_sounds() */ +struct ast_get_sounds_args { + const char *lang; + const char *format; +}; +/*! + * \brief List all sounds. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_sounds(struct ast_variable *headers, struct ast_get_sounds_args *args, struct stasis_http_response *response); +/*! \brief Argument struct for stasis_http_get_stored_sound() */ +struct ast_get_stored_sound_args { + /*! \brief Sound's id */ + const char *sound_id; +}; +/*! + * \brief Get a sound's details. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void stasis_http_get_stored_sound(struct ast_variable *headers, struct ast_get_stored_sound_args *args, struct stasis_http_response *response); + +#endif /* _ASTERISK_RESOURCE_SOUNDS_H */ diff --git a/rest-api-templates/README.txt b/rest-api-templates/README.txt new file mode 100644 index 000000000..e927ad768 --- /dev/null +++ b/rest-api-templates/README.txt @@ -0,0 +1,15 @@ +This directory contains templates and template processing code for generating +HTTP bindings for the RESTful API's. + +The RESTful API's are declared using [Swagger][swagger]. While Swagger provides +a [code generating toolkit][swagger-codegen], it requires Java to run, which +would be an unusual dependency to require for Asterisk developers. + +This code generator is similar, but written in Python. Templates are processed +by using [pystache][pystache], which is a fairly simply Python implementation of +[mustache][mustache]. + + [swagger]: https://github.com/wordnik/swagger-core/wiki + [swagger-codegen]: https://github.com/wordnik/swagger-codegen + [pystache]: https://github.com/defunkt/pystache + [mustache]: http://mustache.github.io/ diff --git a/rest-api-templates/asterisk_processor.py b/rest-api-templates/asterisk_processor.py new file mode 100644 index 000000000..81aefbb39 --- /dev/null +++ b/rest-api-templates/asterisk_processor.py @@ -0,0 +1,179 @@ +# +# 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. +# + +"""Implementation of SwaggerPostProcessor which adds fields needed to generate +Asterisk RESTful HTTP binding code. +""" + +import re + +from swagger_model import * + + +def simple_name(name): + """Removes the {markers} from a path segement. + + @param name: Swagger path segement, with {pathVar} markers. + """ + if name.startswith('{') and name.endswith('}'): + return name[1:-1] + return name + + +def snakify(name): + """Helper to take a camelCase or dash-seperated name and make it + snake_case. + """ + r = '' + prior_lower = False + for c in name: + if c.isupper() and prior_lower: + r += "_" + if c is '-': + c = '_' + prior_lower = c.islower() + r += c.lower() + return r + + +class PathSegment(Stringify): + """Tree representation of a Swagger API declaration. + """ + def __init__(self, name, parent): + """Ctor. + + @param name: Name of this path segment. May have {pathVar} markers. + @param parent: Parent PathSegment. + """ + #: Segment name, with {pathVar} markers removed + self.name = simple_name(name) + #: True if segment is a {pathVar}, else None. + self.is_wildcard = None + #: Underscore seperated name all ancestor segments + self.full_name = None + #: Dictionary of child PathSegements + self.__children = OrderedDict() + #: List of operations on this segement + self.operations = [] + + if self.name != name: + self.is_wildcard = True + + if not self.name: + assert(not parent) + self.full_name = '' + if not parent or not parent.name: + self.full_name = name + else: + self.full_name = "%s_%s" % (parent.full_name, self.name) + + def get_child(self, path): + """Walks decendents to get path, creating it if necessary. + + @param path: List of path names. + @return: PageSegment corresponding to path. + """ + assert simple_name(path[0]) == self.name + if (len(path) == 1): + return self + child = self.__children.get(path[1]) + if not child: + child = PathSegment(path[1], self) + self.__children[path[1]] = child + return child.get_child(path[1:]) + + def children(self): + """Gets list of children. + """ + return self.__children.values() + + def num_children(self): + """Gets count of children. + """ + return len(self.__children) + + +class AsteriskProcessor(SwaggerPostProcessor): + """A SwaggerPostProcessor which adds fields needed to generate Asterisk + RESTful HTTP binding code. + """ + + #: How Swagger types map to C. + type_mapping = { + 'string': 'const char *', + 'boolean': 'int', + 'number': 'int', + 'int': 'int', + 'long': 'long', + 'double': 'double', + 'float': 'float', + } + + #: String conversion functions for string to C type. + convert_mapping = { + 'const char *': '', + 'int': 'atoi', + 'long': 'atol', + 'double': 'atof', + } + + def process_api(self, resource_api, context): + # Derive a resource name from the API declaration's filename + resource_api.name = re.sub('\..*', '', + os.path.basename(resource_api.path)) + # Now in all caps, from include guard + resource_api.name_caps = resource_api.name.upper() + # Construct the PathSegement tree for the API. + if resource_api.api_declaration: + resource_api.root_path = PathSegment('', None) + for api in resource_api.api_declaration.apis: + segment = resource_api.root_path.get_child(api.path.split('/')) + for operation in api.operations: + segment.operations.append(operation) + # Since every API path should start with /[resource], root should + # have exactly one child. + if resource_api.root_path.num_children() != 1: + raise SwaggerError( + "Should not mix resources in one API declaration", context) + # root_path isn't needed any more + resource_api.root_path = resource_api.root_path.children()[0] + if resource_api.name != resource_api.root_path.name: + raise SwaggerError( + "API declaration name should match", context) + resource_api.root_full_name = resource_api.root_path.full_name + + def process_operation(self, operation, context): + # Nicknames are camelcase, Asterisk coding is snake case + operation.c_nickname = snakify(operation.nickname) + operation.c_http_method = 'AST_HTTP_' + operation.http_method + if not operation.summary.endswith("."): + raise SwaggerError("Summary should end with .", context) + + def process_parameter(self, parameter, context): + if not parameter.data_type in self.type_mapping: + raise SwaggerError( + "Invalid parameter type %s" % paramter.data_type, context) + # Parameter names are camelcase, Asterisk convention is snake case + parameter.c_name = snakify(parameter.name) + parameter.c_data_type = self.type_mapping[parameter.data_type] + parameter.c_convert = self.convert_mapping[parameter.c_data_type] + # You shouldn't put a space between 'char *' and the variable + if parameter.c_data_type.endswith('*'): + parameter.c_space = '' + else: + parameter.c_space = ' ' diff --git a/rest-api-templates/do-not-edit.mustache b/rest-api-templates/do-not-edit.mustache new file mode 100644 index 000000000..05ba14276 --- /dev/null +++ b/rest-api-templates/do-not-edit.mustache @@ -0,0 +1,4 @@ +{{! A partial for the big warning, so it's not in the template itself }} + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!! DO NOT EDIT !!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/rest-api-templates/make_stasis_http_stubs.py b/rest-api-templates/make_stasis_http_stubs.py new file mode 100755 index 000000000..1ec7c5c91 --- /dev/null +++ b/rest-api-templates/make_stasis_http_stubs.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# 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. +# + +try: + import pystache +except ImportError: + print >> sys.stderr, "Pystache required. Please sudo pip install pystache." + +import os.path +import pystache +import sys + +from asterisk_processor import AsteriskProcessor +from optparse import OptionParser +from swagger_model import * +from transform import Transform + +TOPDIR = os.path.dirname(os.path.abspath(__file__)) + + +def rel(file): + """Helper to get a file relative to the script's directory + + @parm file: Relative file path. + """ + return os.path.join(TOPDIR, file) + +API_TRANSFORMS = [ + Transform(rel('res_stasis_http_resource.c.mustache'), + 'res_stasis_http_{{name}}.c'), + Transform(rel('stasis_http_resource.h.mustache'), + 'stasis_http/resource_{{name}}.h'), + Transform(rel('stasis_http_resource.c.mustache'), + 'stasis_http/resource_{{name}}.c', False), +] + +RESOURCES_TRANSFORMS = [ + Transform(rel('stasis_http.make.mustache'), 'stasis_http.make'), +] + + +def main(argv): + parser = OptionParser(usage="Usage %prog [resources.json] [destdir]") + + (options, args) = parser.parse_args(argv) + + if len(args) != 3: + parser.error("Wrong number of arguments") + + source = args[1] + dest_dir = args[2] + renderer = pystache.Renderer(search_dirs=[TOPDIR], missing_tags='strict') + processor = AsteriskProcessor() + + # Build the models + base_dir = os.path.dirname(source) + resources = ResourceListing().load_file(source, processor) + for api in resources.apis: + api.load_api_declaration(base_dir, processor) + + # Render the templates + for api in resources.apis: + for transform in API_TRANSFORMS: + transform.render(renderer, api, dest_dir) + for transform in RESOURCES_TRANSFORMS: + transform.render(renderer, resources, dest_dir) + +if __name__ == "__main__": + sys.exit(main(sys.argv) or 0) diff --git a/rest-api-templates/odict.py b/rest-api-templates/odict.py new file mode 100644 index 000000000..8f536a2b8 --- /dev/null +++ b/rest-api-templates/odict.py @@ -0,0 +1,261 @@ +# Downloaded from http://code.activestate.com/recipes/576693/ +# Licensed under the MIT License + +# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. +# Passes Python2.7's test suite and incorporates all the latest updates. + +try: + from thread import get_ident as _get_ident +except ImportError: + from dummy_thread import get_ident as _get_ident + +try: + from _abcoll import KeysView, ValuesView, ItemsView +except ImportError: + pass + + +class OrderedDict(dict): + 'Dictionary that remembers insertion order' + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as for regular dictionaries. + + # The internal self.__map dictionary maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # Each link is stored as a list of length three: [PREV, NEXT, KEY]. + + def __init__(self, *args, **kwds): + '''Initialize an ordered dictionary. Signature is the same as for + regular dictionaries, but keyword arguments are not recommended + because their insertion order is arbitrary. + + ''' + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__root + except AttributeError: + self.__root = root = [] # sentinel node + root[:] = [root, root, None] + self.__map = {} + self.__update(*args, **kwds) + + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link which goes at the end of the linked + # list, and the inherited dictionary is updated with the new key/value pair. + if key not in self: + root = self.__root + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which is + # then removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link_prev, link_next, key = self.__map.pop(key) + link_prev[1] = link_next + link_next[0] = link_prev + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + root = self.__root + curr = root[1] + while curr is not root: + yield curr[2] + curr = curr[1] + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + root = self.__root + curr = root[0] + while curr is not root: + yield curr[2] + curr = curr[0] + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + try: + for node in self.__map.itervalues(): + del node[:] + root = self.__root + root[:] = [root, root, None] + self.__map.clear() + except AttributeError: + pass + dict.clear(self) + + def popitem(self, last=True): + '''od.popitem() -> (k, v), return and remove a (key, value) pair. + Pairs are returned in LIFO order if last is true or FIFO order if false. + + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root[0] + link_prev = link[0] + link_prev[1] = root + root[0] = link_prev + else: + link = root[1] + link_next = link[1] + root[1] = link_next + link_next[0] = root + key = link[2] + del self.__map[key] + value = dict.pop(self, key) + return key, value + + # -- the following methods do not depend on the internal structure -- + + def keys(self): + 'od.keys() -> list of keys in od' + return list(self) + + def values(self): + 'od.values() -> list of values in od' + return [self[key] for key in self] + + def items(self): + 'od.items() -> list of (key, value) pairs in od' + return [(key, self[key]) for key in self] + + def iterkeys(self): + 'od.iterkeys() -> an iterator over the keys in od' + return iter(self) + + def itervalues(self): + 'od.itervalues -> an iterator over the values in od' + for k in self: + yield self[k] + + def iteritems(self): + 'od.iteritems -> an iterator over the (key, value) items in od' + for k in self: + yield (k, self[k]) + + def update(*args, **kwds): + '''od.update(E, **F) -> None. Update od from dict/iterable E and F. + + If E is a dict instance, does: for k in E: od[k] = E[k] + If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] + Or if E is an iterable of items, does: for k, v in E: od[k] = v + In either case, this is followed by: for k, v in F.items(): od[k] = v + + ''' + if len(args) > 2: + raise TypeError('update() takes at most 2 positional ' + 'arguments (%d given)' % (len(args),)) + elif not args: + raise TypeError('update() takes at least 1 argument (0 given)') + self = args[0] + # Make progressively weaker assumptions about "other" + other = () + if len(args) == 2: + other = args[1] + if isinstance(other, dict): + for key in other: + self[key] = other[key] + elif hasattr(other, 'keys'): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + __update = update # let subclasses override update without breaking __init__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' + if key in self: + return self[key] + self[key] = default + return default + + def __repr__(self, _repr_running={}): + 'od.__repr__() <==> repr(od)' + call_key = id(self), _get_ident() + if call_key in _repr_running: + return '...' + _repr_running[call_key] = 1 + try: + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + finally: + del _repr_running[call_key] + + def __reduce__(self): + 'Return state information for pickling' + items = [[k, self[k]] for k in self] + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S + and values equal to v (which defaults to None). + + ''' + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return len(self)==len(other) and self.items() == other.items() + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other + + # -- the following methods are only used in Python 2.7 -- + + def viewkeys(self): + "od.viewkeys() -> a set-like object providing a view on od's keys" + return KeysView(self) + + def viewvalues(self): + "od.viewvalues() -> an object providing a view on od's values" + return ValuesView(self) + + def viewitems(self): + "od.viewitems() -> a set-like object providing a view on od's items" + return ItemsView(self) diff --git a/rest-api-templates/res_stasis_http_resource.c.mustache b/rest-api-templates/res_stasis_http_resource.c.mustache new file mode 100644 index 000000000..b02ab62bd --- /dev/null +++ b/rest-api-templates/res_stasis_http_resource.c.mustache @@ -0,0 +1,116 @@ +{{#api_declaration}} +/* + * Asterisk -- An open source telephony toolkit. + * + * {{{copyright}}} + * + * {{{author}}} +{{! Template Copyright + * 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. + */ + +{{! Template for rendering the res_ module for an HTTP resource. }} +/* +{{> do-not-edit}} + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/res_stasis_http_resource.c.mustache + */ + +/*! \file + * + * \brief {{{description}}} + * + * \author {{{author}}} + */ + +/*** MODULEINFO + <depend type="module">res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "stasis_http/resource_{{name}}.h" + +{{#apis}} +{{#operations}} +/*! + * \brief Parameter parsing callback for {{path}}. + * \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_{{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}} + stasis_http_{{c_nickname}}(headers, &args, response); +} +{{/operations}} +{{/apis}} + +{{! The rest_handler partial expands to the tree of stasis_rest_handlers }} +{{#root_path}} +{{> rest_handler}} +{{/root_path}} + +static int load_module(void) +{ + return stasis_http_add_handler(&{{root_full_name}}); +} + +static int unload_module(void) +{ + stasis_http_remove_handler(&{{root_full_name}}); + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, + "RESTful API module - {{{description}}}", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); +{{/api_declaration}} diff --git a/rest-api-templates/rest_handler.mustache b/rest-api-templates/rest_handler.mustache new file mode 100644 index 000000000..a7dfc60e8 --- /dev/null +++ b/rest-api-templates/rest_handler.mustache @@ -0,0 +1,38 @@ +{{! -*- 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. +}} +{{! + * Recursive partial template to render a rest_handler. Used in + * res_stasis_http_resource.c.mustache. +}} +{{#children}} +{{> rest_handler}} +{{/children}} +/*! \brief REST handler for {{path}} */ +static struct stasis_rest_handlers {{full_name}} = { + .path_segment = "{{name}}", +{{#is_wildcard}} + .is_wildcard = 1, +{{/is_wildcard}} + .callbacks = { +{{#operations}} + [{{c_http_method}}] = stasis_http_{{c_nickname}}_cb, +{{/operations}} + }, + .num_children = {{num_children}}, + .children = { {{#children}}&{{full_name}},{{/children}} } +}; diff --git a/rest-api-templates/stasis_http.make.mustache b/rest-api-templates/stasis_http.make.mustache new file mode 100644 index 000000000..103eb2bff --- /dev/null +++ b/rest-api-templates/stasis_http.make.mustache @@ -0,0 +1,26 @@ +{{! -*- Makefile -*- }} +# +# Asterisk -- A telephony toolkit for Linux. +# +# Generated Makefile for res_stasis_http dependencies. +# +# Copyright (C) 2013, Digium, Inc. +# +# This program is free software, distributed under the terms of +# the GNU General Public License +# + +# +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !!!!! DO NOT EDIT !!!!! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# This file is generated by a template. Please see the original template at +# rest-api-templates/stasis_http.make.mustache +# + +{{#apis}} +res_stasis_http_{{name}}.so: stasis_http/resource_{{name}}.o + +stasis_http/resource_{{name}}.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_{{name}}) + +{{/apis}} diff --git a/rest-api-templates/stasis_http_resource.c.mustache b/rest-api-templates/stasis_http_resource.c.mustache new file mode 100644 index 000000000..7a5535511 --- /dev/null +++ b/rest-api-templates/stasis_http_resource.c.mustache @@ -0,0 +1,41 @@ +{{#api_declaration}} +/* + * Asterisk -- An open source telephony toolkit. + * + * {{{copyright}}} + * + * {{{author}}} + * + * 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 {{{resource_path}}} implementation- {{{description}}} + * + * \author {{{author}}} + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "resource_{{name}}.h" + +{{#apis}} +{{#operations}} +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"); +} +{{/operations}} +{{/apis}} +{{/api_declaration}} diff --git a/rest-api-templates/stasis_http_resource.h.mustache b/rest-api-templates/stasis_http_resource.h.mustache new file mode 100644 index 000000000..6e7af1648 --- /dev/null +++ b/rest-api-templates/stasis_http_resource.h.mustache @@ -0,0 +1,68 @@ +{{#api_declaration}} +/* + * Asterisk -- An open source telephony toolkit. + * + * {{{copyright}}} + * + * {{{author}}} + * + * 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 Generated file - declares stubs to be implemented in + * res/stasis_http/resource_{{name}}.c + * + * {{{description}}} + * + * \author {{{author}}} + */ + +/* +{{> do-not-edit}} + * This file is generated by a mustache template. Please see the original + * template in rest-api-templates/stasis_http_resource.h.mustache + */ + +#ifndef _ASTERISK_RESOURCE_{{name_caps}}_H +#define _ASTERISK_RESOURCE_{{name_caps}}_H + +#include "asterisk/stasis_http.h" + +{{#apis}} +{{#operations}} +/*! \brief Argument struct for stasis_http_{{c_nickname}}() */ +struct ast_{{c_nickname}}_args { +{{#parameters}} +{{#description}} + /*! \brief {{{description}}} */ +{{/description}} + {{c_data_type}}{{c_space}}{{c_name}}; +{{/parameters}} +}; +/*! + * \brief {{summary}} +{{#notes}} + * + * {{{notes}}} +{{/notes}} + * + * \param headers HTTP headers + * \param args Swagger parameters + * \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); +{{/operations}} +{{/apis}} + +#endif /* _ASTERISK_RESOURCE_{{name_caps}}_H */ +{{/api_declaration}} diff --git a/rest-api-templates/swagger_model.py b/rest-api-templates/swagger_model.py new file mode 100644 index 000000000..c42bb7086 --- /dev/null +++ b/rest-api-templates/swagger_model.py @@ -0,0 +1,482 @@ + +# 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. +# + +"""Swagger data model objects. + +These objects should map directly to the Swagger api-docs, without a lot of +additional fields. In the process of translation, it should also validate the +model for consistency against the Swagger spec (i.e., fail if fields are +missing, or have incorrect values). + +See https://github.com/wordnik/swagger-core/wiki/API-Declaration for the spec. +""" + +import json +import os.path +import pprint +import sys +import traceback + +try: + from collections import OrderedDict +except ImportError: + from odict import OrderedDict + + +SWAGGER_VERSION = "1.1" + + +class SwaggerError(Exception): + """Raised when an error is encountered mapping the JSON objects into the + model. + """ + + def __init__(self, msg, context, cause=None): + """Ctor. + + @param msg: String message for the error. + @param context: Array of strings for current context in the API. + @param cause: Optional exception that caused this one. + """ + super(Exception, self).__init__(msg, context, cause) + + +class SwaggerPostProcessor(object): + """Post processing interface for model objects. This processor can add + fields to model objects for additional information to use in the + templates. + """ + def process_api(self, resource_api, context): + """Post process a ResourceApi object. + + @param resource_api: ResourceApi object. + @param contect: Current context in the API. + """ + pass + + def process_operation(self, operation, context): + """Post process a Operation object. + + @param operation: Operation object. + @param contect: Current context in the API. + """ + pass + + def process_parameter(self, parameter, context): + """Post process a Parameter object. + + @param parameter: Parameter object. + @param contect: Current context in the API. + """ + pass + + +class Stringify(object): + """Simple mix-in to make the repr of the model classes more meaningful. + """ + def __repr__(self): + return "%s(%s)" % (self.__class__, pprint.saferepr(self.__dict__)) + + +class AllowableRange(Stringify): + """Model of a allowableValues of type RANGE + + See https://github.com/wordnik/swagger-core/wiki/datatypes#complex-types + """ + def __init__(self, min_value, max_value): + self.min_value = min_value + self.max_value = max_value + + +class AllowableList(Stringify): + """Model of a allowableValues of type LIST + + See https://github.com/wordnik/swagger-core/wiki/datatypes#complex-types + """ + def __init__(self, values): + self.values = values + + +def load_allowable_values(json, context): + """Parse a JSON allowableValues object. + + This returns None, AllowableList or AllowableRange, depending on the + valueType in the JSON. If the valueType is not recognized, a SwaggerError + is raised. + """ + if not json: + return None + + if not 'valueType' in json: + raise SwaggerError("Missing valueType field", context) + + value_type = json['valueType'] + + if value_type == 'RANGE': + if not 'min' in json: + raise SwaggerError("Missing field min", context) + if not 'max' in json: + raise SwaggerError("Missing field max", context) + return AllowableRange(json['min'], json['max']) + if value_type == 'LIST': + if not 'values' in json: + raise SwaggerError("Missing field values", context) + return AllowableList(json['values']) + raise SwaggerError("Unkown valueType %s" % value_type, context) + + +class Parameter(Stringify): + """Model of an operation's parameter. + + See https://github.com/wordnik/swagger-core/wiki/parameters + """ + + required_fields = ['name', 'paramType', 'dataType'] + + def __init__(self): + self.param_type = None + self.name = None + self.description = None + self.data_type = None + self.required = None + self.allowable_values = None + self.allow_multiple = None + + def load(self, parameter_json, processor, context): + context = add_context(context, parameter_json, 'name') + validate_required_fields(parameter_json, self.required_fields, context) + self.name = parameter_json.get('name') + self.param_type = parameter_json.get('paramType') + self.description = parameter_json.get('description') or '' + self.data_type = parameter_json.get('dataType') + self.required = parameter_json.get('required') or False + self.allowable_values = load_allowable_values( + parameter_json.get('allowableValues'), context) + self.allow_multiple = parameter_json.get('allowMultiple') or False + processor.process_parameter(self, context) + return self + + def is_type(self, other_type): + return self.param_type == other_type + + +class ErrorResponse(Stringify): + """Model of an error response. + + See https://github.com/wordnik/swagger-core/wiki/errors + """ + + required_fields = ['code', 'reason'] + + def __init__(self): + self.code = None + self.reason = None + + def load(self, err_json, processor, context): + context = add_context(context, err_json, 'code') + validate_required_fields(err_json, self.required_fields, context) + self.code = err_json.get('code') + self.reason = err_json.get('reason') + return self + + +class Operation(Stringify): + """Model of an operation on an API + + See https://github.com/wordnik/swagger-core/wiki/API-Declaration#apis + """ + + required_fields = ['httpMethod', 'nickname', 'responseClass', 'summary'] + + def __init__(self): + self.http_method = None + self.nickname = None + self.response_class = None + self.parameters = [] + self.summary = None + self.notes = None + self.error_responses = [] + + def load(self, op_json, processor, context): + context = add_context(context, op_json, 'nickname') + validate_required_fields(op_json, self.required_fields, context) + self.http_method = op_json.get('httpMethod') + self.nickname = op_json.get('nickname') + self.response_class = op_json.get('responseClass') + params_json = op_json.get('parameters') or [] + self.parameters = [ + Parameter().load(j, processor, context) for j in params_json] + self.query_parameters = [ + p for p in self.parameters if p.is_type('query')] + self.has_query_parameters = self.query_parameters and True + self.path_parameters = [ + p for p in self.parameters if p.is_type('path')] + self.has_path_parameters = self.path_parameters and True + self.header_parameters = [ + p for p in self.parameters if p.is_type('header')] + self.has_header_parameters = self.header_parameters and True + self.has_parameters = self.has_query_parameters or \ + self.has_path_parameters or self.has_header_parameters + self.summary = op_json.get('summary') + self.notes = op_json.get('notes') + err_json = op_json.get('errorResponses') or [] + self.error_responses = [ + ErrorResponse().load(j, processor, context) for j in err_json] + processor.process_operation(self, context) + return self + + +class Api(Stringify): + """Model of a single API in an API declaration. + + See https://github.com/wordnik/swagger-core/wiki/API-Declaration + """ + + required_fields = ['path', 'operations'] + + def __init__(self,): + self.path = None + self.description = None + self.operations = [] + + def load(self, api_json, processor, context): + context = add_context(context, api_json, 'path') + validate_required_fields(api_json, self.required_fields, context) + self.path = api_json.get('path') + self.description = api_json.get('description') + op_json = api_json.get('operations') + self.operations = [ + Operation().load(j, processor, context) for j in op_json] + return self + + +class Property(Stringify): + """Model of a Swagger property. + + See https://github.com/wordnik/swagger-core/wiki/datatypes + """ + + required_fields = ['type'] + + def __init__(self, name): + self.name = name + self.type = None + self.description = None + self.required = None + + def load(self, property_json, processor, context): + validate_required_fields(property_json, self.required_fields, context) + self.type = property_json.get('type') + self.description = property_json.get('description') or '' + self.required = property_json.get('required') or False + return self + + +class Model(Stringify): + """Model of a Swagger model. + + See https://github.com/wordnik/swagger-core/wiki/datatypes + """ + + def __init__(self): + self.id = None + self.properties = None + + def load(self, model_json, processor, context): + context = add_context(context, model_json, 'id') + self.id = model_json.get('id') + props = model_json.get('properties').items() or [] + self.properties = [ + Property(k).load(j, processor, context) for (k, j) in props] + return self + + +class ApiDeclaration(Stringify): + """Model class for an API Declaration. + + See https://github.com/wordnik/swagger-core/wiki/API-Declaration + """ + + required_fields = [ + 'swaggerVersion', '_author', '_copyright', 'apiVersion', 'basePath', + 'resourcePath', 'apis', 'models' + ] + + def __init__(self): + self.swagger_version = None + self.author = None + self.copyright = None + self.api_version = None + self.base_path = None + self.resource_path = None + self.apis = [] + self.models = [] + + def load_file(self, api_declaration_file, processor, context=[]): + context = context + [api_declaration_file] + try: + return self.__load_file(api_declaration_file, processor, context) + except SwaggerError: + raise + except Exception as e: + print >> sys.stderr, "Error: ", traceback.format_exc() + raise SwaggerError( + "Error loading %s" % api_declaration_file, context, e) + + def __load_file(self, api_declaration_file, processor, context): + with open(api_declaration_file) as fp: + self.load(json.load(fp), processor, context) + + expected_resource_path = '/api-docs/' + \ + os.path.basename(api_declaration_file) \ + .replace(".json", ".{format}") + + if self.resource_path != expected_resource_path: + print "%s != %s" % (self.resource_path, expected_resource_path) + raise SwaggerError("resourcePath has incorrect value", context) + + return self + + def load(self, api_decl_json, processor, context): + """Loads a resource from a single Swagger resource.json file. + """ + # If the version doesn't match, all bets are off. + self.swagger_version = api_decl_json.get('swaggerVersion') + if self.swagger_version != SWAGGER_VERSION: + raise SwaggerError( + "Unsupported Swagger version %s" % swagger_version, context) + + validate_required_fields(api_decl_json, self.required_fields, context) + + self.author = api_decl_json.get('_author') + self.copyright = api_decl_json.get('_copyright') + self.api_version = api_decl_json.get('apiVersion') + self.base_path = api_decl_json.get('basePath') + self.resource_path = api_decl_json.get('resourcePath') + api_json = api_decl_json.get('apis') or [] + self.apis = [ + Api().load(j, processor, context) for j in api_json] + models = api_decl_json.get('models').items() or [] + self.models = OrderedDict( + (k, Model().load(j, processor, context)) for (k, j) in models) + + for (name, model) in self.models.items(): + c = list(context).append('model = %s' % name) + if name != model.id: + raise SwaggerError("Model id doesn't match name", c) + return self + + +class ResourceApi(Stringify): + """Model of an API listing in the resources.json file. + """ + + required_fields = ['path', 'description'] + + def __init__(self): + self.path = None + self.description = None + self.api_declaration = None + + def load(self, api_json, processor, context): + context = add_context(context, api_json, 'path') + validate_required_fields(api_json, self.required_fields, context) + self.path = api_json['path'] + self.description = api_json['description'] + + if not self.path or self.path[0] != '/': + raise SwaggerError("Path must start with /", context) + processor.process_api(self, context) + return self + + def load_api_declaration(self, base_dir, processor): + self.file = (base_dir + self.path).replace('{format}', 'json') + self.api_declaration = ApiDeclaration().load_file(self.file, processor) + processor.process_api(self, [self.file]) + + +class ResourceListing(Stringify): + """Model of Swagger's resources.json file. + """ + + required_fields = ['apiVersion', 'basePath', 'apis'] + + def __init__(self): + self.swagger_version = None + self.api_version = None + self.base_path = None + self.apis = None + + def load_file(self, resource_file, processor): + context = [resource_file] + try: + return self.__load_file(resource_file, processor, context) + except SwaggerError: + raise + except Exception as e: + print >> sys.stderr, "Error: ", traceback.format_exc() + raise SwaggerError( + "Error loading %s" % resource_file, context, e) + + def __load_file(self, resource_file, processor, context): + with open(resource_file) as fp: + return self.load(json.load(fp), processor, context) + + def load(self, resources_json, processor, context): + # If the version doesn't match, all bets are off. + self.swagger_version = resources_json.get('swaggerVersion') + if self.swagger_version != SWAGGER_VERSION: + raise SwaggerError( + "Unsupported Swagger version %s" % swagger_version, context) + + validate_required_fields(resources_json, self.required_fields, context) + self.api_version = resources_json['apiVersion'] + self.base_path = resources_json['basePath'] + apis_json = resources_json['apis'] + self.apis = [ + ResourceApi().load(j, processor, context) for j in apis_json] + return self + + +def validate_required_fields(json, required_fields, context): + """Checks a JSON object for a set of required fields. + + If any required field is missing, a SwaggerError is raised. + + @param json: JSON object to check. + @param required_fields: List of required fields. + @param context: Current context in the API. + """ + missing_fields = [f for f in required_fields if not f in json] + + if missing_fields: + raise SwaggerError( + "Missing fields: %s" % ', '.join(missing_fields), context) + + +def add_context(context, json, id_field): + """Returns a new context with a new item added to it. + + @param context: Old context. + @param json: Current JSON object. + @param id_field: Field identifying this object. + @return New context with additional item. + """ + if not id_field in json: + raise SwaggerError("Missing id_field: %s" % id_field, context) + return context + ['%s=%s' % (id_field, str(json[id_field]))] diff --git a/rest-api-templates/transform.py b/rest-api-templates/transform.py new file mode 100644 index 000000000..d0ef3c4a1 --- /dev/null +++ b/rest-api-templates/transform.py @@ -0,0 +1,53 @@ +# +# 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. +# + +import os.path +import pystache + + +class Transform(object): + """Transformation for template to code. + """ + def __init__(self, template_file, dest_file_template_str, overwrite=True): + """Ctor. + + @param template_file: Filename of the mustache template. + @param dest_file_template_str: Destination file name. This is a + mustache template, so each resource can write to a unique file. + @param overwrite: If True, destination file is ovewritten if it exists. + """ + template_str = unicode(open(template_file, "r").read()) + self.template = pystache.parse(template_str) + dest_file_template_str = unicode(dest_file_template_str) + self.dest_file_template = pystache.parse(dest_file_template_str) + self.overwrite = overwrite + + def render(self, renderer, model, dest_dir): + """Render a model according to this transformation. + + @param render: Pystache renderer. + @param model: Model object to render. + @param dest_dir: Destination directory to write generated code. + """ + dest_file = pystache.render(self.dest_file_template, model) + dest_file = os.path.join(dest_dir, dest_file) + if os.path.exists(dest_file) and not self.overwrite: + return + print "Rendering %s" % dest_file + with open(dest_file, "w") as out: + out.write(renderer.render(self.template, model)) diff --git a/rest-api/README.txt b/rest-api/README.txt new file mode 100644 index 000000000..893bf87c3 --- /dev/null +++ b/rest-api/README.txt @@ -0,0 +1,9 @@ +<!-- Written in -*- Markdown -*- --> + +This directory contains the specification for the Asterisk RESTful +API. The API is documented using Swagger[1]. This is used to not only +generate executable documentation pages for the API, but also to +generate a lot of the boilerplate necessary for implementing the API +with Asterisk's HTTP server. + + [1]: http://swagger.wordnik.com/ diff --git a/rest-api/api-docs/asterisk.json b/rest-api/api-docs/asterisk.json new file mode 100644 index 000000000..ef6c7b864 --- /dev/null +++ b/rest-api/api-docs/asterisk.json @@ -0,0 +1,47 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/asterisk.{format}", + "apis": [ + { + "path": "/asterisk/info", + "description": "Asterisk system information (similar to core show settings)", + "operations": [ + { + "httpMethod": "GET", + "summary": "Gets Asterisk system information.", + "nickname": "getAsteriskInfo", + "responseClass": "AsteriskInfo", + "parameters": [ + { + "name": "only", + "description": "Filter information returned", + "paramType": "query", + "required": false, + "allowMultiple": true, + "dataType": "string", + "allowableValues": { + "valueType": "LIST", + "values": [ + "version", + "modules", + "uptime" + ] + } + } + ] + } + ] + } + ], + "models": { + "AsteriskInfo": { + "id": "AsteriskInfo", + "properties": {} + } + } +} diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json new file mode 100644 index 000000000..fd0971a4d --- /dev/null +++ b/rest-api/api-docs/bridges.json @@ -0,0 +1,257 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/bridges.{format}", + "apis": [ + { + "path": "/bridges", + "description": "Active bridges", + "operations": [ + { + "httpMethod": "GET", + "summary": "List active bridges.", + "nickname": "getBridges", + "responseClass": "List[Bridge]" + }, + { + "httpMethod": "POST", + "summary": "Create a new bridge.", + "notes": "This bridge persists until it has been shut down, or Asterisk has been shut down.", + "nickname": "newBridge", + "responseClass": "Bridge", + "parameters": [ + { + "name": "type", + "description": "Type of bridge to create.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string", + "allowedValues": { + "type": "LIST", + "values": [ + "two-party", + "multi-party", + "holding" + ] + } + } + ] + } + ] + }, + { + "path": "/bridges/{bridgeId}", + "description": "Individual bridge", + "operations": [ + { + "httpMethod": "GET", + "summary": "Get bridge details.", + "nickname": "getBridge", + "responseClass": "Bridge", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + }, + { + "httpMethod": "DELETE", + "summary": "Shut down a bridge bridge.", + "notes": "If any channels are in this bridge, they will be removed and resume whatever they were doing beforehand.", + "nickname": "deleteBridge", + "responseClass": "void", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/bridges/{bridgeId}/addChannel", + "description": "Add a channel to a bridge", + "operations": [ + { + "httpMethod": "POST", + "summary": "Add a channel to a bridge.", + "nickname": "addChannelToBridge", + "responseClass": "void", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "channel", + "description": "Channel's id", + "paramType": "query", + "required": true, + "allowMultiple": true, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/bridges/{bridgeId}/removeChannel", + "description": "Remove a channel from a bridge", + "operations": [ + { + "httpMethod": "POST", + "summary": "Remove a channel from a bridge.", + "nickname": "removeChannelFromBridge", + "responseClass": "void", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "channel", + "description": "Channel's id", + "paramType": "query", + "required": true, + "allowMultiple": true, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/bridges/{bridgeId}/record", + "description": "Record audio on a bridge", + "operations": [ + { + "httpMethod": "POST", + "summary": "Start a recording.", + "notes": "This records the mixed audio from all channels participating in this bridge.", + "nickname": "recordBridge", + "responseClass": "LiveRecording", + "parameters": [ + { + "name": "bridgeId", + "description": "Bridge's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "name", + "description": "Recording's filename", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "maxDurationSeconds", + "description": "Maximum duration of the recording, in seconds. 0 for no limit.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "int", + "defaultValue": 0 + }, + { + "name": "maxSilenceSeconds", + "description": "Maximum duration of silence, in seconds. 0 for no limit.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "int", + "defaultValue": 0 + }, + { + "name": "append", + "description": "If true, and recording already exists, append to recording.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "boolean", + "defaultValue": false + }, + { + "name": "beep", + "description": "Play beep when recording begins", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "boolean", + "defaultValue": false + }, + { + "name": "terminateOn", + "description": "DTMF input to terminate recording.", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string", + "defaultValue": "none", + "allowableValues": { + "valueType": "LIST", + "values": [ + "none", + "any", + "*", + "#" + ] + } + } + ] + } + ] + } + ], + "models": { + "Bridge": { + "id": "Bridge", + "properties": { + "bridgeType": { + "type": "string", + "description": "Type of bridge technology", + "required": true, + "allowedValues": { + "type": "LIST", + "values": [ + "two-party", + "multi-party", + "holding" + ] + } + }, + "channels": { + "type": "List[string]", + "description": "Id's of channels participating in this bridge", + "required": true + } + } + } + } +} diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json new file mode 100644 index 000000000..c2d77b22c --- /dev/null +++ b/rest-api/api-docs/channels.json @@ -0,0 +1,645 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/channels.{format}", + "apis": [ + { + "path": "/channels", + "description": "Active channels", + "operations": [ + { + "httpMethod": "GET", + "summary": "List active channels.", + "nickname": "getChannels", + "responseClass": "List[Channel]" + }, + { + "httpMethod": "POST", + "summary": "Create a new channel (originate).", + "nickname": "originate", + "responseClass": "Originated", + "parameters": [ + { + "name": "endpoint", + "description": "Endpoint to call. If not specified, originate is routed via dialplan", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "extension", + "description": "Extension to dial", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "context", + "description": "When routing via dialplan, the context use. If omitted, uses 'default'", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}", + "description": "Active channel", + "operations": [ + { + "httpMethod": "GET", + "summary": "Channel details.", + "nickname": "getChannel", + "responseClass": "Channel", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + } + ] + }, + { + "httpMethod": "DELETE", + "summary": "Delete (i.e. hangup) a channel.", + "nickname": "deleteChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/dial", + "description": "Create a new channel (originate) and bridge to this channel", + "operations": [ + { + "httpMethod": "POST", + "summary": "Create a new channel (originate) and bridge to this channel.", + "nickname": "dial", + "responseClass": "Dialed", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "endpoint", + "description": "Endpoint to call. If not specified, dial is routed via dialplan", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "extension", + "description": "Extension to dial", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "context", + "description": "When routing via dialplan, the context use. If omitted, uses 'default'", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/continue", + "description": "Exit application; continue execution in the dialplan", + "operations": [ + { + "httpMethod": "POST", + "summary": "Exit application; continue execution in the dialplan.", + "nickname": "continueInDialplan", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/answer", + "description": "Answer a channel", + "operations": [ + { + "httpMethod": "POST", + "summary": "Answer a channel.", + "nickname": "answerChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/mute", + "description": "Mute a channel", + "operations": [ + { + "httpMethod": "POST", + "summary": "Mute a channel.", + "nickname": "muteChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "direction", + "description": "Direction in which to mute audio", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string", + "defaultValue": "both", + "allowableValues": { + "valueType": "LIST", + "values": [ + "both", + "in", + "out" + ] + } + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/unmute", + "description": "Unmute a channel", + "operations": [ + { + "httpMethod": "POST", + "summary": "Unmute a channel.", + "nickname": "unmuteChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "direction", + "description": "Direction in which to unmute audio", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string", + "defaultValue": "both", + "allowableValues": { + "valueType": "LIST", + "values": [ + "both", + "in", + "out" + ] + } + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/hold", + "description": "Put a channel on hold", + "operations": [ + { + "httpMethod": "POST", + "summary": "Hold a channel.", + "nickname": "holdChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/unhold", + "description": "Remove a channel from hold", + "operations": [ + { + "httpMethod": "POST", + "summary": "Remove a channel from hold.", + "nickname": "unholdChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/play", + "description": "Play media to a channel", + "operations": [ + { + "httpMethod": "POST", + "summary": "Start playback of media.", + "notes": "The media URI may be any of a number of URI's. You may use http: and https: URI's, as well as sound: and recording: URI's. This operation creates a playback resource that can be used to control the playback of media (pause, rewind, fast forward, etc.)", + "nickname": "playOnChannel", + "responseClass": "Playback", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "media", + "description": "Media's URI to play.", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel not in a Stasis application" + } + ] + } + ] + }, + { + "path": "/channels/{channelId}/record", + "description": "Record audio from a channel", + "operations": [ + { + "httpMethod": "POST", + "summary": "Start a recording.", + "notes": "Record audio from a channel. Note that this will not capture audio sent to the channel. The bridge itself has a record feature if that's what you want.", + "nickname": "recordChannel", + "responseClass": "void", + "parameters": [ + { + "name": "channelId", + "description": "Channel's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "name", + "description": "Recording's filename", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "format", + "description": "Format to encode audio in", + "paramType": "query", + "required": true, + "allowMultiple": true, + "dataType": "string" + }, + { + "name": "maxDurationSeconds", + "description": "Maximum duration of the recording, in seconds. 0 for no limit", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "int", + "defaultValue": 0 + }, + { + "name": "maxSilenceSeconds", + "description": "Maximum duration of silence, in seconds. 0 for no limit", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "int", + "defaultValue": 0 + }, + { + "name": "append", + "description": "If true, and recording already exists, append to recording", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "boolean", + "defaultValue": false + }, + { + "name": "beep", + "description": "Play beep when recording begins", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "boolean", + "defaultValue": false + }, + { + "name": "terminateOn", + "description": "DTMF input to terminate recording", + "paramType": "query", + "required": false, + "allowMultiple": false, + "dataType": "string", + "defaultValue": "none", + "allowableValues": { + "valueType": "LIST", + "values": [ + "none", + "any", + "*", + "#" + ] + } + } + ], + "errorResponses": [ + { + "code": 404, + "reason": "Channel not found" + }, + { + "code": 409, + "reason": "Channel is not in a Stasis application." + }, + { + "code": 409, + "reason": "The channel is currently bridges with other channels." + } + ] + } + ] + } + ], + "models": { + "Originated": { + "id": "Originated", + "properties": {} + }, + "Dialed": { + "id": "Dialed", + "properties": {} + }, + "DialplanCEP": { + "id": "DialplanCEP", + "properties": { + "context": { + "required": true, + "type": "string", + "description": "Context in the dialplan" + }, + "exten": { + "required": true, + "type": "string", + "description": "Extension in the dialplan" + }, + "priority": { + "required": true, + "type": "long", + "description": "Priority in the dialplan" + } + } + }, + "CallerID": { + "id": "CallerID", + "properties": { + "name": { + "required": true, + "type": "string" + }, + "number": { + "required": true, + "type": "string" + } + } + }, + "Channel": { + "id": "Channel", + "properties": { + "uniqueid": { + "required": true, + "type": "string", + "description": "Unique identifier of the channel" + }, + "name": { + "required": true, + "type": "string", + "description": "Name of the channel (i.e. SIP/foo-0000a7e3)" + }, + "state": { + "required": true, + "type": "string" + }, + "accountcode": { + "required": true, + "type": "string" + }, + "peeraccount": { + "required": true, + "type": "string" + }, + "userfield": { + "required": true, + "type": "string" + }, + "linkedid": { + "required": true, + "type": "string" + }, + "parkinglot": { + "required": true, + "type": "string" + }, + "hangupsource": { + "required": true, + "type": "string" + }, + "appl": { + "required": true, + "type": "string", + "description": "Currently executing dialplan application" + }, + "data": { + "required": true, + "type": "string", + "description": "Arguments passed to appl" + }, + "dialplan": { + "required": true, + "type": "DialplanCEP", + "description": "Current location in the dialplan" + }, + "caller": { + "required": true, + "type": "CallerID" + }, + "connected": { + "required": true, + "type": "CallerID" + }, + "creationtime": { + "required": true, + "type": "Date", + "description": "Timestamp when channel was created" + } + } + } + } +} diff --git a/rest-api/api-docs/endpoints.json b/rest-api/api-docs/endpoints.json new file mode 100644 index 000000000..43b8453d7 --- /dev/null +++ b/rest-api/api-docs/endpoints.json @@ -0,0 +1,68 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/endpoints.{format}", + "apis": [ + { + "path": "/endpoints", + "description": "Asterisk endpoints", + "operations": [ + { + "httpMethod": "GET", + "summary": "List available endoints.", + "nickname": "getEndpoints", + "responseClass": "List[Endpoint]", + "parameters": [ + { + "name": "withType", + "description": "Filter endpoints by type (sip,iax2,dhadi,...)", + "paramType": "query", + "required": false, + "allowMultiple": true, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/endpoints/{endpointId}", + "description": "Single endpoint", + "operations": [ + { + "httpMethod": "GET", + "summary": "Details for an endpoint.", + "nickname": "getEndpoint", + "responseClass": "Endpoint", + "parameters": [ + { + "name": "endpointId", + "description": "ID of the endpoint", + "paramType": "path", + "dataType": "string" + } + ] + } + ] + } + ], + "models": { + "Endpoint": { + "id": "Endpoint", + "properties": { + "technology": { + "type": "string", + "required": true + }, + "name": { + "type": "string", + "required": true + } + } + } + } +} diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json new file mode 100644 index 000000000..b1389017f --- /dev/null +++ b/rest-api/api-docs/events.json @@ -0,0 +1,166 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/events.{format}", + "apis": [ + { + "path": "/events", + "description": "Events from Asterisk to applications", + "operations": [ + { + "httpMethod": "GET", + "summary": "WebSocket connection for events.", + "nickname": "eventWebsocket", + "responseClass": "Event", + "parameters": [ + { + "name": "app", + "description": "Comma seperated list of applications to subscribe to.", + "paramType": "query", + "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" + ] + } + } + ] + } + ] + } + ], + "models": { + "Event": { + "id": "Event", + "description": "Asynchronous events from Asterisk. The non-required fields of this object are mutually exclusive.", + "properties": { + "application": { + "type": "string", + "description": "Name of the application receiving the event.", + "required": true + }, + "application_replaced": { "type": "ApplicationReplaced" }, + "bridge_created": { "type": "BridgeCreated" }, + "bridge_destroyed": { "type": "BridgeDestroyed" }, + "channel_entered_bridge": { "type": "ChannelEnteredBridge" }, + "channel_left_bridge": { "type": "ChannelLeftBridge" }, + "channel_state_change": { "type": "ChannelStateChange" }, + "dtmf_received": { "type": "DtmfReceived" }, + "stasis_end": { "type": "StasisEnd" }, + "stasis_start": { "type": "StasisStart" } + } + }, + "ApplicationReplaced": { + "id": "ApplicationReplaced", + "description": "Notification that another WebSocket has taken over for an application.", + "notes": "An application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.", + "properties": { + "application": { + "type": "string" + } + } + }, + "BridgeCreated": { + "id": "BridgeCreated", + "description": "Notification that a bridge has been created.", + "properties": { + "bridge": { + "type": "Bridge" + } + } + }, + "BridgeDestroyed": { + "id": "BridgeDestroyed", + "description": "Notification that a bridge has been destroyed.", + "properties": { + "bridge": { + "type": "Bridge" + } + } + }, + "ChannelEnteredBridge": { + "id": "ChannelEnteredBridge", + "description": "Notification that a channel has entered a bridge.", + "properties": { + "bridge": { + "type": "Bridge" + }, + "channel": { + "type": "Channel" + } + } + }, + "ChannelLeftBridge": { + "id": "ChannelLeftBridge", + "description": "Notification that a channel has left a bridge.", + "properties": { + "bridge": { + "type": "Bridge" + }, + "channel": { + "type": "Channel" + } + } + }, + "ChannelStateChange": { + "id": "ChannelStateChange", + "description": "Notification of a channel's state change.", + "properties": { + "channel_info": { + "type": "Channel" + } + } + }, + "DtmfReceived": { + "id": "DtmfReceived", + "description": "DTMF received on a channel.", + "notes": "This event is sent when the DTMF ends. There is no notification about the start of DTMF", + "properties": { + "digit": { + "type": "string", + "description": "DTMF digit received (0-9, A-E, # or *)" + }, + "channel": { + "type": "Channel", + "description": "The channel on which DTMF was received" + } + } + }, + "StasisEnd": { + "id": "StasisEnd", + "description": "Notification that a channel has left a Stasis appliction.", + "properties": { + "channel_info": { + "type": "Channel" + } + } + }, + "StasisStart": { + "id": "StasisStart", + "description": "Notification that a channel has entered a Stasis appliction.", + "properties": { + "args": { + "type": "List[string]", + "description": "Arguments to the application" + }, + "channel_info": { + "type": "Channel" + } + } + } + } +} diff --git a/rest-api/api-docs/playback.json b/rest-api/api-docs/playback.json new file mode 100644 index 000000000..aa758781c --- /dev/null +++ b/rest-api/api-docs/playback.json @@ -0,0 +1,102 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/playback.{format}", + "apis": [ + { + "path": "/playback/{playbackId}", + "description": "Control object for a playback operation.", + "operations": [ + { + "httpMethod": "GET", + "summary": "Get a playback's details.", + "nickname": "getPlayback", + "responseClass": "Playback", + "parameters": [ + { + "name": "playbackId", + "description": "Playback's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + }, + { + "httpMethod": "DELETE", + "summary": "Stop a playback.", + "nickname": "stopPlayback", + "responseClass": "Playback", + "parameters": [ + { + "name": "playbackId", + "description": "Playback's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/playback/{playbackId}/control", + "description": "Control object for a playback operation.", + "operations": [ + { + "httpMethod": "POST", + "summary": "Get a playback's details.", + "nickname": "controlPlayback", + "responseClass": "Playback", + "parameters": [ + { + "name": "playbackId", + "description": "Playback's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + }, + { + "name": "operation", + "description": "Operation to perform on the playback.", + "paramType": "query", + "required": true, + "allowMultiple": false, + "dataType": "string", + "allowableValues": { + "valueType": "LIST", + "values": [ + "play", + "pause", + "rewind", + "fast-forward", + "speed-up", + "slow-down" + ] + } + } + ] + } + ] + } + ], + "models": { + "Playback": { + "id": "Playback", + "properties": { + "id": { + "required": true, + "description": "Playback's identifier.", + "type": "string" + } + } + } + } +} diff --git a/rest-api/api-docs/recordings.json b/rest-api/api-docs/recordings.json new file mode 100644 index 000000000..2f5f92a08 --- /dev/null +++ b/rest-api/api-docs/recordings.json @@ -0,0 +1,270 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/recordings.{format}", + "apis": [ + { + "path": "/recordings", + "description": "Recordings", + "operations": [ + { + "httpMethod": "GET", + "summary": "List all recordings.", + "nickname": "getRecordings", + "responseClass": "List[Recording]" + } + ] + }, + { + "path": "/recordings/stored", + "description": "Recordings", + "operations": [ + { + "httpMethod": "GET", + "summary": "List recordings that are complete.", + "nickname": "getStoredRecordings", + "responseClass": "List[StoredRecording]" + } + ] + }, + { + "path": "/recordings/stored/{recordingId}", + "description": "Individual recording", + "operations": [ + { + "httpMethod": "GET", + "summary": "Get a stored recording's details.", + "nickname": "getStoredRecording", + "responseClass": "StoredRecording", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + }, + { + "httpMethod": "DELETE", + "summary": "Delete a stored recording.", + "nickname": "deleteStoredRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/recordings/live", + "description": "Recordings that are in progress", + "operations": [ + { + "httpMethod": "GET", + "summary": "List libe recordings.", + "nickname": "getLiveRecordings", + "responseClass": "List[LiveRecording]" + } + ] + }, + { + "path": "/recordings/live/{recordingId}", + "description": "A recording that is in progress", + "operations": [ + { + "httpMethod": "GET", + "summary": "List live recordings.", + "nickname": "getLiveRecording", + "responseClass": "LiveRecording", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + }, + { + "httpMethod": "DELETE", + "summary": "Stop a live recording and discard it.", + "nickname": "cancelRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/recordings/live/{recordingId}/stop", + "operations": [ + { + "httpMethod": "POST", + "summary": "Stop a live recording and store it.", + "nickname": "stopRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/recordings/live/{recordingId}/pause", + "operations": [ + { + "httpMethod": "POST", + "summary": "Pause a live recording.", + "nickname": "pauseRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/recordings/live/{recordingId}/unpause", + "operations": [ + { + "httpMethod": "POST", + "summary": "Unpause a live recording.", + "nickname": "unpauseRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/recordings/live/{recordingId}/mute", + "operations": [ + { + "httpMethod": "POST", + "summary": "Mute a live recording.", + "nickname": "muteRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + }, + { + "path": "/recordings/live/{recordingId}/unmute", + "operations": [ + { + "httpMethod": "POST", + "summary": "Unmute a live recording.", + "nickname": "unmuteRecording", + "responseClass": "void", + "parameters": [ + { + "name": "recordingId", + "description": "Recording's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + } + ], + "models": { + "Recording": { + "id": "Recording", + "properties": { + "id": { + "required": true, + "type": "string" + } + } + }, + "StoredRecording": { + "id": "StoredRecording", + "properties": { + "id": { + "required": true, + "type": "string" + }, + "formats": { + "required": true, + "type": "List[string]" + }, + "durationSeconds": { + "required": false, + "type": "int" + }, + "time": { + "description": "Time recording was started", + "required": false, + "type": "Date" + } + } + }, + "LiveRecording": { + "id": "LiveRecording", + "properties": { + "id": { + "required": true, + "type": "string" + } + } + } + } +} diff --git a/rest-api/api-docs/sounds.json b/rest-api/api-docs/sounds.json new file mode 100644 index 000000000..23d02257d --- /dev/null +++ b/rest-api/api-docs/sounds.json @@ -0,0 +1,85 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "resourcePath": "/api-docs/sounds.{format}", + "apis": [ + { + "path": "/sounds", + "description": "Sounds", + "operations": [ + { + "httpMethod": "GET", + "summary": "List all sounds.", + "nickname": "getSounds", + "responseClass": "List[Sound]", + "parameters": [ + { + "name": "lang", + "paramType": "query", + "dataType": "string", + "required": false + }, + { + "name": "format", + "paramType": "query", + "dataType": "string", + "required": false, + "__note": "core show translation can show translation paths between formats, along with relative costs. so this could be just installed format, or we could follow that for transcoded formats." + } + ] + } + ] + }, + { + "path": "/sounds/{soundId}", + "description": "Individual sound", + "operations": [ + { + "httpMethod": "GET", + "summary": "Get a sound's details.", + "nickname": "getStoredSound", + "responseClass": "Sound", + "parameters": [ + { + "name": "soundId", + "description": "Sound's id", + "paramType": "path", + "required": true, + "allowMultiple": false, + "dataType": "string" + } + ] + } + ] + } + ], + "models": { + "Sound": { + "id": "Sound", + "properties": { + "id": { + "required": true, + "description": "Sound's identifier.", + "type": "string" + }, + "text": { + "required": false, + "description": "Text description of the sound, usually the words spoken.", + "type": "string" + }, + "lang": { + "required": true, + "type": "string" + }, + "formats": { + "required": true, + "type": "List[string]" + } + } + } + } +} diff --git a/rest-api/resources.json b/rest-api/resources.json new file mode 100644 index 000000000..d9a0c5b35 --- /dev/null +++ b/rest-api/resources.json @@ -0,0 +1,42 @@ +{ + "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.", + "_author": "David M. Lee, II <dlee@digium.com>", + "_svn_revision": "$Revision$", + "apiVersion": "0.0.1", + "swaggerVersion": "1.1", + "basePath": "http://localhost:8088/stasis", + "apis": [ + { + "path": "/api-docs/asterisk.{format}", + "description": "Asterisk resources" + }, + { + "path": "/api-docs/endpoints.{format}", + "description": "Endpoint resources" + }, + { + "path": "/api-docs/channels.{format}", + "description": "Channel resources" + }, + { + "path": "/api-docs/bridges.{format}", + "description": "Bridge resources" + }, + { + "path": "/api-docs/recordings.{format}", + "description": "Recording resources" + }, + { + "path": "/api-docs/sounds.{format}", + "description": "Sound resources" + }, + { + "path": "/api-docs/playback.{format}", + "description": "Playback control resources" + }, + { + "path": "/api-docs/events.{format}", + "description": "WebSocket resource" + } + ] +} diff --git a/tests/test_stasis.c b/tests/test_stasis.c index 3a7d52c07..8f81378ab 100644 --- a/tests/test_stasis.c +++ b/tests/test_stasis.c @@ -645,7 +645,6 @@ AST_TEST_DEFINE(cache) RAII_VAR(struct stasis_message *, test_message1_clear, NULL, ao2_cleanup); int actual_len; struct stasis_cache_update *actual_update; - struct ao2_container *cache_dump; switch (cmd) { case TEST_INIT: @@ -681,12 +680,6 @@ AST_TEST_DEFINE(cache) actual_len = consumer_wait_for(consumer, 2); ast_test_validate(test, 2 == actual_len); - /* Dump the cache to ensure that it has the correct number of items in it */ - cache_dump = stasis_cache_dump(caching_topic, NULL); - ast_test_validate(test, 2 == ao2_container_count(cache_dump)); - ao2_ref(cache_dump, -1); - cache_dump = NULL; - /* Check for new snapshot messages */ ast_test_validate(test, stasis_cache_update_type() == stasis_message_type(consumer->messages_rxed[0])); actual_update = stasis_message_data(consumer->messages_rxed[0]); @@ -722,12 +715,6 @@ AST_TEST_DEFINE(cache) /* stasis_cache_get returned a ref, so unref test_message2_2 */ ao2_ref(test_message2_2, -1); - /* Dump the cache to ensure that it has the correct number of items in it */ - cache_dump = stasis_cache_dump(caching_topic, NULL); - ast_test_validate(test, 2 == ao2_container_count(cache_dump)); - ao2_ref(cache_dump, -1); - cache_dump = NULL; - /* Clear snapshot 1 */ test_message1_clear = stasis_cache_clear_create(cache_type, "1"); ast_test_validate(test, NULL != test_message1_clear); @@ -742,17 +729,109 @@ AST_TEST_DEFINE(cache) ast_test_validate(test, NULL == actual_update->new_snapshot); ast_test_validate(test, NULL == stasis_cache_get(caching_topic, cache_type, "1")); - /* Dump the cache to ensure that it has the correct number of items in it */ + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(cache_dump) +{ + RAII_VAR(struct stasis_message_type *, cache_type, NULL, ao2_cleanup); + RAII_VAR(struct stasis_topic *, topic, NULL, ao2_cleanup); + RAII_VAR(struct stasis_caching_topic *, caching_topic, NULL, stasis_caching_unsubscribe); + RAII_VAR(struct consumer *, consumer, NULL, ao2_cleanup); + RAII_VAR(struct stasis_subscription *, sub, NULL, stasis_unsubscribe); + RAII_VAR(struct stasis_message *, test_message1_1, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, test_message2_1, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, test_message2_2, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, test_message1_clear, NULL, ao2_cleanup); + RAII_VAR(struct ao2_container *, cache_dump, NULL, ao2_cleanup); + int actual_len; + struct ao2_iterator i; + void *obj; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = test_category; + info->summary = "Test passing messages through cache topic unscathed."; + info->description = "Test passing messages through cache topic unscathed."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + cache_type = stasis_message_type_create("Cacheable"); + ast_test_validate(test, NULL != cache_type); + topic = stasis_topic_create("SomeTopic"); + ast_test_validate(test, NULL != topic); + caching_topic = stasis_caching_topic_create(topic, cache_test_data_id); + ast_test_validate(test, NULL != caching_topic); + consumer = consumer_create(1); + ast_test_validate(test, NULL != consumer); + sub = stasis_subscribe(stasis_caching_get_topic(caching_topic), consumer_exec, consumer); + ast_test_validate(test, NULL != sub); + ao2_ref(consumer, +1); + + test_message1_1 = cache_test_message_create(cache_type, "1", "1"); + ast_test_validate(test, NULL != test_message1_1); + test_message2_1 = cache_test_message_create(cache_type, "2", "1"); + ast_test_validate(test, NULL != test_message2_1); + + /* Post a couple of snapshots */ + stasis_publish(topic, test_message1_1); + stasis_publish(topic, test_message2_1); + actual_len = consumer_wait_for(consumer, 2); + ast_test_validate(test, 2 == actual_len); + + /* Check the cache */ + cache_dump = stasis_cache_dump(caching_topic, NULL); + ast_test_validate(test, NULL != cache_dump); + ast_test_validate(test, 2 == ao2_container_count(cache_dump)); + i = ao2_iterator_init(cache_dump, 0); + while ((obj = ao2_iterator_next(&i))) { + RAII_VAR(struct stasis_message *, actual_cache_entry, obj, ao2_cleanup); + ast_test_validate(test, actual_cache_entry == test_message1_1 || actual_cache_entry == test_message2_1); + } + + /* Update snapshot 2 */ + test_message2_2 = cache_test_message_create(cache_type, "2", "2"); + ast_test_validate(test, NULL != test_message2_2); + stasis_publish(topic, test_message2_2); + + actual_len = consumer_wait_for(consumer, 3); + ast_test_validate(test, 3 == actual_len); + + /* Check the cache */ + cache_dump = stasis_cache_dump(caching_topic, NULL); + ast_test_validate(test, NULL != cache_dump); + ast_test_validate(test, 2 == ao2_container_count(cache_dump)); + i = ao2_iterator_init(cache_dump, 0); + while ((obj = ao2_iterator_next(&i))) { + RAII_VAR(struct stasis_message *, actual_cache_entry, obj, ao2_cleanup); + ast_test_validate(test, actual_cache_entry == test_message1_1 || actual_cache_entry == test_message2_2); + } + + /* Clear snapshot 1 */ + test_message1_clear = stasis_cache_clear_create(cache_type, "1"); + ast_test_validate(test, NULL != test_message1_clear); + stasis_publish(topic, test_message1_clear); + + actual_len = consumer_wait_for(consumer, 4); + ast_test_validate(test, 4 == actual_len); + + /* Check the cache */ cache_dump = stasis_cache_dump(caching_topic, NULL); + ast_test_validate(test, NULL != cache_dump); ast_test_validate(test, 1 == ao2_container_count(cache_dump)); - ao2_ref(cache_dump, -1); - cache_dump = NULL; + i = ao2_iterator_init(cache_dump, 0); + while ((obj = ao2_iterator_next(&i))) { + RAII_VAR(struct stasis_message *, actual_cache_entry, obj, ao2_cleanup); + ast_test_validate(test, actual_cache_entry == test_message2_2); + } /* Dump the cache to ensure that it has no subscription change items in it since those aren't cached */ + ao2_cleanup(cache_dump); cache_dump = stasis_cache_dump(caching_topic, stasis_subscription_change_type()); ast_test_validate(test, 0 == ao2_container_count(cache_dump)); - ao2_ref(cache_dump, -1); - cache_dump = NULL; return AST_TEST_PASS; } @@ -909,6 +988,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(forward); AST_TEST_UNREGISTER(cache_passthrough); AST_TEST_UNREGISTER(cache); + AST_TEST_UNREGISTER(cache_dump); AST_TEST_UNREGISTER(route_conflicts); AST_TEST_UNREGISTER(router); AST_TEST_UNREGISTER(interleaving); @@ -925,6 +1005,7 @@ static int load_module(void) AST_TEST_REGISTER(forward); AST_TEST_REGISTER(cache_passthrough); AST_TEST_REGISTER(cache); + AST_TEST_REGISTER(cache_dump); AST_TEST_REGISTER(route_conflicts); AST_TEST_REGISTER(router); AST_TEST_REGISTER(interleaving); diff --git a/tests/test_stasis_http.c b/tests/test_stasis_http.c new file mode 100644 index 000000000..953c1023f --- /dev/null +++ b/tests/test_stasis_http.c @@ -0,0 +1,547 @@ +/* + * 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. + */ + +/*! + * \file \brief Test Stasis HTTP API. + * \author\verbatim David M. Lee, II <dlee@digium.com> \endverbatim + * + * \ingroup tests + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <depend>res_stasis_http</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/test.h" +#include "asterisk/stasis_http.h" + +/*!@{*/ + +/*! + * \internal + * The following code defines a simple RESTful API for unit testing. The + * response encodes the inputs of the invocation. The invocation_count + * counter is also incremented. + * + * - /foo (GET) + * - /foo/bar (GET, POST) + * - /foo/{bam} (GET) + * - /foo/{bam}/bang (GET, POST, DE1LETE) + */ + +static int invocation_count; + +/*! + * \internal + * Shared code for all handlers + */ +static void handler(const char *name, + int response_code, + struct ast_variable *get_params, + struct ast_variable *path_vars, + struct ast_variable *headers, + struct stasis_http_response *response) +{ + struct ast_json *message = ast_json_pack("{s: s, s: {}, s: {}, s: {}}", + "name", name, + "get_params", + "path_vars", + "headers"); + struct ast_json *get_params_obj = ast_json_object_get(message, "get_params"); + struct ast_json *path_vars_obj = ast_json_object_get(message, "path_vars"); + struct ast_json *headers_obj = ast_json_object_get(message, "headers"); + + for (; get_params != NULL; get_params = get_params->next) { + ast_json_object_set(get_params_obj, get_params->name, ast_json_string_create(get_params->value)); + } + + for (; path_vars != NULL; path_vars = path_vars->next) { + ast_json_object_set(path_vars_obj, path_vars->name, ast_json_string_create(path_vars->value)); + } + + for (; headers != NULL; headers = headers->next) { + ast_json_object_set(headers_obj, headers->name, ast_json_string_create(headers->value)); + } + + ++invocation_count; + response->response_code = response_code; + response->message = message; +} + +/*! + * \internal + * Macro to reduce the handler definition boiler-plate. + */ +#define HANDLER(name, response_code) \ + static void name(struct ast_variable *get_params, \ + struct ast_variable *path_vars, \ + struct ast_variable *headers, \ + struct stasis_http_response *response) \ + { \ + handler(#name, response_code, get_params, path_vars, headers, response); \ + } + +HANDLER(bang_get, 200) +HANDLER(bang_post, 200) +HANDLER(bang_delete, 204) +HANDLER(bar_get, 200) +HANDLER(bar_post, 200) +HANDLER(bam_get, 200) +HANDLER(foo_get, 200) + +static struct stasis_rest_handlers bang = { + .path_segment = "bang", + .callbacks = { + [AST_HTTP_GET] = bang_get, + [AST_HTTP_POST] = bang_post, + [AST_HTTP_DELETE] = bang_delete, + }, + .num_children = 0 +}; +static struct stasis_rest_handlers bar = { + .path_segment = "bar", + .callbacks = { + [AST_HTTP_GET] = bar_get, + [AST_HTTP_POST] = bar_post, + }, + .num_children = 0 +}; +static struct stasis_rest_handlers bam = { + .path_segment = "bam", + .is_wildcard = 1, + .callbacks = { + [AST_HTTP_GET] = bam_get, + }, + .num_children = 1, + .children = { &bang } +}; +static struct stasis_rest_handlers test_root = { + .path_segment = "foo", + .callbacks = { + [AST_HTTP_GET] = foo_get, + }, + .num_children = 3, + .children = { &bar, &bam, &bang } +}; +/*!@}*/ + +/*! + * \internal + * \c stasis_http_response constructor. + */ +static struct stasis_http_response *response_alloc(void) +{ + struct stasis_http_response *resp = ast_calloc(1, sizeof(struct stasis_http_response)); + resp->headers = ast_str_create(24); + return resp; +} + +/*! + * \internal + * \c stasis_http_response destructor. + */ +static void response_free(struct stasis_http_response *resp) +{ + ast_free(resp->headers); + ast_json_unref(resp->message); + ast_free(resp); +} + +/*! + * \ internal + * Setup test fixture for invocation tests. + */ +static void *setup_invocation_test(void) { + int r; + invocation_count = 0; + r = stasis_http_add_handler(&test_root); + ast_assert(r == 0); + return NULL; +} + +/*! + * \ internal + * Tear down test fixture for invocation tests. + */ +static void tear_down_invocation_test(void *ignore) { + stasis_http_remove_handler(&test_root); +} + + +AST_TEST_DEFINE(get_docs) +{ + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy); + struct ast_json *basePathJson; + const char *basePath; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test simple API get."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + headers = ast_variable_new("Host", "stasis.asterisk.org", __FILE__); + stasis_http_get_docs("resources.json", headers, response); + ast_test_validate(test, 200 == response->response_code); + + /* basePath should be relative to the Host header */ + 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)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(get_docs_nohost) +{ + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + struct ast_variable *headers = NULL; + struct ast_json *basePathJson; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test API get without a Host header"; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + stasis_http_get_docs("resources.json", headers, response); + ast_test_validate(test, 200 == response->response_code); + + /* basePath should be relative to the Host header */ + basePathJson = ast_json_object_get(response->message, "basePath"); + ast_test_validate(test, NULL == basePathJson); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(get_docs_notfound) +{ + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test API get for invalid resource"; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + stasis_http_get_docs("i-am-not-a-resource.json", headers, response); + ast_test_validate(test, 404 == response->response_code); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(get_docs_hackerz) +{ + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test API get for a file outside the rest-api path"; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + stasis_http_get_docs("../../../../sbin/asterisk", headers, response); + ast_test_validate(test, 404 == response->response_code); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invoke_get) +{ + RAII_VAR(void *, fixture, setup_invocation_test(), tear_down_invocation_test); + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + struct ast_variable *get_params = NULL; + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test simple GET of an HTTP resource."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + get_params = ast_variable_new("get1", "get-one", __FILE__); + ast_assert(get_params != NULL); + get_params->next = ast_variable_new("get2", "get-two", __FILE__); + ast_assert(get_params->next != NULL); + + headers = ast_variable_new("head1", "head-one", __FILE__); + ast_assert(headers != NULL); + headers->next = ast_variable_new("head2", "head-two", __FILE__); + ast_assert(headers->next != NULL); + + expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}", + "name", "foo_get", + "get_params", + "get1", "get-one", + "get2", "get-two", + "headers", + "head1", "head-one", + "head2", "head-two", + "path_vars"); + + stasis_http_invoke("foo", AST_HTTP_GET, get_params, headers, response); + + ast_test_validate(test, 1 == invocation_count); + ast_test_validate(test, 200 == response->response_code); + ast_test_validate(test, ast_json_equal(expected, response->message)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invoke_wildcard) +{ + RAII_VAR(void *, fixture, setup_invocation_test(), tear_down_invocation_test); + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + struct ast_variable *get_params = NULL; + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test GET of a wildcard resource."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}", + "name", "bam_get", + "get_params", + "headers", + "path_vars", + "bam", "foshizzle"); + + stasis_http_invoke("foo/foshizzle", AST_HTTP_GET, get_params, headers, response); + + ast_test_validate(test, 1 == invocation_count); + ast_test_validate(test, 200 == response->response_code); + ast_test_validate(test, ast_json_equal(expected, response->message)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invoke_delete) +{ + RAII_VAR(void *, fixture, setup_invocation_test(), tear_down_invocation_test); + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + struct ast_variable *get_params = NULL; + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test DELETE of an HTTP resource."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + expected = ast_json_pack("{s: s, s: {}, s: {}, s: {s: s}}", + "name", "bang_delete", + "get_params", + "headers", + "path_vars", + "bam", "foshizzle"); + + stasis_http_invoke("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); + ast_test_validate(test, ast_json_equal(expected, response->message)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invoke_post) +{ + RAII_VAR(void *, fixture, setup_invocation_test(), tear_down_invocation_test); + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + struct ast_variable *get_params = NULL; + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test POST of an HTTP resource."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + get_params = ast_variable_new("get1", "get-one", __FILE__); + ast_assert(get_params != NULL); + get_params->next = ast_variable_new("get2", "get-two", __FILE__); + ast_assert(get_params->next != NULL); + + headers = ast_variable_new("head1", "head-one", __FILE__); + ast_assert(headers != NULL); + headers->next = ast_variable_new("head2", "head-two", __FILE__); + ast_assert(headers->next != NULL); + + expected = ast_json_pack("{s: s, s: {s: s, s: s}, s: {s: s, s: s}, s: {}}", + "name", "bar_post", + "get_params", + "get1", "get-one", + "get2", "get-two", + "headers", + "head1", "head-one", + "head2", "head-two", + "path_vars"); + + stasis_http_invoke("foo/bar", AST_HTTP_POST, get_params, headers, response); + + ast_test_validate(test, 1 == invocation_count); + ast_test_validate(test, 200 == response->response_code); + ast_test_validate(test, ast_json_equal(expected, response->message)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invoke_bad_post) +{ + RAII_VAR(void *, fixture, setup_invocation_test(), tear_down_invocation_test); + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + struct ast_variable *get_params = NULL; + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test POST on a resource that doesn't support it."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + stasis_http_invoke("foo", AST_HTTP_POST, get_params, headers, response); + + ast_test_validate(test, 0 == invocation_count); + ast_test_validate(test, 405 == response->response_code); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(invoke_not_found) +{ + RAII_VAR(void *, fixture, setup_invocation_test(), tear_down_invocation_test); + RAII_VAR(struct stasis_http_response *, response, response_alloc(), response_free); + struct ast_variable *get_params = NULL; + struct ast_variable *headers = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = "/stasis/http/"; + info->summary = "Test GET on a resource that does not exist."; + info->description = "Test Stasis HTTP binding logic."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + stasis_http_invoke("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); + + return AST_TEST_PASS; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(get_docs); + AST_TEST_UNREGISTER(get_docs_nohost); + AST_TEST_UNREGISTER(get_docs_notfound); + AST_TEST_UNREGISTER(get_docs_hackerz); + AST_TEST_UNREGISTER(invoke_get); + AST_TEST_UNREGISTER(invoke_wildcard); + AST_TEST_UNREGISTER(invoke_delete); + AST_TEST_UNREGISTER(invoke_post); + AST_TEST_UNREGISTER(invoke_bad_post); + AST_TEST_UNREGISTER(invoke_not_found); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(get_docs); + AST_TEST_REGISTER(get_docs_nohost); + AST_TEST_REGISTER(get_docs_notfound); + AST_TEST_REGISTER(get_docs_hackerz); + AST_TEST_REGISTER(invoke_get); + AST_TEST_REGISTER(invoke_wildcard); + AST_TEST_REGISTER(invoke_delete); + AST_TEST_REGISTER(invoke_post); + AST_TEST_REGISTER(invoke_bad_post); + AST_TEST_REGISTER(invoke_not_found); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Stasis HTTP testing", + .load = load_module, + .unload = unload_module, + .nonoptreq = "res_stasis_http", + ); diff --git a/tests/test_strings.c b/tests/test_strings.c index 2a18c1f3b..5e5a17d27 100644 --- a/tests/test_strings.c +++ b/tests/test_strings.c @@ -251,15 +251,78 @@ cleanup: return res; } +AST_TEST_DEFINE(begins_with_test) +{ + switch (cmd) { + case TEST_INIT: + info->name = "begins_with"; + info->category = "/main/strings/"; + info->summary = "Test ast_begins_with"; + info->description = "Test ast_begins_with"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + // prefixes + ast_test_validate(test, 1 == ast_begins_with("foobar", "foobar")); + ast_test_validate(test, 1 == ast_begins_with("foobar", "foo")); + ast_test_validate(test, 1 == ast_begins_with("foobar", "")); + ast_test_validate(test, 1 == ast_begins_with("", "")); + + // not prefixes + ast_test_validate(test, 0 == ast_begins_with("foobar", "bang")); + ast_test_validate(test, 0 == ast_begins_with("foobar", "foobat")); + ast_test_validate(test, 0 == ast_begins_with("boo", "boom")); + ast_test_validate(test, 0 == ast_begins_with("", "blitz")); + + // nothing failed; we're all good! + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(ends_with_test) +{ + switch (cmd) { + case TEST_INIT: + info->name = "ends_with"; + info->category = "/main/strings/"; + info->summary = "Test ast_ends_with"; + info->description = "Test ast_ends_with"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + // prefixes + ast_test_validate(test, 1 == ast_ends_with("foobar", "foobar")); + ast_test_validate(test, 1 == ast_ends_with("foobar", "bar")); + ast_test_validate(test, 1 == ast_ends_with("foobar", "")); + ast_test_validate(test, 1 == ast_ends_with("", "")); + + // not suffixes + ast_test_validate(test, 0 == ast_ends_with("bar", "bbar")); + ast_test_validate(test, 0 == ast_ends_with("foobar", "bang")); + ast_test_validate(test, 0 == ast_ends_with("foobar", "foobat")); + ast_test_validate(test, 0 == ast_ends_with("boo", "boom")); + ast_test_validate(test, 0 == ast_ends_with("", "blitz")); + + // nothing failed; we're all good! + return AST_TEST_PASS; +} + static int unload_module(void) { AST_TEST_UNREGISTER(str_test); + AST_TEST_UNREGISTER(begins_with_test); + AST_TEST_UNREGISTER(ends_with_test); return 0; } static int load_module(void) { AST_TEST_REGISTER(str_test); + AST_TEST_REGISTER(begins_with_test); + AST_TEST_REGISTER(ends_with_test); return AST_MODULE_LOAD_SUCCESS; } |