Add 'xo-websock/' from commit '57bf06a68f'

git-subtree-dir: xo-websock
git-subtree-mainline: 97eefaea22
git-subtree-split: 57bf06a68f
This commit is contained in:
Roland Conybeare 2025-05-11 15:08:51 -05:00
commit dd1a6b1afc
30 changed files with 4667 additions and 0 deletions

261
xo-websock/.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,261 @@
name: build xo-callback + xo dependencies
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
# You can convert this to a matrix build if you need cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- name: checkout source
uses: actions/checkout@v3
- name: Install catch2
# install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]]
run: sudo apt-get install -y catch2
- name: Install libjsoncpp (dep of libwebsockets)
# install jsoncpp
run: sudo apt-get install -y libjsoncpp-dev
- name: Install libwebsockets
# install libwebsockets.
run: sudo apt-get install -y libwebsockets-dev
# ----------------------------------------------------------------
- name: Clone xo-cmake
uses: actions/checkout@v3
with:
repository: Rconybea/xo-cmake
path: repo/xo-cmake
- name: Configure xo-cmake
run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake
- name: Build xo-cmake (trivial)
run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}}
- name: Install xo-cmake
run: cmake --install ${{github.workspace}}/build_xo-cmake
# ----------------------------------------------------------------
- name: Clone indentlog
uses: actions/checkout@v3
with:
repository: Rconybea/indentlog
path: repo/indentlog
- name: Configure indentlog
# configure cmake for indentlog in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog
- name: Build indentlog
run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}}
- name: Install indentlog
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_indentlog
# ----------------------------------------------------------------
- name: Clone refcnt
uses: actions/checkout@v3
with:
repository: Rconybea/refcnt
path: repo/refcnt
- name: Configure refcnt
# configure cmake for refcnt in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt
- name: Build refcnt
run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}}
- name: Install refcnt
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_refcnt
# ----------------------------------------------------------------
- name: Clone subsys
uses: actions/checkout@v3
with:
repository: Rconybea/subsys
path: repo/subsys
- name: Configure subsys
# configure cmake for subsys in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys
- name: Build subsys
run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}}
- name: Install subsys
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_subsys
# ----------------------------------------------------------------
- name: Clone reflect
uses: actions/checkout@v3
with:
repository: Rconybea/reflect
path: repo/reflect
- name: Configure reflect
# configure cmake for reflect in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect
- name: Build reflect
run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}}
- name: Install reflect
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_reflect
# ----------------------------------------------------------------
- name: Clone callback
uses: actions/checkout@v3
with:
repository: Rconybea/xo-callback
path: repo/callback
- name: Configure callback
# configure cmake for callback in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback
- name: Build callback
run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}}
- name: Install callback
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_callback
# ----------------------------------------------------------------
- name: Clone webutil
uses: actions/checkout@v3
with:
repository: Rconybea/xo-webutil
path: repo/webutil
- name: Configure webutil
# configure cmake for webutil in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil
- name: Build webutil
run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}}
- name: Install webutil
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_webutil
# ----------------------------------------------------------------
- name: Clone printjson
uses: actions/checkout@v3
with:
repository: Rconybea/xo-printjson
path: repo/printjson
- name: Configure printjson
# configure cmake for printjson in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson
- name: Build printjson
run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}}
- name: Install printjson
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_printjson
# ----------------------------------------------------------------
- name: Clone randomgen
uses: actions/checkout@v3
with:
repository: Rconybea/randomgen
path: repo/randomgen
- name: Configure randomgen
# configure cmake for randomgen in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen
- name: Build randomgen
run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}}
- name: Install randomgen
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_randomgen
# ----------------------------------------------------------------
- name: Clone ordinaltree
uses: actions/checkout@v3
with:
repository: Rconybea/xo-ordinaltree
path: repo/ordinaltree
- name: Configure ordinaltree
# configure cmake for ordinaltree in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree
- name: Build ordinaltree
run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}}
- name: Install ordinaltree
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_ordinaltree
# ----------------------------------------------------------------
- name: Clone reactor
uses: actions/checkout@v3
with:
repository: Rconybea/xo-reactor
path: repo/reactor
- name: Configure reactor
# configure cmake for reactor in dedicated build directory.
run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor
- name: Build reactor
run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}}
- name: Install reactor
# install into ${{github.workspace}}/local
run: cmake --install ${{github.workspace}}/build_reactor
# ----------------------------------------------------------------
- name: check cmake-supporting packages
run: ls /usr/lib/x86_64-linux-gnu/cmake
- name: Configure self (websock)
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake "-DCMAKE_PREFIX_PATH=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake" -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build self (websock)
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build_websock --config ${{env.BUILD_TYPE}}
- name: Test self (websock)
working-directory: ${{github.workspace}}/build_websock
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}

6
xo-websock/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

30
xo-websock/CMakeLists.txt Normal file
View file

@ -0,0 +1,30 @@
# xo-websock/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(websock VERSION 1.0)
include(GNUInstallDirs)
include(cmake/xo-bootstrap-macros.cmake)
xo_cxx_toplevel_options3()
# ----------------------------------------------------------------
# c++ settings
# one-time project-specific c++ flags. usually empty
set(PROJECT_CXX_FLAGS "")
#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
add_definitions(${PROJECT_CXX_FLAGS})
# ----------------------------------------------------------------
add_subdirectory(src/websock)
#add_subdirectory(utest)
# ----------------------------------------------------------------
# provide find_package() support for websock customers
xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)
# end CMakeLists.txt

55
xo-websock/README.md Normal file
View file

@ -0,0 +1,55 @@
# websock library
http library including websocket support.
Built around the C-library libwebsocket
# dependencies
build+install these first
- xo-somelib [github.com/Rconybea/xo-somelib]
# build + install
## build
```
$ cd websock
$ mkdir build
$ cd build
$ INSTALL_PREFIX=/usr/local # or wherever you prefer
$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} ..
$ make
$ make install
```
(also see .github/workflows/main.yml)
## build for unit test coverage
```
$ cd xo-websock
$ mkdir ccov
$ cd ccov
$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug ..
```
# development
## LSP support
LSP looks for compile commands in the root of the source tree;
cmake creates them in the root of its build directory.
```
$ cd xo-websock
$ ln -s build/compile_commands.json
```
## display cmake variables
- `-L` list variables
- `-A` include 'advanced' variables
- `-H` include help text
```
$ cd websock/build
$ cmake -LAH
```

View file

@ -0,0 +1,14 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# note: changes to find_dependency() calls here
# must coordinate with xo_dependency() calls
# in xo-websock/src/websock/CMakeLists.txt
#
find_dependency(reactor)
find_dependency(webutil)
find_dependency(Libwebsockets)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

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,
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 rp<Webserver> make(WebserverConfig const & ws_config,
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 rp<WebsocketSink> make(rp<Webserver> const & websrv,
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 */

View file

@ -0,0 +1,21 @@
# xo-websock/CMakeLists.txt
set(SELF_LIB websock)
set(SELF_SRCS EndpointUtil.cpp DynamicEndpoint.cpp WebsockUtil.cpp WebsocketSink.cpp Webserver.cpp)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
# ----------------------------------------------------------------
# external dependencies
# note: changes to xo_dependency() calls here
# must coordinate with find_dependency() calls in
# xo-websock/cmake/websockConfig.cmake.in
xo_dependency(${SELF_LIB} reactor)
xo_dependency(${SELF_LIB} webutil)
# see LibwebsocketsTargets-release.cmake for available targets
# this dependency doesn't show up via cmake-export
xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared)
# see jsoncpp-namespaced-targets.cmake (maybe?) for available targets
xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib)

View file

@ -0,0 +1,145 @@
/* file DynamicEndpoint.cpp
*
* author: Roland Conybeare, Sep 2022
*/
#include "DynamicEndpoint.hpp"
namespace xo {
using xo::web::Alist;
using xo::fn::CallbackId;
namespace web {
DynamicEndpoint::DynamicEndpoint(std::string uri_pattern,
HttpEndpointFn http_fn,
StreamSubscribeFn subscribe_fn,
StreamUnsubscribeFn unsubscribe_fn)
: uri_pattern_{std::move(uri_pattern)},
http_fn_{std::move(http_fn)},
subscribe_fn_{std::move(subscribe_fn)},
unsubscribe_fn_{std::move(unsubscribe_fn)}
{
std::string r_pat;
/* 1st pass -- construct pattern regex .uri_regex
* to identify urls that belong to this endpoint
*
* using regex like:
* \$\{[[:alnum:]]+\}
*/
{
std::regex var_rgx("\\$\\{[[:alnum:]]+\\}");
/* e.g. if .uri_pattern:
* /fixed/stem/${a}/more/fixed/stuff/${b}
* then want r_pat:
* /fixed/stem/[[:alnum:]]+/more/fixed/stuff/[[:alnum:]]+
* to find values pattern variables like ${a}, ${b}
*/
std::regex_replace(std::back_inserter(r_pat),
this->uri_pattern_.begin(),
this->uri_pattern_.end(),
var_rgx,
std::string("([[:alnum:]]+)"));
this->uri_regex_ = std::regex(r_pat);
}
/* 2nd pass -- identify pattern variables */
{
/* regex for:
* \$\{([[:alnum:]]+)\}
* use to match input like
* ${apple}
* and also extract the variable name
* apple
*/
std::regex var_rgx("\\$\\{([[:alnum:]]+)\\}");
std::smatch match;
std::string subject = this->uri_pattern_;
/* if subject like
* /fixed/stem/${a}/more/fixed/stuff/${b}
* extract
* ["a", "b"]
*
* for
* /fixed/stem/${a}/more/fixed/stuff/${b}/${a}
* also extract
* ["a", "b"]
* i.e. avoid extracting the same variable name twice
*/
while (std::regex_search(subject, match, var_rgx)) {
std::string v = match[1];
bool present_flag = false;
for (auto const & x : this->var_v_) {
if (x == v) {
present_flag = true;
break;
}
}
if (!present_flag)
this->var_v_.push_back(match[1]);
subject = match.suffix().str();
}
}
} /*ctor*/
void
DynamicEndpoint::http_response(std::string const & incoming_uri,
std::ostream * p_os) const
{
/* send this uri argument list callback.
* contains variables extracted from .uri_pattern
* (variables surrounded by ${...})
*/
Alist alist;
/* extract pattern variables in uri
* c.f. 2nd pass in DynamicEndpoint.ctor
*/
std::smatch match;
std::string subject = incoming_uri;
/* if subject like
* /fixed/stem/apple/more/fixed/stuff/beagle
* with .uri_pattern
* /fixed/stem/${a}/more/fixed/stuff/${b}
* then we have .uri_regex
* /fixed/stem/([[:alnum:]]+)/more/fixed/stuff/([[:alnum:]]+)
* use this to extract values for keys in .var_v,
* in the same order
*/
if (std::regex_match(subject, match, this->uri_regex_)) {
for (size_t i = 0, n = this->var_v_.size(); i<n; ++i) {
std::string i_name = this->var_v_[i];
std::string i_value = match[1+i];
alist.push_back(i_name, i_value);
}
}
this->http_fn_(incoming_uri, alist, p_os);
} /*http_response*/
CallbackId
DynamicEndpoint::subscribe(std::string const & /*incoming_uri*/,
rp<AbstractSink> const & ws_sink) const
{
return this->subscribe_fn_(ws_sink);
} /*subscribe*/
void
DynamicEndpoint::unsubscribe(CallbackId id) const
{
return this->unsubscribe_fn_(id);
} /*unsubscribe*/
} /*namespace web*/
} /*namespace xo*/
/* end DynamicEndpoint.cpp */

