From 40520a6dc61de135c09cdfb0097910b766ccb6ee Mon Sep 17 00:00:00 2001 From: Luciano Iam Date: Thu, 9 Apr 2020 15:57:09 +0200 Subject: [PATCH] Clean up and prepare for HTTP --- libs/surfaces/websockets/ardour_websockets.cc | 2 +- libs/surfaces/websockets/ardour_websockets.h | 6 +- libs/surfaces/websockets/interface.cc | 4 +- libs/surfaces/websockets/server.cc | 185 ++++++++++++++---- libs/surfaces/websockets/server.h | 22 ++- 5 files changed, 163 insertions(+), 56 deletions(-) diff --git a/libs/surfaces/websockets/ardour_websockets.cc b/libs/surfaces/websockets/ardour_websockets.cc index 38c7849839..2f2e9aff9c 100644 --- a/libs/surfaces/websockets/ardour_websockets.cc +++ b/libs/surfaces/websockets/ardour_websockets.cc @@ -35,7 +35,7 @@ using namespace ArdourSurface; #include "pbd/abstract_ui.cc" // instantiate template ArdourWebsockets::ArdourWebsockets (Session& s) - : ControlProtocol (s, X_ (SURFACE_NAME)) + : ControlProtocol (s, X_ (surface_name)) , AbstractUI (name ()) , _strips (*this) , _globals (*this) diff --git a/libs/surfaces/websockets/ardour_websockets.h b/libs/surfaces/websockets/ardour_websockets.h index 0b61032e80..21d444183a 100644 --- a/libs/surfaces/websockets/ardour_websockets.h +++ b/libs/surfaces/websockets/ardour_websockets.h @@ -39,11 +39,11 @@ #include "server.h" #include "strips.h" -#define SURFACE_NAME "WebSockets Server (Experimental)" -#define SURFACE_ID "uri://ardour.org/surfaces/ardour_websockets:0" - 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 { public: ArdourWebsocketsUIRequest () {} diff --git a/libs/surfaces/websockets/interface.cc b/libs/surfaces/websockets/interface.cc index 462ac79cb8..fc426ac56b 100644 --- a/libs/surfaces/websockets/interface.cc +++ b/libs/surfaces/websockets/interface.cc @@ -56,8 +56,8 @@ ardour_websockets_request_buffer_factory (uint32_t num_requests) } static ControlProtocolDescriptor ardour_websockets_descriptor = { - /*name : */ SURFACE_NAME, - /*id : */ SURFACE_ID, + /*name : */ surface_name, + /*id : */ surface_id, /*ptr : */ 0, /*module : */ 0, /*mandatory : */ 0, diff --git a/libs/surfaces/websockets/server.cc b/libs/surfaces/websockets/server.cc index beab8fb254..a64d42c00b 100644 --- a/libs/surfaces/websockets/server.cc +++ b/libs/surfaces/websockets/server.cc @@ -38,6 +38,8 @@ #endif #endif +#define MAX_INDEX_SIZE 65536 + using namespace Glib; WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface) @@ -49,7 +51,6 @@ WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface) memset (&proto, 0, sizeof (lws_protocols)); proto.name = "lws-ardour"; proto.callback = WebsocketsServer::lws_callback; - proto.per_session_data_size = 0; proto.rx_buffer_size = 0; proto.id = 0; proto.user = 0; @@ -59,9 +60,29 @@ WebsocketsServer::WebsocketsServer (ArdourSurface::ArdourWebsockets& surface) _lws_proto[0] = proto; 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)); _lws_info.port = WEBSOCKET_LISTEN_PORT; _lws_info.protocols = _lws_proto; + _lws_info.mounts = &_lws_mnt_index; _lws_info.uid = -1; _lws_info.gid = -1; _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) { /* 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 (0); _fd_ctx[fd] = ctx; + + return 0; } -void +int WebsocketsServer::mod_poll_fd (struct lws_pollargs* pa) { LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd); if (it == _fd_ctx.end ()) { - return; + return 1; } it->second.lws_pfd.events = pa->events; @@ -189,7 +212,7 @@ WebsocketsServer::mod_poll_fd (struct lws_pollargs* pa) if (it->second.wg_iosrc) { /* already polling for write */ - return; + return 0; } RefPtr 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 (0); } } + + return 0; } -void +int WebsocketsServer::del_poll_fd (struct lws_pollargs* pa) { LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (pa->fd); if (it == _fd_ctx.end ()) { - return; + return 1; } it->second.rg_iosrc->destroy (); @@ -219,30 +244,36 @@ WebsocketsServer::del_poll_fd (struct lws_pollargs* pa) } _fd_ctx.erase (it); + + return 0; } -void +int WebsocketsServer::add_client (Client wsi) { _client_ctx.emplace (wsi, ClientContext (wsi)); dispatcher ().update_all_nodes (wsi); // send all state + return 0; } -void +int WebsocketsServer::del_client (Client wsi) { ClientContextMap::iterator it = _client_ctx.find (wsi); + if (it != _client_ctx.end ()) { _client_ctx.erase (it); } + + return 0; } -void +int WebsocketsServer::recv_client (Client wsi, void* buf, size_t len) { NodeStateMessage msg (buf, len); if (!msg.is_valid ()) { - return; + return 1; } #ifndef NDEBUG @@ -251,26 +282,28 @@ WebsocketsServer::recv_client (Client wsi, void* buf, size_t len) ClientContextMap::iterator it = _client_ctx.find (wsi); if (it == _client_ctx.end ()) { - return; + return 1; } /* avoid echo */ it->second.update_state (msg.state ()); dispatcher ().dispatch (wsi, msg); + + return 0; } -void +int WebsocketsServer::write_client (Client wsi) { ClientContextMap::iterator it = _client_ctx.find (wsi); if (it == _client_ctx.end ()) { - return; + return 1; } ClientOutputBuffer& pending = it->second.output_buf (); if (pending.empty ()) { - return; + return 0; } /* one lws_write() call per LWS_CALLBACK_SERVER_WRITEABLE callback */ @@ -279,13 +312,15 @@ WebsocketsServer::write_client (Client wsi) pending.pop_front (); 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) { #ifndef NDEBUG std::cerr << "TX " << msg.state ().debug_str () << std::endl; #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 { PBD::error << "ArdourWebsockets: cannot serialize message" << endmsg; } @@ -293,21 +328,84 @@ WebsocketsServer::write_client (Client wsi) if (!pending.empty ()) { lws_callback_on_writable (wsi); } + + return 0; } -void -WebsocketsServer::reject_http_client (Client wsi) +int +WebsocketsServer::send_index_hdr (Client wsi) { - const char *html_body = "

