From 9c696e665fde0e94b6d6aac20cf40bcb35ac94e6 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Thu, 18 Apr 2013 17:30:28 +0000 Subject: Allow WebSocket connections on more URL's This patch adds the concept of ast_websocket_server to res_http_websocket, allowing WebSocket connections on URL's more more than /ws. The existing funcitons for managing the WebSocket subprotocols on /ws still work, so this patch should be completely backward compatible. (closes issue ASTERISK-21279) Review: https://reviewboard.asterisk.org/r/2453/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@386020 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- include/asterisk/http_websocket.h | 54 +++++++++++++++++++-- res/res_http_websocket.c | 98 ++++++++++++++++++++++++++++----------- 2 files changed, 123 insertions(+), 29 deletions(-) diff --git a/include/asterisk/http_websocket.h b/include/asterisk/http_websocket.h index d59bc25cd..82c7ad8b6 100644 --- a/include/asterisk/http_websocket.h +++ b/include/asterisk/http_websocket.h @@ -19,6 +19,7 @@ #ifndef _ASTERISK_HTTP_WEBSOCKET_H #define _ASTERISK_HTTP_WEBSOCKET_H +#include "asterisk/http.h" #include "asterisk/optional_api.h" /*! @@ -40,7 +41,13 @@ enum ast_websocket_opcode { }; /*! - * \brief Opaque structure for WebSocket sessions + * \brief Opaque structure for WebSocket server. + * \since 12 + */ +struct ast_websocket_server; + +/*! + * \brief Opaque structure for WebSocket sessions. */ struct ast_websocket; @@ -58,7 +65,24 @@ struct ast_websocket; typedef void (*ast_websocket_callback)(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers); /*! - * \brief Add a sub-protocol handler to the server + * \brief Creates a \ref websocket_server + * + * \retval New \ref websocket_server instance + * \retval \c NULL on error + * \since 12 + */ +struct ast_websocket_server *ast_websocket_server_create(void); + +/*! + * \brief Callback suitable for use with a \ref ast_http_uri. + * + * Set the data field of the ast_http_uri to \ref ast_websocket_server. + * \since 12 + */ +int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers); + +/*! + * \brief Add a sub-protocol handler to the default /ws server * * \param name Name of the sub-protocol to register * \param callback Callback called when a new connection requesting the sub-protocol is established @@ -69,7 +93,7 @@ typedef void (*ast_websocket_callback)(struct ast_websocket *session, struct ast AST_OPTIONAL_API(int, ast_websocket_add_protocol, (const char *name, ast_websocket_callback callback), {return -1;}); /*! - * \brief Remove a sub-protocol handler from the server + * \brief Remove a sub-protocol handler from the default /ws server. * * \param name Name of the sub-protocol to unregister * \param callback Callback that was previously registered with the sub-protocol @@ -79,6 +103,30 @@ AST_OPTIONAL_API(int, ast_websocket_add_protocol, (const char *name, ast_websock */ AST_OPTIONAL_API(int, ast_websocket_remove_protocol, (const char *name, ast_websocket_callback callback), {return -1;}); +/*! + * \brief Add a sub-protocol handler to the given server. + * + * \param name Name of the sub-protocol to register + * \param callback Callback called when a new connection requesting the sub-protocol is established + * + * \retval 0 success + * \retval -1 if sub-protocol handler could not be registered + * \since 12 + */ +AST_OPTIONAL_API(int, ast_websocket_server_add_protocol, (struct ast_websocket_server *server, const char *name, ast_websocket_callback callback), {return -1;}); + +/*! + * \brief Remove a sub-protocol handler from the given server. + * + * \param name Name of the sub-protocol to unregister + * \param callback Callback that was previously registered with the sub-protocol + * + * \retval 0 success + * \retval -1 if sub-protocol was not found or if callback did not match + * \since 12 + */ +AST_OPTIONAL_API(int, ast_websocket_server_remove_protocol, (struct ast_websocket_server *server, const char *name, ast_websocket_callback callback), {return -1;}); + /*! * \brief Read a WebSocket frame and handle it * diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c index 15ff8fa45..d96564af8 100644 --- a/res/res_http_websocket.c +++ b/res/res_http_websocket.c @@ -77,9 +77,6 @@ struct websocket_protocol { ast_websocket_callback callback; /*!< Callback called when a new session is established */ }; -/*! \brief Container for registered protocols */ -static struct ao2_container *protocols; - /*! \brief Hashing function for protocols */ static int protocol_hash_fn(const void *obj, const int flags) { @@ -105,6 +102,36 @@ static void protocol_destroy_fn(void *obj) ast_free(protocol->name); } +/*! \brief Structure for a WebSocket server */ +struct ast_websocket_server { + struct ao2_container *protocols; /*!< Container for registered protocols */ +}; + +static void websocket_server_dtor(void *obj) +{ + struct ast_websocket_server *server = obj; + ao2_cleanup(server->protocols); + server->protocols = NULL; +} + +struct ast_websocket_server *ast_websocket_server_create(void) +{ + RAII_VAR(struct ast_websocket_server *, server, NULL, ao2_cleanup); + + server = ao2_alloc(sizeof(*server), websocket_server_dtor); + if (!server) { + return NULL; + } + + server->protocols = ao2_container_alloc(MAX_PROTOCOL_BUCKETS, protocol_hash_fn, protocol_cmp_fn); + if (!server->protocols) { + return NULL; + } + + ao2_ref(server, +1); + return server; +} + /*! \brief Destructor function for sessions */ static void session_destroy_fn(void *obj) { @@ -118,38 +145,38 @@ static void session_destroy_fn(void *obj) ast_free(session->payload); } -int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_websocket_callback callback) +int AST_OPTIONAL_API_NAME(ast_websocket_server_add_protocol)(struct ast_websocket_server *server, const char *name, ast_websocket_callback callback) { struct websocket_protocol *protocol; - if (!protocols) { + if (!server->protocols) { return -1; } - ao2_lock(protocols); + ao2_lock(server->protocols); /* Ensure a second protocol handler is not registered for the same protocol */ - if ((protocol = ao2_find(protocols, name, OBJ_KEY | OBJ_NOLOCK))) { + if ((protocol = ao2_find(server->protocols, name, OBJ_KEY | OBJ_NOLOCK))) { ao2_ref(protocol, -1); - ao2_unlock(protocols); + ao2_unlock(server->protocols); return -1; } if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) { - ao2_unlock(protocols); + ao2_unlock(server->protocols); return -1; } if (!(protocol->name = ast_strdup(name))) { ao2_ref(protocol, -1); - ao2_unlock(protocols); + ao2_unlock(server->protocols); return -1; } protocol->callback = callback; - ao2_link_flags(protocols, protocol, OBJ_NOLOCK); - ao2_unlock(protocols); + ao2_link_flags(server->protocols, protocol, OBJ_NOLOCK); + ao2_unlock(server->protocols); ao2_ref(protocol, -1); ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name); @@ -157,15 +184,11 @@ int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_webs return 0; } -int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_websocket_callback callback) +int AST_OPTIONAL_API_NAME(ast_websocket_server_remove_protocol)(struct ast_websocket_server *server, const char *name, ast_websocket_callback callback) { struct websocket_protocol *protocol; - if (!protocols) { - return -1; - } - - if (!(protocol = ao2_find(protocols, name, OBJ_KEY))) { + if (!(protocol = ao2_find(server->protocols, name, OBJ_KEY))) { return -1; } @@ -174,7 +197,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_w return -1; } - ao2_unlink(protocols, protocol); + ao2_unlink(server->protocols, protocol); ao2_ref(protocol, -1); ast_verb(2, "WebSocket unregistered sub-protocol '%s'\n", name); @@ -474,14 +497,14 @@ int AST_OPTIONAL_API_NAME(ast_websocket_read)(struct ast_websocket *session, cha return 0; } -/*! \brief Callback that is executed everytime an HTTP request is received by this module */ -static int websocket_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_vars, struct ast_variable *headers) +int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers) { struct ast_variable *v; char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL; int version = 0, flags = 1; struct websocket_protocol *protocol_handler = NULL; struct ast_websocket *session; + struct ast_websocket_server *server; /* Upgrade requests are only permitted on GET methods */ if (method != AST_HTTP_GET) { @@ -489,6 +512,8 @@ static int websocket_callback(struct ast_tcptls_session_instance *ser, const str return -1; } + server = urih->data; + /* Get the minimum headers required to satisfy our needs */ for (v = headers; v; v = v->next) { if (!strcasecmp(v->name, "Upgrade")) { @@ -533,7 +558,7 @@ static int websocket_callback(struct ast_tcptls_session_instance *ser, const str /* Iterate through the requested protocols trying to find one that we have a handler for */ while ((protocol = strsep(&requested_protocols, ","))) { - if ((protocol_handler = ao2_find(protocols, ast_strip(protocol), OBJ_KEY))) { + if ((protocol_handler = ao2_find(server->protocols, ast_strip(protocol), OBJ_KEY))) { break; } } @@ -619,7 +644,7 @@ static int websocket_callback(struct ast_tcptls_session_instance *ser, const str } static struct ast_http_uri websocketuri = { - .callback = websocket_callback, + .callback = ast_websocket_uri_cb, .description = "Asterisk HTTP WebSocket", .uri = "ws", .has_subtree = 0, @@ -664,9 +689,30 @@ end: ast_websocket_unref(session); } +int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_websocket_callback callback) +{ + struct ast_websocket_server *ws_server = websocketuri.data; + if (!ws_server) { + return -1; + } + return ast_websocket_server_add_protocol(ws_server, name, callback); +} + +int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_websocket_callback callback) +{ + struct ast_websocket_server *ws_server = websocketuri.data; + if (!ws_server) { + return -1; + } + return ast_websocket_server_remove_protocol(ws_server, name, callback); +} + static int load_module(void) { - protocols = ao2_container_alloc(MAX_PROTOCOL_BUCKETS, protocol_hash_fn, protocol_cmp_fn); + websocketuri.data = ast_websocket_server_create(); + if (!websocketuri.data) { + return AST_MODULE_LOAD_FAILURE; + } ast_http_uri_link(&websocketuri); ast_websocket_add_protocol("echo", websocket_echo_callback); @@ -677,8 +723,8 @@ static int unload_module(void) { ast_websocket_remove_protocol("echo", websocket_echo_callback); ast_http_uri_unlink(&websocketuri); - ao2_ref(protocols, -1); - protocols = NULL; + ao2_ref(websocketuri.data, -1); + websocketuri.data = NULL; return 0; } -- cgit v1.2.3