implementation + compile as independent module

This commit is contained in:
Roland Conybeare 2023-10-17 16:07:33 -04:00
commit c634f33e67
26 changed files with 4297 additions and 0 deletions

View file

@ -0,0 +1,126 @@
/* file DynamicEndpoint.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "EndpointUtil.hpp"
#include "xo/webutil/HttpEndpointDescr.hpp"
#include "xo/webutil/StreamEndpointDescr.hpp"
#include "xo/webutil/Alist.hpp"
#include <regex>
namespace xo {
namespace web {
/* a dynamic http endpoint. content served on-browser-demand
* by user-provided callback
*/
class DynamicEndpoint {
public:
using AbstractSink = xo::reactor::AbstractSink;
using CallbackId = fn::CallbackId;
public:
static std::unique_ptr<DynamicEndpoint> make_http(std::string uri_pattern,
HttpEndpointFn http_cb) {
return (std::unique_ptr<DynamicEndpoint>
(new DynamicEndpoint(std::move(uri_pattern),
std::move(http_cb),
nullptr,
nullptr)));
} /*make_http*/
static std::unique_ptr<DynamicEndpoint> make_stream(std::string uri_pattern,
StreamSubscribeFn sub_fn,
StreamUnsubscribeFn unsub_fn) {
return (std::unique_ptr<DynamicEndpoint>
(new DynamicEndpoint(std::move(uri_pattern),
nullptr,
std::move(sub_fn),
std::move(unsub_fn))));
} /*make_stream*/
std::string stem() const {
return EndpointUtil::stem(this->uri_pattern_);
} /*stem*/
#ifdef NOT_USING
/* true iff incoming_uri matches .uri_pattern */
bool is_match(std::string const & incoming_uri) const {
/* c++ regex = javascript regexes,
* so these characters are special:
* ^ $ \ . * + ? ( ) [ ] { } |
*/
} /*is_match*/
#endif
/* get html from this endpoint, on behalf of uri=incoming_uri;
* write html on *p_os
*
* require: non-null http_fn
*/
void http_response(std::string const & incoming_uri,
std::ostream * p_os) const;
/* subscribe stream from this endpoint, on behalf of uri=incoming_uri.
* send output to ws_sink
*/
CallbackId subscribe(std::string const & incoming_uri,
ref::rp<AbstractSink> const & ws_sink) const;
/* unsubscribe stream from this endpoint;
* reverses the effect of a previous call to .subscribe()
* that returned id
*/
void unsubscribe(CallbackId id) const;
private:
explicit DynamicEndpoint(std::string uri_pattern,
HttpEndpointFn http_fn,
StreamSubscribeFn subscribe_fn,
StreamUnsubscribeFn unsubscribe_fn);
private:
/* pattern for this endpoint
* can be string like
* /fixed/stem/${a}/more/fixed/stuff/${b}
* in which case:
*
* 1. will match uris like:
* /fixed/stem/apple/more/fixed/stuff/bananas
* --> invoke callback with Alist
* ("a" -> "apple", "b" -> "bananas")
* endpoint will be stored in WebserverImpl.stem_map
* under fixed prefix, in this case
* /fixed/stem/
*
* 2. will not match uris like:
* /fixed/stem/app/le/more/fixed/stuff/bononos
*/
std::string uri_pattern_;
/* regex for matching input that satisfies .uri_pattern:
* each occurrence of
* ${...} replaced by [[:alnum:]]+
*/
std::regex uri_regex_;
/* variables found in .uri_pattern,
* in the order in which they appear
* if .uri_pattern is
* /fixed/stem/${a}/more/fixed/stuff/${b}
* then .var_v will be:
* ["a", "b"]
*/
std::vector<std::string> var_v_;
/* run this function to produce an http response */
HttpEndpointFn http_fn_;
/* run this function to subscribe event stream */
StreamSubscribeFn subscribe_fn_;
/* run this function to unsubscribe event stream */
StreamUnsubscribeFn unsubscribe_fn_;
}; /*DynamicEndpoint*/
} /*namespace web*/
} /*namespace xo*/
/* end DynamicEndpoint.hpp */

View file

@ -0,0 +1,26 @@
/* file EndpointUtil.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include <string>
namespace xo {
namespace web {
class EndpointUtil {
public:
/* find fixed prefix for a URI pattern.
* patterns are used with both http endpoints (see DynamicEndpoint),
* and stream endpoints (see StreamEndpoint)
*
* e.g. stem("/dyn/uls/${ulticker}/snap") => "/dyn/uls/"
*/
static std::string stem(std::string const & pattern);
}; /*EndpointUtil*/
} /*namespace web*/
} /*namespace xo*/
/* end EndpointUtil.hpp */

View file

@ -0,0 +1,40 @@
/* file SafetyToken.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
namespace xo {
namespace web {
/* token for cooperative compile-time threadsafety checking.
*
* requirements for cooperating code:
* - token contains no state, so in principle can be optimized away
* - token is deliberately not copyable, and not moveable
* - derive from token, and make derived ctor private
* - make method/class responsible for threadsafety a friend of token,
* so it can have exclusive right to create a token instance.
* - pass token reference down stack
* to demonstrate ownership of protected resource,
* limited to the lifetime of called function.
*/
template<typename T>
class SafetyToken {
public:
SafetyToken(SafetyToken const & x) = delete;
SafetyToken(SafetyToken && x) = delete;
/* optionally: invoke this to "announce use of a protected resource" */
bool verify() const { return true; }
SafetyToken & operator=(SafetyToken const & x) = delete;
SafetyToken & operator=(SafetyToken && x) = delete;
protected:
SafetyToken() = default;
}; /*SafetyToken*/
} /*namespace web*/
} /*namespace xo*/
/* end SafetyToken.hpp */