View file

@ -0,0 +1,38 @@
/* file EndpointUtil.cpp
*
* author: Roland Conybeare, Sep 2022
*/
#include "EndpointUtil.hpp"
namespace xo {
namespace web {
std::string
EndpointUtil::stem(std::string const & pattern)
{
std::size_t p = 0;
do {
p = pattern.find_first_of("$", p);
if ((p != std::string::npos) && (pattern[p+1] == '{')) {
/* fixed stem is chars [0 .. p-1], i.e. 1st p characters */
break;
}
if (p != std::string::npos) {
/* skip to next '$' */
++p;
}
} while (p != std::string::npos);
if (p == std::string::npos) {
/* pattern has no variable components */
return pattern;
} else {
return pattern.substr(0, p);
}
} /*stem*/
} /*namespace web*/
} /*namespace xo*/
/* end EndpointUtil.cpp */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,155 @@
/* @file WebsockUtil.cpp */
#include "WebsockUtil.hpp"
#define STRINGIFY(x) #x
namespace xo {
namespace web {
char const *
WebsockUtil::ws_callback_reason_descr(lws_callback_reasons x) {
#define CASE(x) case x: return STRINGIFY(x)
/* ubuntu build (available via github actions) has older version of libwebsockets.
* typically building (e.g. via nix) with libwebsockets 4.3.2
*
* see lws_config.h for version numbers vars
*/
switch (x) {
CASE(LWS_CALLBACK_PROTOCOL_INIT);
CASE(LWS_CALLBACK_PROTOCOL_DESTROY);
CASE(LWS_CALLBACK_WSI_CREATE);
CASE(LWS_CALLBACK_WSI_DESTROY);
CASE(LWS_CALLBACK_WSI_TX_CREDIT_GET);
CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS);
CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS);
CASE(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION);
#ifdef OBSOLETE // at least on osx w/ nixpkgs dd868b7bd4d1407d607da0d1d9c5eca89132e2f7
CASE(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY);
#endif
CASE(LWS_CALLBACK_SSL_INFO);
CASE(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION);
CASE(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED);
CASE(LWS_CALLBACK_HTTP);
CASE(LWS_CALLBACK_HTTP_BODY);
CASE(LWS_CALLBACK_HTTP_BODY_COMPLETION);
CASE(LWS_CALLBACK_HTTP_FILE_COMPLETION);
CASE(LWS_CALLBACK_HTTP_WRITEABLE);
CASE(LWS_CALLBACK_CLOSED_HTTP);
CASE(LWS_CALLBACK_FILTER_HTTP_CONNECTION);
CASE(LWS_CALLBACK_ADD_HEADERS);
CASE(LWS_CALLBACK_VERIFY_BASIC_AUTHORIZATION);
CASE(LWS_CALLBACK_CHECK_ACCESS_RIGHTS);
CASE(LWS_CALLBACK_PROCESS_HTML);
CASE(LWS_CALLBACK_HTTP_BIND_PROTOCOL);
CASE(LWS_CALLBACK_HTTP_DROP_PROTOCOL);
CASE(LWS_CALLBACK_HTTP_CONFIRM_UPGRADE);
CASE(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP);
CASE(LWS_CALLBACK_CLOSED_CLIENT_HTTP);
CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ);
CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP);
CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP);
CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE);
#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3))
CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT);
#endif
CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL);
CASE(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL);
CASE(LWS_CALLBACK_ESTABLISHED);
CASE(LWS_CALLBACK_CLOSED);
CASE(LWS_CALLBACK_SERVER_WRITEABLE);
CASE(LWS_CALLBACK_RECEIVE);
CASE(LWS_CALLBACK_RECEIVE_PONG);
CASE(LWS_CALLBACK_WS_PEER_INITIATED_CLOSE);
CASE(LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION);
CASE(LWS_CALLBACK_CONFIRM_EXTENSION_OKAY);
CASE(LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL);
CASE(LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL);
CASE(LWS_CALLBACK_CLIENT_CONNECTION_ERROR);
CASE(LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH);
CASE(LWS_CALLBACK_CLIENT_ESTABLISHED);
CASE(LWS_CALLBACK_CLIENT_CLOSED);
CASE(LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER);
CASE(LWS_CALLBACK_CLIENT_RECEIVE);
CASE(LWS_CALLBACK_CLIENT_RECEIVE_PONG);
CASE(LWS_CALLBACK_CLIENT_WRITEABLE);
CASE(LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED);
CASE(LWS_CALLBACK_WS_EXT_DEFAULTS);
CASE(LWS_CALLBACK_FILTER_NETWORK_CONNECTION);
CASE(LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL);
CASE(LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL);
CASE(LWS_CALLBACK_GET_THREAD_ID);
CASE(LWS_CALLBACK_ADD_POLL_FD);
CASE(LWS_CALLBACK_DEL_POLL_FD);
CASE(LWS_CALLBACK_CHANGE_MODE_POLL_FD);
CASE(LWS_CALLBACK_LOCK_POLL);
CASE(LWS_CALLBACK_UNLOCK_POLL);
CASE(LWS_CALLBACK_CGI);
CASE(LWS_CALLBACK_CGI_TERMINATED);
CASE(LWS_CALLBACK_CGI_STDIN_DATA);
CASE(LWS_CALLBACK_CGI_STDIN_COMPLETED);
CASE(LWS_CALLBACK_CGI_PROCESS_ATTACH);
CASE(LWS_CALLBACK_SESSION_INFO);
CASE(LWS_CALLBACK_GS_EVENT);
CASE(LWS_CALLBACK_HTTP_PMO);
CASE(LWS_CALLBACK_RAW_PROXY_CLI_RX);
CASE(LWS_CALLBACK_RAW_PROXY_SRV_RX);
CASE(LWS_CALLBACK_RAW_PROXY_CLI_CLOSE);
CASE(LWS_CALLBACK_RAW_PROXY_SRV_CLOSE);
CASE(LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE);
CASE(LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE);
CASE(LWS_CALLBACK_RAW_PROXY_CLI_ADOPT);
CASE(LWS_CALLBACK_RAW_PROXY_SRV_ADOPT);
CASE(LWS_CALLBACK_RAW_PROXY_CLI_BIND_PROTOCOL);
CASE(LWS_CALLBACK_RAW_PROXY_SRV_BIND_PROTOCOL);
CASE(LWS_CALLBACK_RAW_PROXY_CLI_DROP_PROTOCOL);
CASE(LWS_CALLBACK_RAW_PROXY_SRV_DROP_PROTOCOL);
CASE(LWS_CALLBACK_RAW_RX);
CASE(LWS_CALLBACK_RAW_CLOSE);
CASE(LWS_CALLBACK_RAW_WRITEABLE);
CASE(LWS_CALLBACK_RAW_ADOPT);
CASE(LWS_CALLBACK_RAW_CONNECTED);
CASE(LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL);
CASE(LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL);
CASE(LWS_CALLBACK_RAW_ADOPT_FILE);
CASE(LWS_CALLBACK_RAW_RX_FILE);
CASE(LWS_CALLBACK_RAW_WRITEABLE_FILE);
CASE(LWS_CALLBACK_RAW_CLOSE_FILE);
CASE(LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL);
CASE(LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL);
CASE(LWS_CALLBACK_TIMER);
CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED);
CASE(LWS_CALLBACK_CHILD_CLOSING);
#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3))
CASE(LWS_CALLBACK_CONNECTING);
#endif
CASE(LWS_CALLBACK_VHOST_CERT_AGING);
CASE(LWS_CALLBACK_VHOST_CERT_UPDATE);
CASE(LWS_CALLBACK_MQTT_NEW_CLIENT_INSTANTIATED);
CASE(LWS_CALLBACK_MQTT_IDLE);
CASE(LWS_CALLBACK_MQTT_CLIENT_ESTABLISHED);
CASE(LWS_CALLBACK_MQTT_SUBSCRIBED);
CASE(LWS_CALLBACK_MQTT_CLIENT_WRITEABLE);
CASE(LWS_CALLBACK_MQTT_CLIENT_RX);
CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBED);
CASE(LWS_CALLBACK_MQTT_DROP_PROTOCOL);
CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED);
CASE(LWS_CALLBACK_MQTT_ACK);
CASE(LWS_CALLBACK_MQTT_RESEND);
#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3))
CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT);
CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT);
#endif
CASE(LWS_CALLBACK_USER);
}
#undef CASE
return "???";
} /*ws_callback_reason_descr*/
} /*namespace web*/
} /*namespace xo*/
/* end WebsockUtil.cpp */

View file

@ -0,0 +1,147 @@
/* file WebsocketSink.cpp
*
* author: Roland Conybeare, Sep 2022
*/
#include "WebsocketSink.hpp"
#include "Webserver.hpp"
#include "xo/printjson/PrintJson.hpp"
#include "xo/reflect/Reflect.hpp"
#include "xo/reflect/TaggedPtr.hpp"
#include "xo/indentlog/scope.hpp"
namespace xo {
using xo::reactor::AbstractSource;
using xo::json::PrintJson;
using xo::reflect::Reflect;
using xo::reflect::TaggedPtr;
using xo::reflect::TypeDescr;
using xo::ref::brw;
using xo::print::quot;
using xo::print::qcstr;
using xo::scope;
using xo::xtag;
namespace web {
/* a sink that publishes to a websocket.
* The websocket api creates a WebsocketSink instance
* on behalf of an incoming subscription request.
* application code will hold onto the sink somewhere
* and publish events to it, to send them via websocket.
*/
class WebsocketSinkImpl : public WebsocketSink {
public:
using PrintJson = xo::json::PrintJson;
using AbstractSource = reactor::AbstractSource;
public:
WebsocketSinkImpl(rp<Webserver> const & websrv,
rp<PrintJson> const & pjson,
uint32_t session_id,
std::string stream_name)
: websrv_{std::move(websrv)},
pjson_{std::move(pjson)},
session_id_{session_id},
stream_name_{std::move(stream_name)}
{}
virtual std::string const & name() const override { return name_; }
virtual void set_name(std::string const & x) override { this->name_ = x; }
/* 0 consumers for websocket sink, since it's not a source */
virtual void visit_direct_consumers(std::function<void (brw<AbstractEventProcessor>)> const &) override {}
virtual void display(std::ostream & os) const override;
virtual bool allow_polymorphic_source() const override { return true; }
virtual TypeDescr sink_ev_type() const override;
virtual bool allow_volatile_source() const override { return true; }
virtual uint32_t n_in_ev() const override { return n_in_ev_; }
virtual void attach_source(rp<AbstractSource> const & src) override;
virtual void notify_ev_tp(TaggedPtr const & ev_tp) override;
private:
/* (ideally unique) user-controlled name for this sink
* in practice not likely to be accessible,
* so probably want to generate a unique-y default
*/
std::string name_;
/* webserver implementation */
rp<Webserver> websrv_;
/* print arbitrary reflected stuff as json */
rp<PrintJson> pjson_;
/* websocket session id# - events arriving at this sink
* will be sent only to the session identified by .session_id
*/
uint32_t session_id_;
/* name for stream.
* this will be the vale of the "stream" tag in
* initiating subscription message
* {"cmd": "subscribe", "stream", "/this/stream/name"}
* e.g. in python:
* web.register_stream_endpoint(kf.stream_endpoint_descr("/this/stream/name"))
*/
std::string stream_name_;
/* count #of events received */
uint32_t n_in_ev_ = 0;
}; /*WebsocketSinkImpl*/
TypeDescr
WebsocketSinkImpl::sink_ev_type() const
{
return Reflect::require<void>();
} /*sink_ev_type*/
void
WebsocketSinkImpl::attach_source(rp<AbstractSource> const & src) {
src->attach_sink(this);
} /*attach_source*/
void
WebsocketSinkImpl::notify_ev_tp(TaggedPtr const & ev_tp)
{
scope log(XO_DEBUG(true /*debug_flag*/));
std::stringstream ss;
/* format message envelope */
ss << "{" << qcstr("stream") << ": " << quot(this->stream_name_)
<< ", " << qcstr("event") << ": ";
/* format event as json */
this->pjson_->print_tp(ev_tp, &ss);
ss << "}";
log && log("sending", xtag("msg", ss.str()));
++(this->n_in_ev_);
/* send event via associated websocket */
this->websrv_->send_text(this->session_id_, ss.str());
} /*notify_ev_tp*/
void
WebsocketSinkImpl::display(std::ostream & os) const
{
os << "<WebsocketSinkImpl"
<< xtag("addr", (void*)this)
<< xtag("name", name_)
<< xtag("n_in_ev", n_in_ev_)
<< xtag("stream", stream_name_)
<< ">";
} /*display*/
// ----- WebsocketSink -----
rp<WebsocketSink>
WebsocketSink::make(rp<Webserver> const & websrv,
rp<PrintJson> const & pjson,
uint32_t session_id,
std::string const & stream_name)
{
return new WebsocketSinkImpl(websrv, pjson, session_id, stream_name);
} /*make*/
} /*namespace web*/
} /*namespace xo*/
/* end WebsocketSink.cpp */

View file

@ -0,0 +1,47 @@
# build unittest websock/utest
set(SELF_EXE utest.websock)
set(SELF_SRCS websock_utest_main.cpp)
add_executable(${SELF_EXE} ${SELF_SRCS})
xo_include_options2(${SELF_EXE})
## note: can't add this yet, because test not automated.
## requires manual interaction from browser
##
#add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE})
#target_code_coverage(${SELF_EXE} AUTO ALL)
# copy static {.html, .js, .svg} files to build directory
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mount-origin/"
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/mount-origin")
# ----------------------------------------------------------------
# internal dependency (on this codebase)
xo_self_dependency(${SELF_EXE} websock)
# ----------------------------------------------------------------
# external dependencies
target_link_libraries(${SELF_EXE} PUBLIC websock)
# Need to port option, volfit before we can build this test here
target_link_libraries(${SELF_EXE} PUBLIC option)
target_link_libraries(${SELF_EXE} PUBLIC volfit)
#target_link_libraries(utest.option PUBLIC logutil)
# should be getting this via xo_include_options2()
## ----------------------------------------------------------------
## make standard directories for std:: includes explicit
## so that
## (1) they appear in compile_commands.json.
## (2) clangd (run from emacs lsp-mode) can find them
##
#if(CMAKE_EXPORT_COMPILE_COMMANDS)
# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
#endif()
# end CMakeLists.txt

13
xo-websock/utest/README Normal file
View file

@ -0,0 +1,13 @@
To run this unit test:
$ cd path/to/kalman/build/src/websock/utest
$ ./utest.websock # listens for http requests on port 7682
point browser to
localhost:7682/ex_websock.html
static files served from
path/to/kalman/build/src/websock/utest/mount-origin

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- <svg width="30" height="50"> ... </svg> does not satisfy chrome -->
<svg width="30.0mm" height="50.0mm" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="25" cy="25" r="22" fill="blue" stroke="gray" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">
import * as d3 from "https://cdn.skypack.dev/d3@7";
const body = d3.selectAll("body");
</script>
</head>
<body>
simple d3 example
<script type="module">
</script>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>pywebsock example page</title>
<script type="module" src="ex_websock.js"></script>
</head>
<body>
<p>pywebsock example page</p>
<button id="refresh" type="button">refresh</button>
<div id="uls"></div>
<div id="kfs"></div>
</body>
</html>

View file

@ -0,0 +1,842 @@
/* webpage to display kalman filter output
* coordinates with ex_websock.py in this directory
*/
import * as d3 from "https://cdn.skypack.dev/d3@7";
/* json5 accepts ieee floatingpoint special values;
* regular json excludes them (!?#)
*/
import JSON5 from "https://unpkg.com/json5@2/dist/index.min.mjs";
/* NOTE: put "export" in front of a variable/function
* that we want to make accessible outside this module
*/
/* for use for browser's javascript console */
globalThis.d3 = d3;
//globalThis.jparse = JSON5.parse;
/* u: document.URL */
function choose_ws_url(suffix_url)
{
var pcol;
var u = document.URL;
/*
* We open the websocket encrypted if this page came on an
* https:// url itself, otherwise unencrypted
*/
if (u.substring(0, 5) === "https") {
pcol = "wss://";
u = u.substr(8);
} else {
pcol = "ws://";
if (u.substring(0, 4) === "http")
u = u.substr(7);
}
u = u.split("/");
/* + "/xxx" bit is for IE10 workaround */
return pcol + u[0] + "/" + suffix_url;
} /*choose_ws_url*/
class Datatype {
#typename = null;
#nominal = null;
/* .from_json(x) convert a value received in json format
* to native representation.
*/
#from_json = null;
/* .make_scale(range) builds d3 scale object */
#make_scale = null;
constructor(typename, nominal, from_json, make_scale) {
this.#typename = typename;
this.#nominal = nominal;
this.#from_json = from_json;
this.#make_scale = make_scale;
}
typename() { return this.#typename; }
nominal() { return this.#nominal; }
from_json(x) { return this.#from_json(x); }
make_scale(domain) { return this.#make_scale(domain); }
}; /*Datatype*/
class DatatypeFactory {
static dtype_map = DatatypeFactory.make_dtype_map();
static make_float_dtype() {
return new Datatype("float" /*typename*/,
0.0 /*nominal*/,
(x) => { return x; } /*from_json*/,
(dom) => { return d3.scaleLinear().domain(dom); } /*make_scale*/
); }
static make_datetime_dtype() {
return new Datatype("datetime" /*typename*/,
new Date() /*nominal*/,
(x) => { return new Date(x); } /*from_json*/,
(dom) => { return d3.scaleTime().domain(dom); } /*make_scale*/
); }
static make_dtype_map() {
let retval = new Map();
retval.set("float", DatatypeFactory.make_float_dtype());
retval.set("datetime", DatatypeFactory.make_datetime_dtype());
return retval;
}
static lookup(typename) {
if (DatatypeFactory.dtype_map.has(typename)) {
return DatatypeFactory.dtype_map.get(typename);
} else {
throw new Error("DatatypeFactory: typename ["
+ typename
+ "] found where float|datetime expected");
}
}
}; /*DatatypeFactory*/
/* class to extract event values for charting.
* 'traits' because applies to separately-represented event objects
*/
class DataTraits {
/* .x_slotlookup(ev) => x-value */
#x_slotlookup = null;
/* .y_slotlookup(ev) => y-value */
#y_slotlookup = null;
#x_datatype = null; //DatatypeFactory.lookup("datetime");
#y_datatype = null; //DatatypeFactory.lookup("float");
/* x_nt, y_nt: each should be a pair [slotlookup, typename]
* - slotname is a function :: event -> jsonvalue,
* that extracts an attribute from incoming event in json format
* - typename is float|datetime
*/
constructor(x_nt, y_nt) {
this.#x_slotlookup = x_nt[0];
this.#x_datatype = DatatypeFactory.lookup(x_nt[1]);
this.#y_slotlookup = y_nt[0];
this.#y_datatype = DatatypeFactory.lookup(y_nt[1]);
}
x_datatype() { return this.#x_datatype; }
y_datatype() { return this.#y_datatype; }
x_nominal() { return this.#x_datatype.nominal(); }
y_nominal() { return this.#y_datatype.nominal(); }
mapkey(data_ev) { return this.#x_slotlookup(data_ev); }
x_value(data_ev) { return this.#x_datatype.from_json(this.#x_slotlookup(data_ev)); }
y_value(data_ev) { return this.#y_datatype.from_json(this.#y_slotlookup(data_ev)); }
make_x_scale(domain) { return this.#x_datatype.make_scale(domain); }
make_y_scale(domain) { return this.#y_datatype.make_scale(domain); }
}; /*DataTraits*/
function range_outer(lh, rh) {
return [Math.min(lh[0], rh[0]),
Math.max(lh[1], rh[1])];
} /*range_outer*/
/* a dataset driving a chart.
*
* PLAN: multiple lines in the same chart
* - makeitso dataset can contain multiple data series
* - give each series within a dataset its own index#
* - each series computes its own min/max x/y values
* - take union across series to get chart x/y range
* - new class Dataset
*/
class Dataseries {
/* normalizing transformation for event objects.
* use to produce events {.x_value, .y_value} + key suitable for Map
*/
#data_traits = null; //new DataTraits();
/* .dataset_map :: string -> {key value pair}
* must use string as keys, since Map uses object identity if key is Object
*/
#dataset_map = new Map();
/* vector of key-value pairs, in increasing x-axis order */
#dataset_v = [];
/* min,max value of dataset[i].x_value */
#dset_min_x = null;
#dset_max_x = null;
/* min,max value of dataset[i].y_value */
#dset_min_y = null;
#dset_max_y = null;
#max_key = 0;
constructor(data_traits) {
this.#data_traits = data_traits;
this.recalc_minmax();
}
data_traits() { return this.#data_traits; }
dataset_v() { return this.#dataset_v; }
x_range() { return [this.#dset_min_x, this.#dset_max_x]; }
y_range() { return [this.#dset_min_y, this.#dset_max_y]; }
/* data_ev must have attributes consistent with what .#data_traits expects */
update_dataset(data_ev) {
//console.log("Dataseries.update_dataset: data_ev=", data_ev);
let x = this.#data_traits.x_value(data_ev);
let y = this.#data_traits.y_value(data_ev);
/* using this key to recognize + suppress duplicate points
* (e.g. if browser winds up sending multiple snapshot requests
* for the same dataset)
*/
let mapkey = this.#data_traits.mapkey(data_ev);
//console.log("Dataseries.update_dataset: x=", x, ", y=", y, ", mapkey=", mapkey);
/* in map must use time strings (not Dates) as keys */
if (this.#dataset_map.has(mapkey)) {
/*skip -- assuming that source is immutable */;
} else {
/* kv.key is ordinal number identifying a datum.
* not related to mapkey, except in so far as both work as datum ids
*/
let kv = {key: this.#max_key,
x_value: x,
y_value: y};
/* (reminder: js map keys need to be strings) */
this.#dataset_map.set(mapkey, kv);
this.#dataset_v.push(kv);
this.#max_key = this.#max_key+1;
}
} /*update_dataset*/
recalc_minmax() {
if (this.#dataset_v.length == 0) {
/* min,max value of dataset[i].x_value */
this.#dset_min_x = this.#data_traits.x_nominal();
this.#dset_max_x = this.#data_traits.x_nominal();
/* min,max value of dataset[i].y_value */
this.#dset_min_y = this.#data_traits.y_nominal();
this.#dset_max_y = this.#data_traits.y_nominal();
} else {
/* min,max value of dataset[i].x_value */
this.#dset_min_x = d3.min(this.#dataset_v, (d) => { return d.x_value; });
this.#dset_max_x = d3.max(this.#dataset_v, (d) => { return d.x_value; });
/* min,max value of dataset[i].y_value */
this.#dset_min_y = d3.min(this.#dataset_v, (d) => { return d.y_value; });
this.#dset_max_y = d3.max(this.#dataset_v, (d) => { return d.y_value; });
}
} /*recalc_minmax*/
/* note: caller should invoke .range() before using for drawing */
make_x_scale(xrange) {
return this.#data_traits.make_x_scale(xrange /*domain*/);
} /*make_x_scale*/
/* note: caller should invoke .range() before using for drawing */
make_y_scale(yrange) {
return this.#data_traits.make_y_scale(yrange /*domain*/);
} /*make_y_scale*/
}; /*Dataseries*/
/* bundle multiple dataseries for charting
* for now: can have multiple series, but they need to be driven
* from the same native row storage
*/
class Dataset {
#dataseries_v = [];
/* min/max x-values across all members of .dataseries_v */
#outer_x_range = null;
/* min/max y-values across all members of .dataseries_v */
#outer_y_range = null;
constructor(data_traits_v) {
for (let i=0, n=data_traits_v.length; i<n; ++i) {
this.#dataseries_v[i] = new Dataseries(data_traits_v[i]);
}
this.#recalc_minmax_aux();
}
/* #of dataseries bundled into this dataset */
n_dataseries() {
return this.#dataseries_v.length;
}
/* fetch i'th dataseries.
*
* Require:
* - 0 <= i_dataseries < .n_dataseries()
*/
lookup_dataseries(i_dataseries) {
if (0 <= i_dataseries && i_dataseries < this.n_dataseries()) {
return this.#dataseries_v[i_dataseries];
} else {
throw new Error('lookup_dataseries: expected i in [0..n) i=' + i + ' n=' + this.n_dataseries());
}
} /*lookup_dataseries*/
/* range of x-values, taken across all contained series */
x_range() {
return this.#outer_x_range;
}
/* range of y-values, taken across all contained series */
y_range() {
return this.#outer_y_range;
}
/* update dataset for a single new row */
update_dataset(data_ev) {
for (let i=0, n=this.n_dataseries(); i<n; ++i) {
this.#dataseries_v[i].update_dataset(data_ev);
}
} /*update_dataset*/
/* recalc x,y ranges for each dataseries;
* and recalculate outer x,y range stored in this dataset
*/
recalc_minmax() {
let n=this.n_dataseries();
if (n > 0) {
for (let i=0; i<n; ++i) {
this.#dataseries_v[i].recalc_minmax();
}
this.#recalc_minmax_aux();
}
} /*recalc_minmax*/
#recalc_minmax_aux() {
let n = this.n_dataseries();
if (n > 0) {
/* update dataset minmax (i.e. union of mim-max intervals across series) */
let outer_x = this.#dataseries_v[0].x_range();
let outer_y = this.#dataseries_v[0].y_range();
for (let i=1; i<n; ++i) {
let dataseries = this.#dataseries_v[i];
outer_x = range_outer(outer_x, dataseries.x_range());
outer_y = range_outer(outer_y, dataseries.y_range());
}
this.#outer_x_range = outer_x;
this.#outer_y_range = outer_y;
}
} /*recalc_minmax_aux*/
/* create d3 scale for x-values in this dataset
* (to be used for x-values in all series)
*/
make_x_scale() {
/* plan: verify that all series are compatible?
* e.g. all timeseries or all linear
*/
/* using series #0 (really, its traits) as prototype */
return this.#dataseries_v[0].make_x_scale(this.#outer_x_range);
} /*make_x_scale*/
/* create d3 scale for y-values in this dataset
* (to be used for y-values in all series)
*/
make_y_scale() {
/* plan: verify that all series are compatible?
* e.g. all timeseries or all linear
*/
/* using series #0 (really, its traits) as prototype */
return this.#dataseries_v[0].make_y_scale(this.#outer_y_range);
} /*make_y_scale*/
}; /*Dataset*/
/* drawing code for a line chart.
* uses svg for the "graphed line"
*
* originally extracted from TimeseriesCtl.
*
*
* <------- .chart_w ------->
* +------------------------+ ^
* | | |
* | +----------------+ | |
* | | /| | | |
* | | / | .pad <---> |
* | | / \ | | |
* | | /\ / \ | | .chart_h
* | |/ -- \ --| | |
* | | \/ | | |
* | +----------------+ | |
* | | |
* +------------------------+ v
*
* DOM:
* <? id=#uls> (will attach svg element here)
* +- <svg>
* +- <g id=#x_axis class=xaxis> (d3 will draw x-axis inside, .chart_x_axis() draws)
* +- <g id=#y_axis class=yaxis> (d3 will draw y-axis inside, .chart_y_axis() draws)
* +- <g id=#pts-0 class=pts>
* +- <path id=#line class=line> (.chart_line_gen draws)
*
* may have chart with multiple series;
* we still expect to share
* {.chart_x_scale, .chart_y_scale, .chart_x_axis, .chart_y_axis, .chart_svg}
* across all series.
* we also share .chart_line_gen, since each series supplies normalized (x_value, y_value)
* pairs, and we're sharing the same (x,y) d3-scales
*/
class LineChart {
#chart_w = 100;
#chart_h = 100;
#chart_pad = 10;
/* .chart_xx variables established in .require_gui() */
/* d3 scale for x values */
#chart_x_scale = null;
/* d3 scale for y values */
#chart_y_scale = null;
/* d3 svg-line-generator */
#chart_line_gen = null;
/* d3 axis for x values (bottom of chart area) */
#chart_x_axis = null;
/* d3 axis for y values (left of chart area) */
#chart_y_axis = null;
/* svg chart object (the <svg> tag above in DOM sketch)*/
#chart_svg = null;
constructor(w, h, pad) {
this.#chart_w = w;
this.#chart_h = h;
this.#chart_pad = pad;
}
x_range() { return [this.#chart_pad,
this.#chart_w - this.#chart_pad]; }
/* note: inverting bc svg y-values increase towards bottom of screen;
* we want y-values to increase towards top of screen
*/
y_range() { return [this.#chart_h - this.#chart_pad,
this.#chart_pad]; }
require_gui(parent_d3sel, dataset) {
this.#require_x_scale(dataset);
this.#require_y_scale(dataset);
this.#require_linegen(); /*will use .chart_x_scale, .chart_y_scale */
this.#require_x_axis(); /*will use .chart_x_scale*/
this.#require_y_axis(); /*will use .chart_y_scale*/
this.#require_svg(parent_d3sel, dataset);
}
/* dom element id to use for the i'th dataseries in this chart */
series_html_id(i_dataseries) {
return "pts-" + i_dataseries;
}
/* update chart for new dataset contents
*
* Require:
* - .require_gui(_, dataset) has been called
* - #of dataseries has not changed since last call to .require_svg()
*/
update_chart(dataset) {
/* update d3 scales
* (shared across all series bundled into this dataset
*/
this.#rescale_chart(dataset);
for (let i=0, n=dataset.n_dataseries(); i<n; ++i) {
let dataseries = dataset.lookup_dataseries(i);
let series_svgid = this.series_html_id(i);
// update dataseries (pts)
let line = (this
.#chart_svg
.select("#" + series_svgid)
.select("#line")
.datum(dataseries.dataset_v())
.attr("d", this.#chart_line_gen));
}
} /*update_chart*/
// ----- implementation methods -----
/* svg translate command (a string),
* to move x-axis from origin to location relative to chart svg object
* (i.e. to chart top left corner)
*/
#x_axis_translate_str() {
/* e.g.
* "translate(0,450)"
*/
return "translate(0," + (this.#chart_h - this.#chart_pad) + ")";
}
/* svg translate command (a string)
* to move y-axis from origin to location relative to chart svg object
* (i.e. to chart top left corner)
*/
#y_axis_translate_str() {
/* e.g.
* "translate(50,0)"
*/
return "translate(" + this.#chart_pad + ",0)";
}
#require_x_scale(dataset) {
if (!this.#chart_x_scale) {
this.#chart_x_scale = (dataset.make_x_scale()
.range(this.x_range())
.nice());
}
return this.#chart_x_scale;
} /*require_x_scale*/
#require_y_scale(dataset) {
if (!this.#chart_y_scale) {
this.#chart_y_scale = (dataset.make_y_scale()
.range(this.y_range())
.nice());
}
return this.#chart_y_scale;
} /*require_y_scale*/
#require_linegen() {
if (!this.#chart_line_gen) {
this.#chart_line_gen = (d3.line()
.x((d) => { return this.#chart_x_scale(d.x_value); })
.y((d) => { return this.#chart_y_scale(d.y_value); }));
}
} /*require_linegen*/
#require_x_axis() {
if (!this.#chart_x_axis) {
this.#chart_x_axis = (d3
.axisBottom()
.scale(this.#chart_x_scale)
.ticks(10));
}
}
#require_y_axis() {
if (!this.#chart_y_axis) {
this.#chart_y_axis = (d3
.axisLeft()
.scale(this.#chart_y_scale)
.ticks(10));
}
}
#require_svg(parent_d3sel, dataset) {
if (!this.#chart_svg) {
this.#chart_svg = (parent_d3sel // .select("#uls")
.append("svg")
.attr("width", this.#chart_w)
.attr("height", this.#chart_h));
/* svg group comprising x-axis */
this.#chart_svg.append("g")
.attr("class", "xaxis")
.attr("id", "x_axis")
.attr("transform", this.#x_axis_translate_str())
.call(this.#chart_x_axis);
/* svg group comprising y-axis */
this.#chart_svg.append("g")
.attr("class", "yaxis")
.attr("id", "y_axis")
.attr("transform", this.#y_axis_translate_str())
.call(this.#chart_y_axis);
for (let i=0, n=dataset.n_dataseries(); i<n; ++i) {
let dataseries = dataset.lookup_dataseries(i);
let series_svgid = this.series_html_id(i);
/* svg group comprising chart dataseries
* chart_line_gen gets invoked for each member of .datum()
*/
this.#chart_svg.append("g")
.attr("class", "pts")
.attr("id", series_svgid)
.append("path")
.attr("class", "line")
.attr("id", "line")
.attr("fill", "none")
.attr("stroke", "black")
.datum(dataseries.dataset_v())
.attr("d", this.#chart_line_gen);
}
}
}
#rescale_chart(dataset) {
this.#chart_x_scale.domain(dataset.x_range());
this.#chart_y_scale.domain(dataset.y_range());
// update x-axis
this.#chart_svg.selectAll("#x_axis")
.attr("transform",
this.#x_axis_translate_str())
.call(this.#chart_x_axis);
// update y-axis
this.#chart_svg.selectAll("#y_axis")
.attr("transform",
this.#y_axis_translate_str())
.call(this.#chart_y_axis);
}
}; /*LineChart*/
class Controller {
};
/*
* DOM:
* <? id=#uls> (will attach svg element here)
* +- <svg>
* +- <g id=#x_axis class=xaxis> (d3 will draw x-axis inside, .chart_x_axis() draws)
* +- <g id=#y_axis class=yaxis> (d3 will draw y-axis inside, .chart_y_axis() draws)
* +- <g id=#pts-0 class=pts>
* +- <path id=#line class=line> (.chart_line_gen draws)
*/
class TimeseriesCtl extends Controller {
#dataset_uri = '';
#dataset = null;
#chart = new LineChart(500 /*w*/,
250 /*h*/,
50 /*pad*/);
constructor(dataset_uri, data_traits_v) {
super();
this.#dataset_uri = dataset_uri;
this.#dataset = new Dataset(data_traits_v);
}
static rescale_dataset(dataset) {
dataset.recalc_minmax();
} /*rescale_dataset*/
/* request dataseries snapshot from webserver;
* update+draw graph when snapshot arrives
*
* NOTE:
* 1. typical web docs (e.g. MDN) will advise using response.json():
* fetch(uri)
* .then((response) => response.json())
* .then((data) => dostuffwith(data))
*
* however, this has a flaw: standard json is missing special floating-point values (!!);
* in particular it has no representation for nan/+inf/-inf
* 2. we want to use the extended json standard 'json5';
* however need care since JSON5.parse() fails spuriously (at least JSON5/chrome asof 24sep2022)
* if given a promise
*/
request() {
fetch(this.#dataset_uri)
.then((response) => response.text())
.then((text) => this.on_snapshot_text(text));
// .then((text) => JSON5.parse()
// .then((data) => this.on_snapshot(data));
} /*request*/
/* update from snapshot json text */
on_snapshot_text(text) {
const data = JSON5.parse(text);
this.on_snapshot(data);
} /*on_snapshot_text*/
/* update from snapshot
*
* .on_snapshot() => .#dataset => .on_dataset()
*/
on_snapshot(data) {
//console.log("on_snapshot: data=", data);
data.forEach((x, i) => {
// REFACTORME
if (x._name_ == "UpxEvent") {
this.on_update(x);
} else if (x._name_ == "KalmanFilterStateExt") {
this.on_update(x);
} else {
console.log("unexpected json record x=", x);
}
});
this.on_dataset(this.#dataset);
} /*on_snapshot*/
/* update from websocket
*
* .on_update() => .#dataset => .on_dataset()
*/
on_update(data_ev) {
this.#dataset.update_dataset(data_ev);
this.on_dataset(this.#dataset);
} /*on_update*/
/* call after modifying .#dataset
*
* .on_dataset() =|=> .rescale_dataset() =|========> .chart_x_axis ===\
* | |========> .chart_x_axis =\ |
* | | |
* |=> .chart_svg.#x_axis <==========================/ |
* |=> .chart_svg.#y_axis <============================/
*/
on_dataset(dataset) {
//console.log("on_dataset: dataset=", dataset);
// update x-scale, y-scale
TimeseriesCtl.rescale_dataset(dataset);
this.#chart.update_chart(dataset);
} /*on_dataset*/
/* e.g.
* ctl.require_gui(d3.select("#uls"))
* to build chart gui under DOM element with id="uls"
*/
require_gui(parent_d3sel) {
this.#chart.require_gui(parent_d3sel, this.#dataset);
} /*require_gui*/
}; /*TimeseriesCtl*/
/* controller for timeseries graph, from uri [/dyn/uls/snap] + [/ws/uls] */
var uls_ctl = false;
var uls_ctl_enabled = true;
if (uls_ctl_enabled) {
uls_ctl = new TimeseriesCtl('/dyn/uls/snap',
[new DataTraits([(ev) => ev.tm, "datetime"],
[(ev) => ev.upx, "float"])]);
uls_ctl.require_gui(d3.select("#uls"));
uls_ctl.request();
}
/* controller for timeseries graph, from uri [/dyn/kfs/snap] + [/ws/kfs] */
var kfs_ctl = false;
var kfs_ctl_enabled = true;
if (kfs_ctl_enabled) {
kfs_ctl = new TimeseriesCtl('/dyn/kfs/snap',
[new DataTraits([(ev) => ev.tk,
"datetime"],
[(ev) => { return ev.x[0]; },
"float"]),
/* 2.sigma below estimate */
new DataTraits([(ev) => ev.tk,
"datetime"],
[(ev) => { return Math.max(0.0, ev.x[0] - 2.0 * Math.sqrt(ev.P[0][0])); },
"float"]),
/* 2.sigma above estimate */
new DataTraits([(ev) => ev.tk,
"datetime"],
[(ev) => { return Math.min(1.0, ev.x[0] + 2.0 * Math.sqrt(ev.P[0][0])); },
"float"])
]);
kfs_ctl.require_gui(d3.select("#kfs"));
kfs_ctl.request();
}
let key_fn = ((d) => { return d.key; });
/* controller for volsurface graph (strike -> volatility),
* from uri [/dyn/kf/snap]
*/
d3.select("#refresh")
.on("click",
function() {
console.log("button[#refresh] clicked");
uls_ctl.request();
});
var srv_ws = null;
function content_loaded_fn()
{
console.log("Hi Roly, DOM loaded");
/* use this url to create websocket to the server that delivered current webpage */
let ws_url = choose_ws_url("" /*url_suffix*/);
console.log("ws_url: [", ws_url, "]");
srv_ws = new WebSocket(ws_url, "lws-minimal");
try {
// srv_ws.onopen = () => { ... };
// srv_ws.onclose = () => { ... };
srv_ws.onmessage = (msg) => {
/* msg has dozens of attributes, too many to list here
* actual application message appears in the .data attribute
* (as nested js string)
*/
//console.log("incoming ws msg: [", msg, "]");
let msgdata = JSON5.parse(msg.data);
//console.log("msgdata: [", msgdata, "]");
let stream_name = msgdata.stream;
let event = msgdata.event;
if (stream_name == "/ws/uls") {
if (uls_ctl) {
uls_ctl.on_update(event);
}
} else if (stream_name == "/ws/kfs") {
if (kfs_ctl) {
kfs_ctl.on_update(event);
}
} else {
console.log("unknown stream name [", stream_name, "]");
}
};
} catch(excetpion) {
asert("<p>Error: " + exception);
}
console.log("srv_ws state [", srv_ws.readyState, "]");
srv_ws.addEventListener('open',
(event) => {
console.log("srv_ws state [", srv_ws.readyState, "]");
if (uls_ctl) {
srv_ws.send("{\"cmd\": \"subscribe\", \"stream\": \"/ws/uls\"} ");
}
if (kfs_ctl) {
srv_ws.send("{\"cmd\": \"subscribe\", \"stream\": \"/ws/kfs\"} ");
}
//socket.send('Hello Server!');
});
} /*content_loaded_fn*/
document.addEventListener("DOMContentLoaded",
content_loaded_fn,
false);
/* end ex_websock.js */

View file

@ -0,0 +1,64 @@
function get_appropriate_ws_url(extra_url)
{
var pcol;
var u = document.URL;
/*
* We open the websocket encrypted if this page came on an
* https:// url itself, otherwise unencrypted
*/
if (u.substring(0, 5) === "https") {
pcol = "wss://";
u = u.substr(8);
} else {
pcol = "ws://";
if (u.substring(0, 4) === "http")
u = u.substr(7);
}
u = u.split("/");
/* + "/xxx" bit is for IE10 workaround */
return pcol + u[0] + "/" + extra_url;
}
function new_ws(urlpath, protocol)
{
return new WebSocket(urlpath, protocol);
}
document.addEventListener("DOMContentLoaded", function() {
var ws = new_ws(get_appropriate_ws_url(""), "lws-minimal");
try {
ws.onopen = function() {
document.getElementById("m").disabled = 0;
document.getElementById("b").disabled = 0;
};
ws.onmessage =function got_packet(msg) {
document.getElementById("r").value =
document.getElementById("r").value + msg.data + "\n";
document.getElementById("r").scrollTop =
document.getElementById("r").scrollHeight;
};
ws.onclose = function(){
document.getElementById("m").disabled = 1;
document.getElementById("b").disabled = 1;
};
} catch(exception) {
alert("<p>Error " + exception);
}
function sendmsg()
{
ws.send(document.getElementById("m").value);
document.getElementById("m").value = "";
}
document.getElementById("b").addEventListener("click", sendmsg);
}, false);

View file

@ -0,0 +1,19 @@
<html>
<head>
<meta charset=utf-8 http-equiv="Content-Language" content="en"/>
<script src="/example.js"></script>
</head>
<body>
<img src="libwebsockets.org-logo.svg">
<img src="strict-csp.svg"><br>
LWS chat <b>minimal ws server example</b>.<br>
Chat is sent to all browsers open on this page.
<br>
<br>
<textarea id=r readonly cols=40 rows=10></textarea><br>
<input type="text" id=m cols=40 rows=1>
<button id=b>Send</button>
</body>
</html>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="117.26mm" height="19.676mm" version="1.1" viewBox="0 0 117.26 19.677" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<path d="m0-2.6715e-4h117.26v19.677h-117.26z" fill="none"/>
<g transform="matrix(.63895 0 0 .63895 2.5477 3.6562)">
<g transform="matrix(.9517 0 0 .9517 3.2398 -93.904)">
<path d="m9.5909 107.4h2.5567v2.649h-2.5567z"/>
<path d="m12.12 107.36a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
<path d="m12.127 110.05a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
</g>
<path d="m12.174 13.987a1.2015 1.2015 0 0 1-1.2024 1.2024 1.2015 1.2015 0 0 1-1.2006-1.2024 1.2015 1.2015 0 0 1 1.2006-1.2005 1.2015 1.2015 0 0 1 1.2024 1.2005" stroke-linecap="round" stroke-width=".98647"/>
<path d="m8.2754 5.0474h2.468v5.6755h-2.468z"/>
<path d="m16.25 13.965a1.2015 1.2015 0 0 1-1.2027 1.2005 1.2015 1.2015 0 0 1-1.2004-1.2005 1.2015 1.2015 0 0 1 1.2004-1.2025 1.2015 1.2015 0 0 1 1.2027 1.2025" stroke-linecap="round" stroke-width=".98647"/>
<path d="m19.545 13.928a1.2015 1.2015 0 0 1-1.2025 1.2026 1.2015 1.2015 0 0 1-1.2003-1.2026 1.2015 1.2015 0 0 1 1.2003-1.2005 1.2015 1.2015 0 0 1 1.2025 1.2005" stroke-linecap="round" stroke-width=".98647"/>
<path d="m23.75 13.902a1.2015 1.2015 0 0 1-1.2005 1.2024 1.2015 1.2015 0 0 1-1.2025-1.2024 1.2015 1.2015 0 0 1 1.2025-1.2005 1.2015 1.2015 0 0 1 1.2005 1.2005" stroke-linecap="round" stroke-width=".98647"/>
<path d="m26.249 5.0292a1.2015 1.2015 0 0 1-1.2027 1.2004 1.2015 1.2015 0 0 1-1.2004-1.2004 1.2015 1.2015 0 0 1 1.2004-1.2026 1.2015 1.2015 0 0 1 1.2027 1.2026" stroke-linecap="round" stroke-width=".98647"/>
<g transform="matrix(.9517 0 0 .9517 6.3252 -93.961)">
<path d="m9.5909 107.4h2.5567v2.649h-2.5567z"/>
<path d="m12.12 107.36a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
<path d="m12.127 110.05a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
</g>
<g transform="matrix(.9517 0 0 .9517 9.3806 -93.988)">
<path d="m9.5909 107.4h2.5567v2.649h-2.5567z"/>
<path d="m12.12 107.36a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
<path d="m12.127 110.05a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
</g>
<g transform="matrix(.9517 0 0 .9517 13.506 -94.006)">
<path d="m9.5909 107.4h2.5567v2.649h-2.5567z"/>
<path d="m12.12 107.36a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
<path d="m12.127 110.05a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
</g>
<g transform="matrix(.9517 0 0 .9517 -.82062 -93.74)">
<path d="m9.5909 107.4h2.5567v2.649h-2.5567z"/>
<path d="m12.12 107.36a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
<path d="m12.127 110.05a1.2625 1.2625 0 0 1-1.2625 1.262 1.2625 1.2625 0 0 1-1.2625-1.262 1.2625 1.2625 0 0 1 1.2625-1.262 1.2625 1.2625 0 0 1 1.2625 1.262" stroke-linecap="round" stroke-width="1.0365"/>
</g>
<path d="m10.703 5.0413a1.2015 1.2015 0 0 1-1.2006 1.2025 1.2015 1.2015 0 0 1-1.2025-1.2025 1.2015 1.2015 0 0 1 1.2025-1.2004 1.2015 1.2015 0 0 1 1.2006 1.2004" stroke-linecap="round" stroke-width=".98647"/>
</g>
<g transform="matrix(2.6825 0 0 2.6825 -289.72 -275.57)" dominant-baseline="auto" stroke-width=".29098" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" aria-label="libwebsockets.org">
<path d="m117.05 105.01v2.752h0.224v-2.752z"/>
<path d="m117.95 105.71v2.057h0.223v-2.057zm-0.04-0.695v0.318h0.297v-0.318z"/>
<path d="m118.8 105.01v2.752h0.203l0.02-0.251c0.101 0.157 0.244 0.279 0.649 0.279 0.601 0 0.81-0.307 0.81-1.083 0-0.541-0.09-1.03-0.81-1.03-0.468 0-0.594 0.196-0.649 0.283v-0.95zm0.845 0.87c0.552 0 0.618 0.339 0.618 0.855 0 0.486-0.05 0.852-0.611 0.852-0.426 0-0.632-0.181-0.632-0.852 0-0.597 0.15-0.855 0.625-0.855z"/>
<path d="m120.79 105.71 0.555 2.057h0.314l0.479-1.858 0.482 1.858h0.314l0.551-2.057h-0.23l-0.482 1.879-0.482-1.879h-0.303l-0.486 1.879-0.478-1.879z"/>
<path d="m125.54 106.8v-0.157c0-0.433-0.06-0.964-0.869-0.964-0.824 0-0.891 0.566-0.891 1.079 0 0.688 0.196 1.034 0.926 1.034 0.495 0 0.792-0.178 0.831-0.663h-0.224c-0.01 0.377-0.262 0.464-0.628 0.464-0.611 0-0.688-0.314-0.685-0.793zm-1.54-0.195c0.02-0.374 0.08-0.727 0.667-0.727 0.493 0 0.653 0.21 0.65 0.727z"/>
<path d="m126.04 105.01v2.752h0.203l0.02-0.251c0.101 0.157 0.244 0.279 0.649 0.279 0.601 0 0.81-0.307 0.81-1.083 0-0.541-0.09-1.03-0.81-1.03-0.468 0-0.593 0.196-0.649 0.283v-0.95zm0.845 0.87c0.552 0 0.618 0.339 0.618 0.855 0 0.486-0.05 0.852-0.611 0.852-0.426 0-0.632-0.181-0.632-0.852 0-0.597 0.151-0.855 0.625-0.855z"/>
<path d="m129.86 106.28c0-0.24-0.06-0.604-0.799-0.604-0.426 0-0.814 0.109-0.814 0.601 0 0.381 0.241 0.471 0.489 0.503l0.576 0.08c0.273 0.04 0.381 0.08 0.381 0.338 0 0.315-0.224 0.391-0.618 0.391-0.646 0-0.646-0.223-0.646-0.481h-0.224c0 0.279 0.04 0.684 0.852 0.684 0.37 0 0.856-0.05 0.856-0.608 0-0.415-0.294-0.492-0.486-0.516l-0.607-0.08c-0.234-0.03-0.356-0.07-0.356-0.297 0-0.192 0.06-0.408 0.593-0.408 0.583 0 0.58 0.237 0.58 0.401z"/>
<path d="m130.35 106.74c0 0.594 0.06 1.058 0.908 1.058 0.883 0 0.904-0.51 0.904-1.103 0-0.601-0.108-1.01-0.904-1.01-0.852 0-0.908 0.475-0.908 1.055zm0.908-0.852c0.569 0 0.684 0.213 0.684 0.803 0 0.636-0.05 0.904-0.684 0.904-0.584 0-0.688-0.223-0.688-0.824 0-0.6 0.04-0.883 0.688-0.883z"/>
<path d="m134.12 107.03c0 0.471-0.22 0.565-0.656 0.565-0.496 0-0.667-0.181-0.667-0.838 0-0.782 0.272-0.869 0.677-0.869 0.283 0 0.625 0.06 0.625 0.531h0.22c0.01-0.734-0.639-0.734-0.845-0.734-0.632 0-0.897 0.245-0.897 1.058 0 0.793 0.248 1.055 0.908 1.055 0.468 0 0.834-0.115 0.859-0.768z"/>
<path d="m135.05 106.64v-1.624h-0.22v2.752h0.22v-1.072l1.076 1.072h0.321l-1.149-1.1 1.041-0.957h-0.318z"/>
<path d="m138.48 106.8v-0.157c0-0.433-0.06-0.964-0.87-0.964-0.824 0-0.89 0.566-0.89 1.079 0 0.688 0.195 1.034 0.925 1.034 0.496 0 0.793-0.178 0.831-0.663h-0.223c-0.01 0.377-0.262 0.464-0.629 0.464-0.611 0-0.688-0.314-0.684-0.793zm-1.54-0.195c0.02-0.374 0.08-0.727 0.667-0.727 0.492 0 0.653 0.21 0.649 0.727z"/>
<path d="m139.29 105.71h-0.457v0.206h0.457v1.348c0 0.335 0.07 0.531 0.664 0.531 0.09 0 0.139 0 0.181-0.01v-0.209c-0.06 0-0.136 0.01-0.251 0.01-0.374 0-0.374-0.129-0.374-0.381v-1.292h0.541v-0.206h-0.541v-0.488h-0.22z"/>
<path d="m142.15 106.28c0-0.24-0.06-0.604-0.799-0.604-0.426 0-0.814 0.109-0.814 0.601 0 0.381 0.241 0.471 0.489 0.503l0.576 0.08c0.272 0.04 0.381 0.08 0.381 0.338 0 0.315-0.224 0.391-0.618 0.391-0.646 0-0.646-0.223-0.646-0.481h-0.224c0 0.279 0.04 0.684 0.852 0.684 0.37 0 0.856-0.05 0.856-0.608 0-0.415-0.294-0.492-0.486-0.516l-0.607-0.08c-0.234-0.03-0.356-0.07-0.356-0.297 0-0.192 0.06-0.408 0.593-0.408 0.583 0 0.58 0.237 0.58 0.401z"/>
<path d="m142.76 107.44v0.321h0.293v-0.321z"/>
<path d="m143.54 106.74c0 0.594 0.06 1.058 0.908 1.058 0.883 0 0.904-0.51 0.904-1.103 0-0.601-0.108-1.01-0.904-1.01-0.852 0-0.908 0.475-0.908 1.055zm0.908-0.852c0.569 0 0.684 0.213 0.684 0.803 0 0.636-0.05 0.904-0.684 0.904-0.583 0-0.688-0.223-0.688-0.824 0-0.6 0.04-0.883 0.688-0.883z"/>
<path d="m145.81 105.71v2.057h0.22v-1.337c0-0.542 0.419-0.542 0.569-0.542h0.206v-0.213c-0.37 0-0.576 0-0.775 0.259l-0.01-0.231z"/>
<path d="m149.11 105.62c-0.331 0.01-0.391 0.136-0.429 0.217-0.143-0.14-0.44-0.158-0.646-0.158-0.503 0-0.821 0.14-0.821 0.601 0 0.119 0.02 0.272 0.126 0.398-0.175 0.02-0.265 0.157-0.265 0.325 0 0.1 0.04 0.258 0.22 0.311-0.07 0.03-0.245 0.132-0.245 0.408 0 0.412 0.374 0.51 1.006 0.51 0.593 0 0.971-0.09 0.971-0.541 0-0.297-0.175-0.465-0.601-0.489l-0.922-0.06c-0.105 0-0.223-0.05-0.223-0.182 0-0.08 0.04-0.132 0.153-0.195 0.123 0.09 0.322 0.126 0.629 0.126 0.384 0 0.82-0.05 0.82-0.625 0-0.172-0.04-0.227-0.08-0.297 0.06-0.112 0.108-0.133 0.307-0.143zm-1.065 0.258c0.535 0 0.622 0.182 0.622 0.388 0 0.339-0.178 0.426-0.611 0.426-0.423 0-0.622-0.07-0.622-0.384 0-0.356 0.192-0.43 0.611-0.43zm0.206 1.512c0.36 0.02 0.556 0.06 0.556 0.308 0 0.216-0.147 0.331-0.751 0.331-0.674 0-0.8-0.1-0.8-0.345 0-0.304 0.318-0.336 0.328-0.336z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24.78mm" height="24.78mm" version="1.1" viewBox="0 0 24.780247 24.780247" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="linearGradient955" x1="66.618" x2="82.588" y1="81.176" y2="64.828" gradientTransform="matrix(.82538 0 0 .82538 -392 -92.399)" gradientUnits="userSpaceOnUse">
<stop stop-color="#0aa70b" offset="0"/>
<stop stop-color="#3bff39" offset="1"/>
</linearGradient>
<filter id="filter945" x="-.0516" y="-.0516" width="1.1032" height="1.1032" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="0.58510713"/>
</filter>
</defs>
<g transform="translate(342.15 43.638)">
<circle transform="matrix(.82538 0 0 .82538 -392 -92.399)" cx="75.406" cy="74.089" r="13.607" filter="url(#filter945)" stroke="#000" stroke-linecap="round" stroke-width="1.565"/>
<circle cx="-330.23" cy="-31.716" r="11.231" fill="url(#linearGradient955)" stroke="#000" stroke-linecap="round" stroke-width="1.2917"/>
<g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".51676px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Strict">
<path d="m-330.78-33.775q0 0.73996-0.53676 1.154-0.53676 0.41407-1.4569 0.41407-0.99684 0-1.5336-0.25688v-0.62878q0.34506 0.14569 0.75147 0.23004 0.4064 0.08435 0.80514 0.08435 0.65177 0 0.9815-0.24538 0.32972-0.24921 0.32972-0.69012 0-0.29138-0.11885-0.47542-0.11502-0.18787-0.39107-0.34506-0.27221-0.15719-0.83198-0.35656-0.78213-0.27988-1.1195-0.66328-0.33356-0.3834-0.33356-1.0007 0-0.64794 0.48692-1.0313 0.48691-0.3834 1.2882-0.3834 0.83581 0 1.5374 0.30672l-0.2032 0.56743q-0.69395-0.29138-1.3496-0.29138-0.51759 0-0.80897 0.22237t-0.29138 0.61727q0 0.29138 0.10735 0.47925 0.10735 0.18403 0.36039 0.34123 0.25688 0.15336 0.78214 0.34122 0.88182 0.31439 1.2115 0.67478 0.33356 0.3604 0.33356 0.93549z"/>
<path d="m-328.37-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36807v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.18019 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
<path d="m-325.04-36.562q0.27989 0 0.50226 0.04601l-0.0882 0.59044q-0.26072-0.05751-0.46008-0.05751-0.50993 0-0.87415 0.41407-0.3604 0.41407-0.3604 1.0313v2.2544h-0.63644v-4.2021h0.52525l0.0729 0.7783h0.0307q0.23388-0.41024 0.5636-0.63261 0.32972-0.22237 0.72462-0.22237z"/>
<path d="m-323.11-32.284h-0.63644v-4.2021h0.63644zm-0.69012-5.3408q0-0.21854 0.10735-0.31822 0.10735-0.10352 0.26838-0.10352 0.15336 0 0.26455 0.10352 0.11118 0.10352 0.11118 0.31822 0 0.2147-0.11118 0.32206-0.11119 0.10352-0.26455 0.10352-0.16103 0-0.26838-0.10352-0.10735-0.10735-0.10735-0.32206z"/>
<path d="m-320.07-32.207q-0.91249 0-1.4147-0.55976-0.49842-0.5636-0.49842-1.5911 0-1.0543 0.50609-1.6294 0.50992-0.5751 1.4492-0.5751 0.30288 0 0.60577 0.06518 0.30288 0.06518 0.47541 0.15336l-0.19553 0.54059q-0.21087-0.08435-0.46008-0.13802-0.24921-0.05751-0.44091-0.05751-1.2806 0-1.2806 1.6333 0 0.77447 0.31055 1.1885 0.31439 0.41407 0.92783 0.41407 0.52526 0 1.0774-0.22621v0.5636q-0.42174 0.21854-1.062 0.21854z"/>
<path d="m-316.65-32.732q0.16869 0 0.32589-0.023 0.15719-0.02684 0.24921-0.05368v0.48692q-0.10352 0.04984-0.30672 0.08051-0.19937 0.03451-0.3604 0.03451-1.2192 0-1.2192-1.2844v-2.4998h-0.60194v-0.30672l0.60194-0.26455 0.26838-0.89716h0.36806v0.97384h1.2192v0.49458h-1.2192v2.4729q0 0.37957 0.1802 0.58277 0.1802 0.2032 0.49459 0.2032z"/>
</g>
<g fill="#fff">
<g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".3317px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Content">
<path d="m-332.67-30.173q-0.5931 0-0.93764 0.39622-0.34208 0.39376-0.34208 1.0804 0 0.70631 0.32977 1.0927 0.33224 0.38392 0.94503 0.38392 0.37653 0 0.85889-0.13536v0.36669q-0.37407 0.14028-0.92288 0.14028-0.7949 0-1.228-0.48236-0.43067-0.48236-0.43067-1.3708 0-0.55619 0.20672-0.97456 0.20919-0.41837 0.60049-0.64478 0.39376-0.22641 0.92533-0.22641 0.56603 0 0.98933 0.20672l-0.1772 0.35931q-0.40852-0.19196-0.81705-0.19196z"/>
<path d="m-328.77-28.248q0 0.65955-0.33224 1.0312-0.33223 0.36915-0.91795 0.36915-0.36177 0-0.64233-0.16981-0.28055-0.16981-0.43313-0.48728-0.15259-0.31747-0.15259-0.74322 0-0.65955 0.32978-1.0262 0.32977-0.36915 0.91549-0.36915 0.56603 0 0.89827 0.37653 0.3347 0.37653 0.3347 1.0189zm-2.0549 0q0 0.51681 0.20672 0.78752 0.20673 0.27071 0.60787 0.27071t0.60787-0.26825q0.20918-0.27071 0.20918-0.78998 0-0.51435-0.20918-0.78014-0.20673-0.26825-0.61279-0.26825-0.40115 0-0.60541 0.26333-0.20426 0.26333-0.20426 0.78506z"/>
<path d="m-326.21-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15012-0.16243-0.47005-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33223l0.0664 0.36915h0.0197q0.12551-0.19934 0.35192-0.30762 0.22642-0.11075 0.50451-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
<path d="m-324.09-27.185q0.10828 0 0.20918-0.01477 0.1009-0.01723 0.15997-0.03445v0.31255q-0.0665 0.03199-0.19688 0.05168-0.12797 0.02215-0.23134 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38637v-0.19688l0.38637-0.16981 0.17227-0.57588h0.23626v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11566 0.13043 0.31747 0.13043z"/>
<path d="m-322.04-26.848q-0.59802 0-0.94502-0.36423-0.34454-0.36423-0.34454-1.0115 0-0.65217 0.31993-1.0361 0.32239-0.38392 0.86381-0.38392 0.50697 0 0.80229 0.3347 0.29532 0.33224 0.29532 0.87858v0.25841h-1.8581q0.0123 0.47497 0.23872 0.72107 0.22887 0.2461 0.64232 0.2461 0.4356 0 0.86135-0.18212v0.36423q-0.21657 0.09352-0.41099 0.13289-0.19195 0.04184-0.46513 0.04184zm-0.11074-2.4536q-0.32485 0-0.51927 0.21165-0.19196 0.21165-0.22642 0.58572h1.4102q0-0.38638-0.17227-0.59064-0.17227-0.20672-0.4922-0.20672z"/>
<path d="m-318.51-26.897v-1.7449q0-0.32978-0.15012-0.4922-0.15013-0.16243-0.47006-0.16243-0.42329 0-0.62017 0.22887-0.19688 0.22887-0.19688 0.75553v1.4151h-0.40853v-2.6973h0.33224l0.0664 0.36915h0.0197q0.12552-0.19934 0.35193-0.30762 0.22641-0.11075 0.5045-0.11075 0.48728 0 0.73338 0.23626 0.2461 0.2338 0.2461 0.75061v1.7596z"/>
<path d="m-316.4-27.185q0.10829 0 0.20919-0.01477 0.1009-0.01723 0.15996-0.03445v0.31255q-0.0664 0.03199-0.19688 0.05168-0.12797 0.02215-0.23133 0.02215-0.7826 0-0.7826-0.82444v-1.6046h-0.38638v-0.19688l0.38638-0.16981 0.17227-0.57588h0.23625v0.6251h0.7826v0.31747h-0.7826v1.5873q0 0.24364 0.11567 0.37407 0.11567 0.13043 0.31747 0.13043z"/>
</g>
<g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32428px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Security">
<path d="m-332.03-22.859q0 0.46434-0.33683 0.72417-0.33682 0.25984-0.91423 0.25984-0.62553 0-0.96236-0.1612v-0.39456q0.21653 0.09142 0.47155 0.14435 0.25503 0.05293 0.50524 0.05293 0.409 0 0.61591-0.15398 0.20691-0.15638 0.20691-0.43306 0-0.18285-0.0746-0.29833-0.0722-0.11789-0.2454-0.21653-0.17082-0.09864-0.52208-0.22375-0.4908-0.17563-0.70252-0.41622-0.20931-0.24059-0.20931-0.62794 0-0.4066 0.30555-0.64718t0.80838-0.24059q0.52448 0 0.96476 0.19247l-0.12751 0.35607q-0.43547-0.18285-0.84687-0.18285-0.3248 0-0.50765 0.13954-0.18284 0.13954-0.18284 0.38735 0 0.18285 0.0674 0.30074 0.0674 0.11548 0.22615 0.21412 0.1612 0.09624 0.4908 0.21412 0.55336 0.19728 0.76027 0.42344 0.20931 0.22615 0.20931 0.58704z"/>
<path d="m-330.26-21.875q-0.58463 0-0.92386-0.35607-0.33683-0.35607-0.33683-0.98882 0-0.63756 0.31277-1.0129 0.31517-0.37532 0.84446-0.37532 0.49562 0 0.78432 0.3272 0.28871 0.3248 0.28871 0.8589v0.25262h-1.8164q0.012 0.46434 0.23338 0.70492 0.22374 0.24059 0.62793 0.24059 0.42584 0 0.84206-0.17804v0.35607q-0.21171 0.09142-0.40178 0.12992-0.18766 0.0409-0.45471 0.0409zm-0.10827-2.3987q-0.31757 0-0.50764 0.20691-0.18766 0.20691-0.22134 0.5726h1.3786q0-0.37772-0.16841-0.57741-0.16841-0.2021-0.48118-0.2021z"/>
<path d="m-327.56-21.875q-0.5726 0-0.88777-0.35126-0.31277-0.35366-0.31277-0.99844 0-0.66162 0.31758-1.0225 0.31998-0.36088 0.90942-0.36088 0.19007 0 0.38013 0.0409 0.19007 0.0409 0.29833 0.09624l-0.1227 0.33923q-0.13232-0.05293-0.2887-0.08661-0.15639-0.03609-0.27668-0.03609-0.80357 0-0.80357 1.0249 0 0.48599 0.19488 0.74582 0.19728 0.25984 0.58223 0.25984 0.3296 0 0.67605-0.14195v0.35366q-0.26465 0.13714-0.66643 0.13714z"/>
<path d="m-325.89-24.56v1.7106q0 0.32239 0.14676 0.48118 0.14675 0.15879 0.45952 0.15879 0.41381 0 0.60388-0.22615 0.19247-0.22615 0.19247-0.73861v-1.3858h0.39938v2.6369h-0.32961l-0.0577-0.35367h-0.0217q-0.1227 0.19488-0.34163 0.29833-0.21653 0.10345-0.49561 0.10345-0.48118 0-0.72177-0.22856-0.23818-0.22856-0.23818-0.73139v-1.725z"/>
<path d="m-322.04-24.608q0.17563 0 0.31517 0.02887l-0.0553 0.37051q-0.1636-0.03609-0.2887-0.03609-0.31999 0-0.54855 0.25984-0.22615 0.25984-0.22615 0.64718v1.4147h-0.39938v-2.6369h0.32961l0.0457 0.4884h0.0192q0.14676-0.25743 0.35366-0.39697 0.20691-0.13954 0.45472-0.13954z"/>
<path d="m-320.83-21.923h-0.39938v-2.6369h0.39938zm-0.43306-3.3514q0-0.13714 0.0674-0.19969 0.0674-0.06496 0.16841-0.06496 0.0962 0 0.16601 0.06496 0.0698 0.06496 0.0698 0.19969 0 0.13473-0.0698 0.2021-0.0698 0.06496-0.16601 0.06496-0.10105 0-0.16841-0.06496-0.0674-0.06736-0.0674-0.2021z"/>
<path d="m-319.13-22.205q0.10586 0 0.2045-0.01443 0.0986-0.01684 0.15638-0.03368v0.30555q-0.065 0.03128-0.19247 0.05052-0.1251 0.02165-0.22615 0.02165-0.76507 0-0.76507-0.80597v-1.5686h-0.37773v-0.19247l0.37773-0.16601 0.16841-0.56298h0.23096v0.6111h0.76508v0.31036h-0.76508v1.5518q0 0.23818 0.11308 0.3657t0.31036 0.12751z"/>
<path d="m-318.66-24.56h0.42825l0.57742 1.5037q0.19006 0.51486 0.23577 0.74342h0.0192q0.0313-0.1227 0.12992-0.41862 0.10105-0.29833 0.6544-1.8285h0.42825l-1.1332 3.0025q-0.16841 0.44509-0.39456 0.63034-0.22375 0.18766-0.55095 0.18766-0.18285 0-0.36088-0.0409v-0.31998q0.13232 0.02887 0.29592 0.02887 0.41141 0 0.58704-0.46193l0.14676-0.37532z"/>
</g>
<g transform="matrix(.70929 0 0 .70929 -99.465 -12.686)" stroke-width=".32334px" style="font-feature-settings:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal" aria-label="Policy">
<path d="m-329.37-19.254q0 0.53256-0.36464 0.82043-0.36224 0.28547-1.0387 0.28547h-0.41261v1.3794h-0.40782v-3.5072h0.90919q1.3146 0 1.3146 1.0219zm-1.816 0.75566h0.36703q0.54215 0 0.78445-0.17512 0.24229-0.17512 0.24229-0.56135 0-0.34784-0.2279-0.51817t-0.71008-0.17032h-0.45579z"/>
<path d="m-326.43-18.086q0 0.64291-0.32386 1.0051-0.32385 0.35984-0.89479 0.35984-0.35264 0-0.62612-0.16552t-0.42221-0.47498q-0.14873-0.30946-0.14873-0.72447 0-0.64291 0.32145-1.0003 0.32146-0.35984 0.8924-0.35984 0.55175 0 0.8756 0.36703 0.32626 0.36704 0.32626 0.99315zm-2.0031 0q0 0.50377 0.20151 0.76765t0.59253 0.26388q0.39103 0 0.59254-0.26148 0.2039-0.26388 0.2039-0.77005 0-0.50137-0.2039-0.76046-0.20151-0.26148-0.59733-0.26148-0.39103 0-0.59014 0.25668-0.19911 0.25668-0.19911 0.76525z"/>
<path d="m-325.33-16.769h-0.39822v-3.7327h0.39822z"/>
<path d="m-324.09-16.769h-0.39822v-2.6292h0.39822zm-0.43181-3.3417q0-0.13674 0.0672-0.19911 0.0672-0.06477 0.16793-0.06477 0.0959 0 0.16552 0.06477 0.0696 0.06477 0.0696 0.19911t-0.0696 0.20151q-0.0696 0.06477-0.16552 0.06477-0.10076 0-0.16793-0.06477-0.0672-0.06717-0.0672-0.20151z"/>
<path d="m-322.19-16.721q-0.57094 0-0.8852-0.35024-0.31186-0.35264-0.31186-0.99555 0-0.6597 0.31666-1.0195 0.31906-0.35984 0.90679-0.35984 0.18951 0 0.37903 0.04078 0.18951 0.04078 0.29746 0.09596l-0.12234 0.33825q-0.13194-0.05278-0.28787-0.08636-0.15593-0.03598-0.27588-0.03598-0.80123 0-0.80123 1.0219 0 0.48458 0.19431 0.74366 0.19671 0.25908 0.58054 0.25908 0.32865 0 0.67409-0.14154v0.35264q-0.26388 0.13674-0.6645 0.13674z"/>
<path d="m-321.31-19.398h0.427l0.57574 1.4993q0.18952 0.51337 0.2351 0.74127h0.0192q0.0312-0.12234 0.12954-0.41741 0.10076-0.29747 0.65251-1.8232h0.427l-1.1299 2.9938q-0.16792 0.4438-0.39342 0.62852-0.2231 0.18712-0.54935 0.18712-0.18232 0-0.35984-0.04078v-0.31906q0.13194 0.02879 0.29507 0.02879 0.41021 0 0.58533-0.46059l0.14634-0.37423z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,282 @@
/* @file websock_utest_main.cpp */
#include "websock/Webserver.hpp"
#include "volfit/init_volfit.hpp"
#include "volfit/Volfit.hpp"
#include "volfit/VolfitInputCapture.hpp"
#include "filter/init_filter.hpp"
#include "filter/KalmanFilterSvc.hpp"
#include "option/StrikeSetOmd.hpp"
#include "option/StrikeSetMarketModel.hpp"
#include "option/UlMarketModel.hpp"
#include "option/PricingContext.hpp"
#include "option/OptionStrikeSet.hpp"
#include "process/init_process.hpp"
#include "process/RealizationSource.hpp"
#include "process/RealizationTracer.hpp"
#include "process/UpxEvent.hpp"
#include "process/ExpProcess.hpp"
#include "process/BrownianMotion.hpp"
#include "simulator/init_simulator.hpp"
#include "simulator/Simulator.hpp"
#include "reactor/EventStore.hpp"
#include "randomgen/random_seed.hpp"
#include "randomgen/xoshiro256.hpp"
#include "time/Time.hpp"
#include "printjson/PrintJson.hpp"
#include "indentlog/print/tag.hpp"
#include <signal.h>
/* webserver instance */
static xo::ref::rp<xo::web::Webserver> g_ws;
void sigint_handler(int /*sig*/) {
std::cerr << "main thread interrupt_handler\n";
if (g_ws)
g_ws->interrupt_stop_webserver();
}
int
main(int argc, char **argv) {
using xo::web::Webserver;
using xo::web::WebserverConfig;
using xo::json::PrintJsonSingleton;
using xo::vf::Volfit;
using xo::vf::VolfitInputCaptureSvc;
using xo::kalman::KalmanFilterStateExt;
using xo::kalman::KalmanFilterSvc;
using xo::kalman::KalmanFilterSpec;
using xo::option::FlatVolsfc;
using xo::option::BboTick;
using xo::option::StrikeSetOmd;
using xo::option::StrikeSetMarketModel;
using xo::option::UlMarketModel;
using xo::option::PricingContext;
using xo::option::OptionStrikeSet;
using xo::option::Secid;
using xo::option::Pxtick;
using xo::process::UpxEvent;
using xo::process::RealizationSource;
using xo::process::RealizationTracer;
using xo::process::StochasticProcess;
using xo::process::ExpProcess;
using xo::process::BrownianMotion;
using xo::sim::Simulator;
using xo::reactor::PtrEventStore;
using xo::reactor::StructEventStore;
using xo::reactor::ReactorSource;
using xo::rng::Seed;
using xo::rng::xoshiro256ss;
using xo::ref::rp;
using xo::time::utc_nanos;
using xo::time::timeutil;
using xo::json::PrintJsonSingleton;
using xo::json::PrintJson;
using xo::Subsystem;
using xo::scope;
using xo::xtag;
try {
#ifdef NOT_USING
InitSubsys<S_option_tag>::require();
InitSubsys<S_process_tag>::require();
InitSubsys<S_reactor_tag>::require();
#endif
XO_SUBSYSTEM_REQUIRE(volfit);
XO_SUBSYSTEM_REQUIRE(filter);
XO_SUBSYSTEM_REQUIRE(simulator);
XO_SUBSYSTEM_REQUIRE(process);
Subsystem::initialize_all();
signal(SIGINT, sigint_handler);
scope log(XO_ENTER0(always));
rp<PrintJson> pjson = PrintJsonSingleton::instance();
/* RC Sep 2022 - adding c++ translation of kalman/src/pywebsock/ex_websock.py;
* intending to debug server segfault without complication of running
* from python interpreter
*/
Secid secid0(0, 0);
Secid ul0 = Secid::ul(0);
utc_nanos t0 = timeutil::ymd_hms_usec(20220926 /*ymd*/,
93000 /*hms*/,
0 /*usec*/);
utc_nanos t1 = t0 + std::chrono::hours(30 * 24);
/* sim = pysimulator.Simulator.make() */
rp<Simulator> sim = Simulator::make(t0);
/* ss = pyoption.make_option_strike_set(3, secid0, 10, 1, t1, Pxtick.penny_nickel) */
rp<OptionStrikeSet> ss
= OptionStrikeSet::regular(3 /*n*/,
secid0 /*start_id*/,
10.0 /*lo_strike*/,
1.0 /*d_strike*/,
t1 /*expiry*/,
Pxtick::penny_nickel);
/* cx = pyoption.make_pricing_context(t0, 11.11, .5, .06) */
rp<PricingContext> cx
= PricingContext::make(t0, 11.11 /*ref_spot*/,
FlatVolsfc::make(0.5) /*volatility*/,
0.06 /*rate*/);
/* TODO: replace with constant to get deterministic behavior */
Seed<xoshiro256ss> seed;
/* ebm = pyprocess.make_exponential_brownian_motion(t0, 11.0, 0.5) */
rp<ExpProcess> ebm
= ExpProcess::make(11.0,
BrownianMotion<xoshiro256ss>::make(t0,
0.5 /*volatility*/,
seed));
/* src = pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) */
rp<ReactorSource> src
= RealizationSource<UpxEvent, double>::make(RealizationTracer<double>::make(ebm),
std::chrono::seconds(1));
src->set_name("src");
/* (A)
* ulm = pyoption.make_ul_market_model(ul0, cx)
*/
rp<UlMarketModel> ulm = UlMarketModel::make(ul0, cx);
ulm->set_name("ulm");
/* (B)
* ssm = pyoption.make_strikeset_market_model(ss, cx)
*/
rp<StrikeSetMarketModel> ssm = StrikeSetMarketModel::make(ss, cx);
ssm->set_name("ssm");
/* (C)
* ssmd = pyoption.make_strikeset_omd(ss)
*/
rp<StrikeSetOmd> ssmd = StrikeSetOmd::make(ss);
ssmd->set_name("ssmd");
/* (D)
* bbos = pyoption.BboTickStore.make()
*/
using BboTickStore = StructEventStore<BboTick>;
rp<BboTickStore> bbos = StructEventStore<BboTick>::make();
bbos->set_name("bbos");
/* (E)
* vfin = pyvolfit.make_volfit_input_capture(cx)
*/
rp<VolfitInputCaptureSvc> vfin = VolfitInputCaptureSvc::make(cx);
vfin->set_name("vfin");
/* (F)
* kfspec = pyvolfit.make_kf_spec_m1(t0, s0=0.3, p0=1.0, q=5)
*/
KalmanFilterSpec kfspec = Volfit::kf_spec_m1(t0, 0.3, 1.0, 5.0);
/* kf = pyfilter.make_kalman_filter(spec=kfspec) */
rp<KalmanFilterSvc> kf = KalmanFilterSvc::make(kfspec);
kf->set_debug_sim_flag(true);
kf->set_name("kf");
using KalmanFilterStateEventStore
= PtrEventStore<rp<KalmanFilterStateExt>>;
/* (G)
* kfs = pyfilter.KalmanFilterStateEventStore.make()
*/
rp<KalmanFilterStateEventStore> kfs
= KalmanFilterStateEventStore::make();
/* sim.add_source(src) */
sim->add_source(src);
/* uls = pyprocess.UpxEventStore.make() */
using UpxEventStore = StructEventStore<UpxEvent>;
rp<UpxEventStore> uls = UpxEventStore::make();
src->attach_sink(uls);
/* (A) */
sim->add_source(ulm);
src->attach_sink(ulm);
/* (B) */
sim->add_source(ssm);
src->attach_sink(ssm);
/* (C) */
sim->add_source(ssmd);
ulm->attach_sink(ssmd);
ssm->attach_sink(ssmd);
/* (D) */
ulm->attach_sink(bbos);
ssm->attach_sink(bbos);
/* (E) */
// sim->add_source(vfin) ?
ssmd->attach_sink(vfin);
/* (F) */
// sim->add_source(kf) ?
vfin->attach_sink(kf);
/* (G) */
kf->attach_sink(kfs);
/* wconfig=pywebsock.WebserverConfig(7682, False, False, False)
* web=pywebsock.Webserver.make(wconfig)
*/
g_ws = Webserver::make(WebserverConfig(7682 /*port*/,
false /*!tls_flag*/,
false /*!strict_host_check_flag*/,
false /*!use_retry_flag*/),
PrintJsonSingleton::instance());
/* web.register_http_endpoint(uls.http_endpoint_descr("/uls")) # /dyn/uls/snap
* web.register_http_endpoint(kfs.http_endpoint_descr("/kfs")) # /dyn/kfs/snap
*/
g_ws->register_http_endpoint(uls->http_endpoint_descr(pjson, "/uls"));
g_ws->register_http_endpoint(kfs->http_endpoint_descr(pjson, "/kfs"));
/* web.register_stream_endpoint(src.stream_endpoint_descr("/ws/uls"))
* web.register_stream_endpoint(kf.stream_endpoint_descr("/ws/kfs"))
*/
g_ws->register_stream_endpoint(src->stream_endpoint_descr("/ws/uls"));
g_ws->register_stream_endpoint(kf->stream_endpoint_descr("/ws/kfs"));
log("starting webserver..");
g_ws->start_webserver();
log("..webserver started");
/* attempt simulation.
* throttle so that we have time to connect browser, give url
* http://localhost:7682/ex_websock.html
* pywebsock/ex_websock.py implements a governed simulation loop
* in python, so that it's interruptible.
* here, we can rely on simulator instead
*/
sim->run_throttled_until(t0 /*t1 - ignored if <= sim.t0()*/,
50 /*n_max*/,
2.5 /*replay_factor*/);
log("joining webserver..");
g_ws->join_webserver();
log("..joined webserver");
} catch (std::exception & ex) {
std::cerr << "caught exception" << xtag("ex", ex.what()) << std::endl;
}
} /*main*/
/* end websock_utest_main.cpp */