Skip to content
Snippets Groups Projects
Commit 9c696e66 authored by David M. Lee's avatar David M. Lee
Browse files

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
parent aff127a7
No related branches found
No related tags found
No related merge requests found
......@@ -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
*
......
......@@ -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;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment