mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-26 07:24:14 +00:00
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
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
#ifndef _ASTERISK_HTTP_WEBSOCKET_H
|
#ifndef _ASTERISK_HTTP_WEBSOCKET_H
|
||||||
#define _ASTERISK_HTTP_WEBSOCKET_H
|
#define _ASTERISK_HTTP_WEBSOCKET_H
|
||||||
|
|
||||||
|
#include "asterisk/http.h"
|
||||||
#include "asterisk/optional_api.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;
|
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);
|
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 name Name of the sub-protocol to register
|
||||||
* \param callback Callback called when a new connection requesting the sub-protocol is established
|
* \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;});
|
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 name Name of the sub-protocol to unregister
|
||||||
* \param callback Callback that was previously registered with the sub-protocol
|
* \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;});
|
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
|
* \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 */
|
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 */
|
/*! \brief Hashing function for protocols */
|
||||||
static int protocol_hash_fn(const void *obj, const int flags)
|
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);
|
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 */
|
/*! \brief Destructor function for sessions */
|
||||||
static void session_destroy_fn(void *obj)
|
static void session_destroy_fn(void *obj)
|
||||||
{
|
{
|
||||||
@@ -118,38 +145,38 @@ static void session_destroy_fn(void *obj)
|
|||||||
ast_free(session->payload);
|
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;
|
struct websocket_protocol *protocol;
|
||||||
|
|
||||||
if (!protocols) {
|
if (!server->protocols) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ao2_lock(protocols);
|
ao2_lock(server->protocols);
|
||||||
|
|
||||||
/* Ensure a second protocol handler is not registered for the same protocol */
|
/* 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_ref(protocol, -1);
|
||||||
ao2_unlock(protocols);
|
ao2_unlock(server->protocols);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
|
if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
|
||||||
ao2_unlock(protocols);
|
ao2_unlock(server->protocols);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(protocol->name = ast_strdup(name))) {
|
if (!(protocol->name = ast_strdup(name))) {
|
||||||
ao2_ref(protocol, -1);
|
ao2_ref(protocol, -1);
|
||||||
ao2_unlock(protocols);
|
ao2_unlock(server->protocols);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol->callback = callback;
|
protocol->callback = callback;
|
||||||
|
|
||||||
ao2_link_flags(protocols, protocol, OBJ_NOLOCK);
|
ao2_link_flags(server->protocols, protocol, OBJ_NOLOCK);
|
||||||
ao2_unlock(protocols);
|
ao2_unlock(server->protocols);
|
||||||
ao2_ref(protocol, -1);
|
ao2_ref(protocol, -1);
|
||||||
|
|
||||||
ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name);
|
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;
|
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;
|
struct websocket_protocol *protocol;
|
||||||
|
|
||||||
if (!protocols) {
|
if (!(protocol = ao2_find(server->protocols, name, OBJ_KEY))) {
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(protocol = ao2_find(protocols, name, OBJ_KEY))) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +197,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_w
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ao2_unlink(protocols, protocol);
|
ao2_unlink(server->protocols, protocol);
|
||||||
ao2_ref(protocol, -1);
|
ao2_ref(protocol, -1);
|
||||||
|
|
||||||
ast_verb(2, "WebSocket unregistered sub-protocol '%s'\n", name);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! \brief Callback that is executed everytime an HTTP request is received by this module */
|
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)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
struct ast_variable *v;
|
struct ast_variable *v;
|
||||||
char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL;
|
char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL;
|
||||||
int version = 0, flags = 1;
|
int version = 0, flags = 1;
|
||||||
struct websocket_protocol *protocol_handler = NULL;
|
struct websocket_protocol *protocol_handler = NULL;
|
||||||
struct ast_websocket *session;
|
struct ast_websocket *session;
|
||||||
|
struct ast_websocket_server *server;
|
||||||
|
|
||||||
/* Upgrade requests are only permitted on GET methods */
|
/* Upgrade requests are only permitted on GET methods */
|
||||||
if (method != AST_HTTP_GET) {
|
if (method != AST_HTTP_GET) {
|
||||||
@@ -489,6 +512,8 @@ static int websocket_callback(struct ast_tcptls_session_instance *ser, const str
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server = urih->data;
|
||||||
|
|
||||||
/* Get the minimum headers required to satisfy our needs */
|
/* Get the minimum headers required to satisfy our needs */
|
||||||
for (v = headers; v; v = v->next) {
|
for (v = headers; v; v = v->next) {
|
||||||
if (!strcasecmp(v->name, "Upgrade")) {
|
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 */
|
/* Iterate through the requested protocols trying to find one that we have a handler for */
|
||||||
while ((protocol = strsep(&requested_protocols, ","))) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -619,7 +644,7 @@ static int websocket_callback(struct ast_tcptls_session_instance *ser, const str
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct ast_http_uri websocketuri = {
|
static struct ast_http_uri websocketuri = {
|
||||||
.callback = websocket_callback,
|
.callback = ast_websocket_uri_cb,
|
||||||
.description = "Asterisk HTTP WebSocket",
|
.description = "Asterisk HTTP WebSocket",
|
||||||
.uri = "ws",
|
.uri = "ws",
|
||||||
.has_subtree = 0,
|
.has_subtree = 0,
|
||||||
@@ -664,9 +689,30 @@ end:
|
|||||||
ast_websocket_unref(session);
|
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)
|
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_http_uri_link(&websocketuri);
|
||||||
ast_websocket_add_protocol("echo", websocket_echo_callback);
|
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_websocket_remove_protocol("echo", websocket_echo_callback);
|
||||||
ast_http_uri_unlink(&websocketuri);
|
ast_http_uri_unlink(&websocketuri);
|
||||||
ao2_ref(protocols, -1);
|
ao2_ref(websocketuri.data, -1);
|
||||||
protocols = NULL;
|
websocketuri.data = NULL;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user