From 886ad07eb332125962cd712e49c20d9f045203a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:26:49 -0400 Subject: [PATCH] initial implementation --- CMakeLists.txt | 50 ++++++++++++++ README.md | 28 ++++++++ cmake/webutilConfig.cmake.in | 4 ++ include/xo/webutil/Alist.hpp | 43 ++++++++++++ include/xo/webutil/HttpEndpointDescr.hpp | 76 +++++++++++++++++++++ include/xo/webutil/StreamEndpointDescr.hpp | 78 ++++++++++++++++++++++ 6 files changed, 279 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/webutilConfig.cmake.in create mode 100644 include/xo/webutil/Alist.hpp create mode 100644 include/xo/webutil/HttpEndpointDescr.hpp create mode 100644 include/xo/webutil/StreamEndpointDescr.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..dcce42b2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +# xo-webutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(webutil VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup (no unit tests yet, but want 'make tests' to do something) + +enable_testing() +# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) +add_code_coverage() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +# header-only library. +# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] +# +xo_add_headeronly_library(webutil) + +# ---------------------------------------------------------------- +# standard install + +xo_install_library3(webutil ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install bespoke targets, if any + +#install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a8eb8c77 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# webutil library (header-only) + +# dependencies + +- xo-cmake [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +# clone repo +``` +$ git clone git@github.com:Rconybea/xo-webutil.git +``` + +# build and install +``` +$ cd xo-webutil +$ BUILDDIR=build # for example +$ mkdir $BUILDDIR +$ cd $BUILDDIR +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +# LSP support +``` +$ cd xo-webutil +$ ln -s $BUILDDIR/compile_commands.json +``` diff --git a/cmake/webutilConfig.cmake.in b/cmake/webutilConfig.cmake.in new file mode 100644 index 00000000..c44284f2 --- /dev/null +++ b/cmake/webutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/webutilTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp new file mode 100644 index 00000000..80fcaa20 --- /dev/null +++ b/include/xo/webutil/Alist.hpp @@ -0,0 +1,43 @@ +/* file Alist.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include +#include + +namespace xo { + namespace web { + /* assocation list, maps strings to strings + * use this for arguments to dynamic-endpoint-callbacks + */ + class Alist { + public: + Alist() = default; + + /* lookup association by name */ + std::string_view lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } + + return ""; + } /*lookup*/ + + void push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + + private: + std::vector> assoc_v_; + }; /*Alist*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end Alist.hpp */ diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp new file mode 100644 index 00000000..79a7070f --- /dev/null +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -0,0 +1,76 @@ +/* file EndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "web_util/Alist.hpp" +#include "refcnt/Refcounted.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace web { + /* a function that can deliver http content on demand. */ + using HttpEndpointFn = std::function; + + /* describes an http endpoint -- + * this comprises: + * - a uri pattern. + * - a function that can deliver http content on demand + */ + class HttpEndpointDescr { + public: + HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} + + std::string const & uri_pattern() const { return uri_pattern_; } + HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + /* unique pattern in URI-space for this endpoint. + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that can construct http output on demand + * .endpoint_fn(uri, alist, &os) + * writes http output to os. output is parameterized + * by name-value pairs in alist, and is prepared on behalf + * of .uri_pattern + * alist will report name-value pairs for each variable that + * appears in .uri_pattern (surrounded by ${..}) + */ + HttpEndpointFn endpoint_fn_; + }; /*HttpEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, HttpEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointDescr.hpp */ diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp new file mode 100644 index 00000000..fe86b135 --- /dev/null +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -0,0 +1,78 @@ +/* file StreamEndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "web_util/Alist.hpp" +#include "callback/CallbackSet.hpp" +#include "refcnt/Refcounted.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace reactor { class AbstractSink; } + + namespace web { + /* a function that creates an event subscription */ + using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamUnsubscribeFn = std::function; + + /* describes a stream endpoint + * this comprises + * - a uri pattern (matches stream name) + * - a function that establishes subscription + * (by attaching supplied WebsocketSink to an event source) + */ + class StreamEndpointDescr { + public: + StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} {} + + std::string const & uri_pattern() const { return uri_pattern_; } + StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } + StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + /* unique pattern in URI-space for this endpoint + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that subscribes to an event stream + * (by attaching a websocket sink) + */ + StreamSubscribeFn subscribe_fn_; + /* reverses effect of a particular call to .subscribe_fn */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*StreamEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, StreamEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end StreamEndpointDescr.hpp */