View file

@ -0,0 +1,116 @@
/* @file Webserver.hpp */
#pragma once
#include "xo/refcnt/Displayable.hpp"
#include "xo/printjson/PrintJson.hpp"
#include "xo/webutil/HttpEndpointDescr.hpp"
#include "xo/webutil/StreamEndpointDescr.hpp"
#include <libwebsockets.h> // temporary, while moving callbacks
#include <thread>
#include <vector>
#include <memory>
namespace xo {
namespace web {
enum class Runstate { stopped, stop_requested, running };
class RunstateUtil {
public:
static char const * runstate_descr(Runstate x);
}; /*RunstateUtil*/
inline std::ostream & operator<<(std::ostream &os, Runstate x) {
os << RunstateUtil::runstate_descr(x);
return os;
} /*operator<<*/
class WebserverConfig {
public:
WebserverConfig() = default;
WebserverConfig(std::int32_t port,
bool tls_flag,
bool host_check_flag,
bool use_retry_flag)
: port_{port},
tls_flag_{tls_flag},
host_check_flag_{host_check_flag},
use_retry_flag_{use_retry_flag} {}
std::int32_t port() const { return port_; }
bool tls_flag() const { return tls_flag_; }
bool host_check_flag() const { return host_check_flag_; }
bool use_retry_flag() const { return use_retry_flag_; }
private:
/* accept incoming http requests on this port# */
std::int32_t port_ = 0;
/* if true, support https */
bool tls_flag_ = false;
/* see LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK */
bool host_check_flag_ = false;
/* see lws_context_creation_info.retry_and_idle_policy */
bool use_retry_flag_ = false;
}; /*WebserverConfig*/
/* libwebsocket:
* 1. doesn't support multiple threads
* (actually, looks like it does on further examination)
* 2. doesn't expose listening ports etc (at least afaik);
* in other words it expects to take over application's main thread
*
* enforce this property by making webserver a singleton
*
* .state .start_webserver() .state
* +---------+ -------------------> +---------+
* | stopped | | running |
* +---------+ +---------+
* ^ |
* | | .stop_webserver()
* | |
* +----------------+ |
* | stop_requested | <------------------/
* +----------------+
*
*/
class Webserver : public ref::Displayable {
public:
using Alist = xo::web::Alist;
using PrintJson = xo::json::PrintJson;
public:
/* note: although webserver allows creating multiple instances,
* the underlying libwebsocket library is not advertised to be
* threadsafe
*/
static ref::rp<Webserver> make(WebserverConfig const & ws_config,
ref::rp<PrintJson> const & pjson);
/* current state */
virtual Runstate state() const = 0;
virtual void register_http_endpoint(HttpEndpointDescr const & endpoint) = 0;
virtual void register_stream_endpoint(StreamEndpointDescr const & endpoint) = 0;
/* start thread for this webserver; idempotent */
virtual void start_webserver() = 0;
/* stop thread for this webserver; suitable for calling
* from interrupt handler
*/
virtual void interrupt_stop_webserver() = 0;
/* stop thread for this webserver; idempotent */
virtual void stop_webserver() = 0;
/* wait until webserver thread stopped */
virtual void join_webserver() = 0;
/* send text to a websocket session identified by session_id */
virtual void send_text(uint32_t session_id,
std::string text) = 0;
// ----- Inherited from Displayable -----
virtual void display(std::ostream & os) const;
}; /*Webserver*/
} /*namespace web*/
} /*namespace xo*/
/* end Webserver.hpp */

View file

@ -0,0 +1,18 @@
/* @file WebsockUtil.hpp */
#pragma once
#include <libwebsockets.h>
namespace xo {
namespace web {
/* class-as-namespace idiom */
class WebsockUtil {
public:
/* string representation for callback category enum */
static char const * ws_callback_reason_descr(lws_callback_reasons x);
}; /*WebsockUtil*/
} /*namespace web*/
} /*namespace xo*/
/* end WebsockUtil.hpp */

View file

@ -0,0 +1,28 @@
/* file WebsocketSink.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "xo/reactor/AbstractSink.hpp"
#include "xo/printjson/PrintJson.hpp"
namespace xo {
namespace web {
class Webserver;
class WebsocketSink : public reactor::AbstractSink {
public:
using PrintJson = xo::json::PrintJson;
public:
static ref::rp<WebsocketSink> make(ref::rp<Webserver> const & websrv,
ref::rp<PrintJson> const & pjson,
uint32_t session_id,
std::string const & stream_name);
}; /*WebsocketSink*/
} /*namespace web*/
} /*namespace xo*/
/* end WebsocketSink.hpp */

View file

@ -0,0 +1,29 @@
/* file WsSafetyToken.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "SafetyToken.hpp"
#include <mutex>
namespace xo {
namespace web {
class WebserverImplWsThread;
class WebsocketSessionRecd;
/* only websocket thread can obtain this token */
class WsSafetyToken : public SafetyToken<class WsSafetyToken_tag> {
private:
friend class WebserverImplWsThread;
private:
/* only WebserverImpl should construct this */
WsSafetyToken() = default;
}; /*WsSafetyToken*/
} /*namespace web*/
} /*namespace xo*/
/* end WsSafetyToken.hpp */