Clean up and prepare for HTTP

This commit is contained in:
Luciano Iam 2020-04-09 15:57:09 +02:00 committed by Robin Gareus
parent 3da0cf2a31
commit 40520a6dc6
No known key found for this signature in database
GPG key ID: A090BCE02CF57F04
5 changed files with 163 additions and 56 deletions

View file

@ -35,7 +35,7 @@ using namespace ArdourSurface;
#include "pbd/abstract_ui.cc" // instantiate template #include "pbd/abstract_ui.cc" // instantiate template
ArdourWebsockets::ArdourWebsockets (Session& s) ArdourWebsockets::ArdourWebsockets (Session& s)
: ControlProtocol (s, X_ (SURFACE_NAME)) : ControlProtocol (s, X_ (surface_name))
, AbstractUI<ArdourWebsocketsUIRequest> (name ()) , AbstractUI<ArdourWebsocketsUIRequest> (name ())
, _strips (*this) , _strips (*this)
, _globals (*this) , _globals (*this)

View file

@ -39,11 +39,11 @@
#include "server.h" #include "server.h"
#include "strips.h" #include "strips.h"
#define SURFACE_NAME "WebSockets Server (Experimental)"
#define SURFACE_ID "uri://ardour.org/surfaces/ardour_websockets:0"
namespace ArdourSurface namespace ArdourSurface
{ {
const char * const surface_name = "WebSockets Server (Experimental)";
const char * const surface_id = "uri://ardour.org/surfaces/ardour_websockets:0";
struct ArdourWebsocketsUIRequest : public BaseUI::BaseRequestObject { struct ArdourWebsocketsUIRequest : public BaseUI::BaseRequestObject {
public: public:
ArdourWebsocketsUIRequest () {} ArdourWebsocketsUIRequest () {}

View file

@ -56,8 +56,8 @@ ardour_websockets_request_buffer_factory (uint32_t num_requests)
} }
static ControlProtocolDescriptor ardour_websockets_descriptor = { static ControlProtocolDescriptor ardour_websockets_descriptor = {
/*name : */ SURFACE_NAME, /*name : */ surface_name,
/*id : */ SURFACE_ID, /*id : */ surface_id,
/*ptr : */ 0, /*ptr : */ 0,
/*module : */ 0, /*module : */ 0,
/*mandatory : */ 0, /*mandatory : */ 0,

View file

@ -38,6 +38,8 @@
#endif #endif
#endif #endif
#define MAX_INDEX_SIZE 65536
using namespace Glib; using namespace Glib;
WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface) WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface)
@ -49,7 +51,6 @@ WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface)
memset (&proto, 0, sizeof (lws_protocols)); memset (&proto, 0, sizeof (lws_protocols));
proto.name = "lws-ardour"; proto.name = "lws-ardour";
proto.callback = WebsocketsServer::lws_callback; proto.callback = WebsocketsServer::lws_callback;
proto.per_session_data_size = 0;
proto.rx_buffer_size = 0; proto.rx_buffer_size = 0;
proto.id = 0; proto.id = 0;
proto.user = 0; proto.user = 0;
@ -59,9 +60,29 @@ WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface)
_lws_proto[0] = proto; _lws_proto[0] = proto;
memset (&_lws_proto[1], 0, sizeof (lws_protocols)); memset (&_lws_proto[1], 0, sizeof (lws_protocols));
// '/' is served by a static index.html file in the surface data directory
// inside it there is a 'builtin' subdirectory that contains all built-in
// surfaces so there is no need to create a dedicated mount point for them
// list of surfaces is available as a dynamically generated json file
memset (&_lws_mnt_index, 0, sizeof (lws_http_mount));
_lws_mnt_index.mountpoint = "/";
_lws_mnt_index.mountpoint_len = strlen (_lws_mnt_index.mountpoint);
_lws_mnt_index.origin_protocol = LWSMPRO_FILE;
_lws_mnt_index.origin = _resources.index_dir ().c_str ();
// user defined surfaces in the user config directory
memset (&_lws_mnt_user, 0, sizeof (lws_http_mount));
_lws_mnt_user.mountpoint = "/user";
_lws_mnt_user.mountpoint_len = strlen (_lws_mnt_user.mountpoint);
_lws_mnt_user.origin_protocol = LWSMPRO_FILE;
_lws_mnt_user.origin = _resources.user_dir ().c_str ();
_lws_mnt_index.mount_next = &_lws_mnt_user;
memset (&_lws_info, 0, sizeof (lws_context_creation_info)); memset (&_lws_info, 0, sizeof (lws_context_creation_info));
_lws_info.port = WEBSOCKET_LISTEN_PORT; _lws_info.port = WEBSOCKET_LISTEN_PORT;
_lws_info.protocols = _lws_proto; _lws_info.protocols = _lws_proto;
_lws_info.mounts = &_lws_mnt_index;
_lws_info.uid = -1; _lws_info.uid = -1;
_lws_info.gid = -1; _lws_info.gid = -1;
_lws_info.user = this; _lws_info.user = this;
@ -141,7 +162,7 @@ WebsocketsServer::update_all_clients (const NodeState& state, bool force)
} }
} }
void int
WebsocketsServer::add_poll_fd (struct lws_pollargs* pa) WebsocketsServer::add_poll_fd (struct lws_pollargs* pa)
{ {
/* fd can be SOCKET or int depending platform */ /* fd can be SOCKET or int depending platform */
@ -168,14 +189,16 @@ WebsocketsServer::add_poll_fd (struct lws_pollargs* pa)
ctx.wg_iosrc = Glib::RefPtr<Glib::IOSource> (0); ctx.wg_iosrc = Glib::RefPtr<Glib::IOSource> (0);
_fd_ctx[fd] = ctx; _fd_ctx[fd] = ctx;
return 0;
} }
void int
WebsocketsServer::mod_poll_fd (struct lws_pollargs* pa) WebsocketsServer::mod_poll_fd (struct lws_pollargs* pa)
{ {
LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd); LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd);
if (it == _fd_ctx.end ()) { if (it == _fd_ctx.end ()) {
return; return 1;
} }
it->second.lws_pfd.events = pa->events; it->second.lws_pfd.events = pa->events;
@ -189,7 +212,7 @@ WebsocketsServer::mod_poll_fd (struct lws_pollargs* pa)
if (it->second.wg_iosrc) { if (it->second.wg_iosrc) {
/* already polling for write */ /* already polling for write */
return; return 0;
} }
RefPtr<IOSource> wg_iosrc = it->second.g_channel->create_watch (Glib::IO_OUT); RefPtr<IOSource> wg_iosrc = it->second.g_channel->create_watch (Glib::IO_OUT);
@ -202,14 +225,16 @@ WebsocketsServer::mod_poll_fd (struct lws_pollargs* pa)
it->second.wg_iosrc = Glib::RefPtr<Glib::IOSource> (0); it->second.wg_iosrc = Glib::RefPtr<Glib::IOSource> (0);
} }
} }
return 0;
} }
void int
WebsocketsServer::del_poll_fd (struct lws_pollargs* pa) WebsocketsServer::del_poll_fd (struct lws_pollargs* pa)
{ {
LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd); LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd);
if (it == _fd_ctx.end ()) { if (it == _fd_ctx.end ()) {
return; return 1;
} }
it->second.rg_iosrc->destroy (); it->second.rg_iosrc->destroy ();
@ -219,30 +244,36 @@ WebsocketsServer::del_poll_fd (struct lws_pollargs* pa)
} }
_fd_ctx.erase (it); _fd_ctx.erase (it);
return 0;
} }
void int
WebsocketsServer::add_client (Client wsi) WebsocketsServer::add_client (Client wsi)
{ {
_client_ctx.emplace (wsi, ClientContext (wsi)); _client_ctx.emplace (wsi, ClientContext (wsi));
dispatcher ().update_all_nodes (wsi); // send all state dispatcher ().update_all_nodes (wsi); // send all state
return 0;
} }
void int
WebsocketsServer::del_client (Client wsi) WebsocketsServer::del_client (Client wsi)
{ {
ClientContextMap::iterator it = _client_ctx.find (wsi); ClientContextMap::iterator it = _client_ctx.find (wsi);
if (it != _client_ctx.end ()) { if (it != _client_ctx.end ()) {
_client_ctx.erase (it); _client_ctx.erase (it);
} }
return 0;
} }
void int
WebsocketsServer::recv_client (Client wsi, void* buf, size_t len) WebsocketsServer::recv_client (Client wsi, void* buf, size_t len)
{ {
NodeStateMessage msg (buf, len); NodeStateMessage msg (buf, len);
if (!msg.is_valid ()) { if (!msg.is_valid ()) {
return; return 1;
} }
#ifndef NDEBUG #ifndef NDEBUG
@ -251,26 +282,28 @@ WebsocketsServer::recv_client (Client wsi, void* buf, size_t len)
ClientContextMap::iterator it = _client_ctx.find (wsi); ClientContextMap::iterator it = _client_ctx.find (wsi);
if (it == _client_ctx.end ()) { if (it == _client_ctx.end ()) {
return; return 1;
} }
/* avoid echo */ /* avoid echo */
it->second.update_state (msg.state ()); it->second.update_state (msg.state ());
dispatcher ().dispatch (wsi, msg); dispatcher ().dispatch (wsi, msg);
return 0;
} }
void int
WebsocketsServer::write_client (Client wsi) WebsocketsServer::write_client (Client wsi)
{ {
ClientContextMap::iterator it = _client_ctx.find (wsi); ClientContextMap::iterator it = _client_ctx.find (wsi);
if (it == _client_ctx.end ()) { if (it == _client_ctx.end ()) {
return; return 1;
} }
ClientOutputBuffer& pending = it->second.output_buf (); ClientOutputBuffer& pending = it->second.output_buf ();
if (pending.empty ()) { if (pending.empty ()) {
return; return 0;
} }
/* one lws_write() call per LWS_CALLBACK_SERVER_WRITEABLE callback */ /* one lws_write() call per LWS_CALLBACK_SERVER_WRITEABLE callback */
@ -279,13 +312,15 @@ WebsocketsServer::write_client (Client wsi)
pending.pop_front (); pending.pop_front ();
unsigned char out_buf[1024]; unsigned char out_buf[1024];
size_t len = msg.serialize (out_buf + LWS_PRE, 1024 - LWS_PRE); int len = msg.serialize (out_buf + LWS_PRE, 1024 - LWS_PRE);
if (len > 0) { if (len > 0) {
#ifndef NDEBUG #ifndef NDEBUG
std::cerr << "TX " << msg.state ().debug_str () << std::endl; std::cerr << "TX " << msg.state ().debug_str () << std::endl;
#endif #endif
lws_write (wsi, out_buf + LWS_PRE, len, LWS_WRITE_TEXT); if (lws_write (wsi, out_buf + LWS_PRE, len, LWS_WRITE_TEXT) != len) {
return 1;
}
} else { } else {
PBD::error << "ArdourWebsockets: cannot serialize message" << endmsg; PBD::error << "ArdourWebsockets: cannot serialize message" << endmsg;
} }
@ -293,21 +328,84 @@ WebsocketsServer::write_client (Client wsi)
if (!pending.empty ()) { if (!pending.empty ()) {
lws_callback_on_writable (wsi); lws_callback_on_writable (wsi);
} }
return 0;
} }
void int
WebsocketsServer::reject_http_client (Client wsi) WebsocketsServer::send_index_hdr (Client wsi)
{ {
const char *html_body = "<p>This URL is not meant to be accessed via HTTP; for example using" char url[1024];
" a web browser. Refer to Ardour documentation for further information.</p>";
lws_return_http_status (wsi, 404, html_body); if (lws_hdr_copy (wsi, url, 1024, WSI_TOKEN_GET_URI) < 0) {
return 1;
}
if (strcmp (url, "/index.json") != 0) {
lws_return_http_status (wsi, 404, 0);
return 1;
}
unsigned char out_buf[1024],
*start = out_buf,
*p = start,
*end = &out_buf[sizeof(out_buf) - 1];
#if LWS_LIBRARY_VERSION_MAJOR >= 3
lws_add_http_common_headers (wsi, HTTP_STATUS_OK, "application/json",
LWS_ILLEGAL_HTTP_CONTENT_LEN, &p, end);
lws_add_http_header_by_token (wsi, WSI_TOKEN_HTTP_CACHE_CONTROL,
reinterpret_cast<const unsigned char*> ("no-store"), 8, &p, end);
if (lws_finalize_write_http_header (wsi, start, &p, end) != 0) {
return 1;
}
#else
lws_add_http_header_status (wsi, HTTP_STATUS_OK, &p, end);
lws_add_http_header_by_token (wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
reinterpret_cast<const unsigned char*> ("application/json"), 16, &p, end);
lws_add_http_header_by_token (wsi, WSI_TOKEN_CONNECTION,
reinterpret_cast<const unsigned char*> ("close"), 5, &p, end);
lws_add_http_header_by_token (wsi, WSI_TOKEN_HTTP_CACHE_CONTROL,
reinterpret_cast<const unsigned char*> ("no-store"), 8, &p, end);
lws_finalize_http_header (wsi, &p, end);
int len = p - start;
if (lws_write (wsi, start, len, LWS_WRITE_HTTP_HEADERS) != len) {
return 1;
}
#endif
lws_callback_on_writable (wsi);
return 0;
}
int
WebsocketsServer::send_index_body (Client wsi)
{
std::string index = _resources.scan ();
char body[MAX_INDEX_SIZE];
//lws_strncpy (body, index.c_str (), sizeof(body));
memset (body, 0, sizeof (body));
strncpy (body, index.c_str (), sizeof(body) - 1);
int len = strlen (body);
if (lws_write (wsi, reinterpret_cast<unsigned char*> (body), len, LWS_WRITE_HTTP) != len) {
return 1;
}
lws_http_transaction_completed (wsi);
return -1; // end connection
} }
bool bool
WebsocketsServer::io_handler (Glib::IOCondition ioc, lws_sockfd_type fd) WebsocketsServer::io_handler (Glib::IOCondition ioc, lws_sockfd_type fd)
{ {
/* IO_IN=1, IO_PRI=2, IO_ERR=8, IO_HUP=16 */ /* IO_IN=1, IO_PRI=2, IO_OUT=4, IO_ERR=8, IO_HUP=16 */
//printf ("io_handler ioc = %d\n", ioc);
LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (fd); LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (fd);
if (it == _fd_ctx.end ()) { if (it == _fd_ctx.end ()) {
@ -317,9 +415,7 @@ WebsocketsServer::io_handler (Glib::IOCondition ioc, lws_sockfd_type fd)
struct lws_pollfd* lws_pfd = &it->second.lws_pfd; struct lws_pollfd* lws_pfd = &it->second.lws_pfd;
lws_pfd->revents = ioc_to_events (ioc); lws_pfd->revents = ioc_to_events (ioc);
if (lws_service_fd (_lws_context, lws_pfd) < 0) { lws_service_fd (_lws_context, lws_pfd);
return false;
}
return ioc & (Glib::IO_IN | Glib::IO_OUT); return ioc & (Glib::IO_IN | Glib::IO_OUT);
} }
@ -368,41 +464,48 @@ WebsocketsServer::lws_callback (struct lws* wsi, enum lws_callback_reasons reaso
{ {
void* ctx_userdata = lws_context_user (lws_get_context (wsi)); void* ctx_userdata = lws_context_user (lws_get_context (wsi));
WebsocketsServer* server = static_cast<WebsocketsServer*> (ctx_userdata); WebsocketsServer* server = static_cast<WebsocketsServer*> (ctx_userdata);
int rc;
switch (reason) { switch (reason) {
case LWS_CALLBACK_ADD_POLL_FD: case LWS_CALLBACK_ADD_POLL_FD:
server->add_poll_fd (static_cast<struct lws_pollargs*> (in)); rc = server->add_poll_fd (static_cast<struct lws_pollargs*> (in));
break; break;
case LWS_CALLBACK_CHANGE_MODE_POLL_FD: case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
server->mod_poll_fd (static_cast<struct lws_pollargs*> (in)); rc = server->mod_poll_fd (static_cast<struct lws_pollargs*> (in));
break; break;
case LWS_CALLBACK_DEL_POLL_FD: case LWS_CALLBACK_DEL_POLL_FD:
server->del_poll_fd (static_cast<struct lws_pollargs*> (in)); rc = server->del_poll_fd (static_cast<struct lws_pollargs*> (in));
break; break;
case LWS_CALLBACK_ESTABLISHED: case LWS_CALLBACK_ESTABLISHED:
server->add_client (wsi); rc = server->add_client (wsi);
break; break;
case LWS_CALLBACK_CLOSED: case LWS_CALLBACK_CLOSED:
server->del_client (wsi); rc = server->del_client (wsi);
break; break;
case LWS_CALLBACK_RECEIVE: case LWS_CALLBACK_RECEIVE:
server->recv_client (wsi, in, len); rc = server->recv_client (wsi, in, len);
break; break;
case LWS_CALLBACK_SERVER_WRITEABLE: case LWS_CALLBACK_SERVER_WRITEABLE:
server->write_client (wsi); rc = server->write_client (wsi);
break; break;
/* will be called only if the requested url is not fulfilled
by the any of the mount configurations (index, builtin, user) */
case LWS_CALLBACK_HTTP: case LWS_CALLBACK_HTTP:
server->reject_http_client (wsi); rc = server->send_index_hdr (wsi);
return 1;
break; break;
case LWS_CALLBACK_HTTP_WRITEABLE:
rc = server->send_index_body (wsi);
break;
case LWS_CALLBACK_CLOSED_HTTP:
case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED: case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:
@ -421,20 +524,18 @@ WebsocketsServer::lws_callback (struct lws* wsi, enum lws_callback_reasons reaso
case LWS_CALLBACK_HTTP_CONFIRM_UPGRADE: case LWS_CALLBACK_HTTP_CONFIRM_UPGRADE:
#endif #endif
#endif #endif
/* do nothing but keep connection alive */
rc = 0;
break; break;
/* TODO: handle HTTP connections.
* Serve static ctrl-surface pages, JS, CSS etc.
*/
default: default:
#ifndef NDEBUG #ifndef NDEBUG
/* see libwebsockets.h lws_callback_reasons */ /* see libwebsockets.h lws_callback_reasons */
std::cerr << "LWS: unhandled callback " << reason << std::endl; std::cerr << "LWS: unhandled callback " << reason << std::endl;
#endif #endif
return -1; rc = -1;
break; break;
} }
return 0; return rc;
} }

View file

@ -38,6 +38,7 @@
#include "component.h" #include "component.h"
#include "message.h" #include "message.h"
#include "state.h" #include "state.h"
#include "resources.h"
#define WEBSOCKET_LISTEN_PORT 9000 #define WEBSOCKET_LISTEN_PORT 9000
@ -62,6 +63,8 @@ public:
private: private:
struct lws_protocols _lws_proto[2]; struct lws_protocols _lws_proto[2];
struct lws_http_mount _lws_mnt_index;
struct lws_http_mount _lws_mnt_user;
struct lws_context_creation_info _lws_info; struct lws_context_creation_info _lws_info;
struct lws_context* _lws_context; struct lws_context* _lws_context;
@ -73,15 +76,18 @@ private:
typedef boost::unordered_map<Client, ClientContext> ClientContextMap; typedef boost::unordered_map<Client, ClientContext> ClientContextMap;
ClientContextMap _client_ctx; ClientContextMap _client_ctx;
void add_poll_fd (struct lws_pollargs*); ServerResources _resources;
void mod_poll_fd (struct lws_pollargs*);
void del_poll_fd (struct lws_pollargs*);
void add_client (Client); int add_poll_fd (struct lws_pollargs*);
void del_client (Client); int mod_poll_fd (struct lws_pollargs*);
void recv_client (Client, void* buf, size_t len); int del_poll_fd (struct lws_pollargs*);
void write_client (Client);
void reject_http_client (Client); int add_client (Client);
int del_client (Client);
int recv_client (Client, void*, size_t);
int write_client (Client);
int send_index_hdr (Client);
int send_index_body (Client);
bool io_handler (Glib::IOCondition, lws_sockfd_type); bool io_handler (Glib::IOCondition, lws_sockfd_type);