implementation + compile as independent module
This commit is contained in:
parent
3913e07f81
commit
c634f33e67
26 changed files with 4297 additions and 0 deletions
126
include/xo/websock/DynamicEndpoint.hpp
Normal file
126
include/xo/websock/DynamicEndpoint.hpp
Normal 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 */
|
||||
26
include/xo/websock/EndpointUtil.hpp
Normal file
26
include/xo/websock/EndpointUtil.hpp
Normal 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 */
|
||||
40
include/xo/websock/SafetyToken.hpp
Normal file
40
include/xo/websock/SafetyToken.hpp
Normal 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 */
|
||||
116
include/xo/websock/Webserver.hpp
Normal file
116
include/xo/websock/Webserver.hpp
Normal 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 */
|
||||
18
include/xo/websock/WebsockUtil.hpp
Normal file
18
include/xo/websock/WebsockUtil.hpp
Normal 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 */
|
||||
28
include/xo/websock/WebsocketSink.hpp
Normal file
28
include/xo/websock/WebsocketSink.hpp
Normal 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 */
|
||||
29
include/xo/websock/WsSafetyToken.hpp
Normal file
29
include/xo/websock/WsSafetyToken.hpp
Normal 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue