This patch adds a RESTful HTTP interface to Asterisk.

The API itself is documented using Swagger, a lightweight mechanism for
documenting RESTful API's using JSON. This allows us to use swagger-ui
to provide executable documentation for the API, generate client
bindings in different languages, and generate a lot of the boilerplate
code for implementing the RESTful bindings. The API docs live in the
rest-api/ directory.

The RESTful bindings are generated from the Swagger API docs using a set
of Mustache templates.  The code generator is written in Python, and
uses Pystache. Pystache has no dependencies, and be installed easily
using pip. Code generation code lives in 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.

(closes issue ASTERISK-20891)
Review: https://reviewboard.asterisk.org/r/2376/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@386232 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
David M. Lee 2013-04-22 14:58:53 +00:00
parent 1871017cc6
commit 1c21b8575b
63 changed files with 8605 additions and 59 deletions

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -585,17 +585,34 @@ 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

View File

@ -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.
*
* If the channel is no longer in \c app_stasis, this function does nothing.
*
* \param handler Handler for \c app_stasis
* \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.
*/
void stasis_app_control_continue(struct stasis_app_control *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 res_stasis, this function does nothing.
*
* \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
*/
struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot);
/*! @} */

View File

@ -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 */

View File

@ -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.
*

View File

@ -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 */

View File

@ -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));
}
/*!

View File

@ -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

View File

@ -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;

932
res/res_stasis_http.c Normal file
View File

@ -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,
);

View File

@ -0,0 +1,6 @@
{
global:
LINKER_SYMBOL_PREFIXstasis_http_*;
local:
*;
};

View File

@ -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",
);

View File

@ -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",
);

View File

@ -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",
);

View File

@ -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",
);

View File

@ -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",
);

View File

@ -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",
);

View File

@ -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",
);

View File

@ -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",
);

51
res/stasis_http.make Normal file
View File

@ -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)

View File

@ -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");
}

View File

@ -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 */

View File

@ -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");
}

View File

@ -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 */

View File

@ -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 */
}

View File

@ -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 */

View File

@ -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");
}

View File

@ -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 */

View File

@ -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");
}

View File

@ -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 */

View File

@ -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");
}

View File

@ -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 */

View File

@ -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");
}

View File

@ -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 */

View File

@ -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");
}

View File

@ -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 */

View File

@ -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/

View File

@ -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 = ' '

View File

@ -0,0 +1,4 @@
{{! A partial for the big warning, so it's not in the template itself }}
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !!!!! DO NOT EDIT !!!!!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

View File

@ -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)

261
rest-api-templates/odict.py Normal file
View File

@ -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)

View File

@ -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}}

View File

@ -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}} }
};

View File

@ -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}}

View File

@ -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}}

View File

@ -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}}

View File

@ -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]))]

View File

@ -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))

9
rest-api/README.txt Normal file
View File

@ -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/

View File

@ -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": {}
}
}
}

View File

@ -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
}
}
}
}
}

View File

@ -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"
}
}
}
}
}

View File

@ -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
}
}
}
}
}

View File

@ -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"
}
}
}
}
}

View File

@ -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"
}
}
}
}
}

View File

@ -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"
}
}
}
}
}

View File

@ -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]"
}
}
}
}
}

42
rest-api/resources.json Normal file
View File

@ -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"
}
]
}

View File

@ -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);

547
tests/test_stasis_http.c Normal file
View File

@ -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",
);

View File

@ -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;
}