This URL is not meant to be accessed via HTTP; for example using" - " a web browser. Refer to Ardour documentation for further information.

"; - lws_return_http_status (wsi, 404, html_body); + char url[1024]; + + 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 ("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 ("application/json"), 16, &p, end); + lws_add_http_header_by_token (wsi, WSI_TOKEN_CONNECTION, + reinterpret_cast ("close"), 5, &p, end); + lws_add_http_header_by_token (wsi, WSI_TOKEN_HTTP_CACHE_CONTROL, + reinterpret_cast ("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 (body), len, LWS_WRITE_HTTP) != len) { + return 1; + } + + lws_http_transaction_completed (wsi); + + return -1; // end connection } bool WebsocketsServer::io_handler (Glib::IOCondition ioc, lws_sockfd_type fd) { - /* IO_IN=1, IO_PRI=2, IO_ERR=8, IO_HUP=16 */ - //printf ("io_handler ioc = %d\n", ioc); + /* IO_IN=1, IO_PRI=2, IO_OUT=4, IO_ERR=8, IO_HUP=16 */ LwsPollFdGlibSourceMap::iterator it = _fd_ctx.find (fd); 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; lws_pfd->revents = ioc_to_events (ioc); - if (lws_service_fd (_lws_context, lws_pfd) < 0) { - return false; - } + lws_service_fd (_lws_context, lws_pfd); 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)); WebsocketsServer* server = static_cast (ctx_userdata); + int rc; switch (reason) { case LWS_CALLBACK_ADD_POLL_FD: - server->add_poll_fd (static_cast (in)); + rc = server->add_poll_fd (static_cast (in)); break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: - server->mod_poll_fd (static_cast (in)); + rc = server->mod_poll_fd (static_cast (in)); break; case LWS_CALLBACK_DEL_POLL_FD: - server->del_poll_fd (static_cast (in)); + rc = server->del_poll_fd (static_cast (in)); break; case LWS_CALLBACK_ESTABLISHED: - server->add_client (wsi); + rc = server->add_client (wsi); break; case LWS_CALLBACK_CLOSED: - server->del_client (wsi); + rc = server->del_client (wsi); break; case LWS_CALLBACK_RECEIVE: - server->recv_client (wsi, in, len); + rc = server->recv_client (wsi, in, len); break; case LWS_CALLBACK_SERVER_WRITEABLE: - server->write_client (wsi); + rc = server->write_client (wsi); 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: - server->reject_http_client (wsi); - return 1; + rc = server->send_index_hdr (wsi); 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_PROTOCOL_CONNECTION: 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: #endif #endif + /* do nothing but keep connection alive */ + rc = 0; break; - /* TODO: handle HTTP connections. - * Serve static ctrl-surface pages, JS, CSS etc. - */ - default: #ifndef NDEBUG /* see libwebsockets.h lws_callback_reasons */ std::cerr << "LWS: unhandled callback " << reason << std::endl; #endif - return -1; + rc = -1; break; } - return 0; + return rc; } diff --git a/libs/surfaces/websockets/server.h b/libs/surfaces/websockets/server.h index c585f22655..a47fbee51c 100644 --- a/libs/surfaces/websockets/server.h +++ b/libs/surfaces/websockets/server.h @@ -38,6 +38,7 @@ #include "component.h" #include "message.h" #include "state.h" +#include "resources.h" #define WEBSOCKET_LISTEN_PORT 9000 @@ -62,6 +63,8 @@ public: private: 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* _lws_context; @@ -73,15 +76,18 @@ private: typedef boost::unordered_map ClientContextMap; ClientContextMap _client_ctx; - void add_poll_fd (struct lws_pollargs*); - void mod_poll_fd (struct lws_pollargs*); - void del_poll_fd (struct lws_pollargs*); + ServerResources _resources; - void add_client (Client); - void del_client (Client); - void recv_client (Client, void* buf, size_t len); - void write_client (Client); - void reject_http_client (Client); + int add_poll_fd (struct lws_pollargs*); + int mod_poll_fd (struct lws_pollargs*); + int del_poll_fd (struct lws_pollargs*); + + 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);