diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c004aa..b2d46ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,8 @@ set(ADC_NETWORK_HEADERS net/adc_netmsg.h # net/adc_netmessage.h net/adc_netproto.h - net/adc_netservice.h + net/adc_netservice.h + net/adc_url.h ) option(BUILD_TESTS "Build tests" ON) diff --git a/net/adc_url.h b/net/adc_url.h new file mode 100644 index 0000000..ddd1083 --- /dev/null +++ b/net/adc_url.h @@ -0,0 +1,256 @@ +#pragma once + + +#include "../common/adc_traits.h" + +namespace adc +{ + + +/* + * Very simple URL parser and holder class + * + * URL: proto_mark://host_name:port_num/path + */ + +class AdcURL +{ +protected: + static constexpr std::string_view protoHostDelim = "://"; + static constexpr std::string_view hostPortDelim = ":"; + static constexpr std::string_view portPathDelim = "/"; + +public: + enum proto_id_t { PROTO_ID_LOCAL, PROTO_ID_TCP, PROTO_ID_TLS, PROTO_ID_UDP, PROTO_ID_WS, PROTO_ID_WSS }; + + static constexpr std::string_view protoMarkLocal{"local"}; // UNIX domain + static constexpr std::string_view protoMarkTCP{"tcp"}; // TCP + static constexpr std::string_view protoMarkTLS{"tls"}; // TLS + static constexpr std::string_view protoMarkUDP{"udp"}; // UDP + static constexpr std::string_view protoMarkWS{"ws"}; // Websocket + static constexpr std::string_view protoMarkWSS{"wss"}; // Secure Websocket + + static constexpr std::array validProtoMarks = {protoMarkLocal, protoMarkTCP, protoMarkTLS, + protoMarkUDP, protoMarkWS, protoMarkWSS}; + + /* Constructors and destructor */ + + AdcURL() = default; + + // full-feature constructor + template + AdcURL(proto_id_t proto_id, HT&& host, int port, PTT&& path) : AdcURL() + { + setURL(proto_id, std::forward(host), port, std::forward(path)); + } + + + bool valid() const + { + // + return _isValid; + } + + + template + R url() const + { + R r; + if (!_isValid) { + return r; + } + + if (_protoID == PROTO_ID_LOCAL) { // only proto mark, host and its delimiter + std::ranges::copy(_urlView | std::views::take(3) | std::views::join, std::back_inserter(r)); + } else { + std::ranges::copy(_urlView | std::views::join, std::back_inserter(r)); + } + + return r; + } + + std::string url() const + { + // + return url(); + } + + + template + R proto() const + { + if (!_isValid) { + return R(); + } + + const auto& proto = validProtoMarks[_protoID]; + return R{proto.begin(), proto.end()}; + } + + + std::string_view proto() const + { + if (!_isValid) { + return std::string_view(); + } + + return validProtoMarks[_protoID]; + } + + + template + R host() const + { + R r; + if (!_isValid) { + return r; + } + + std::ranges::copy(_host, std::back_inserter(r)); + + return r; + } + + template + R hostView() const + { + if (!_isValid) { + return R(); + } + + return R{_host.begin(), _host.end()}; + } + + std::string host() const + { + // + return _host; + } + + std::string_view hostView() const + { + // + return std::string_view{_host.begin(), _host.end()}; + } + + + int port() const + { + // + return _isValid ? _port : -1; + } + + template + R path() const + { + R r; + if (!_isValid) { + return r; + } + + std::ranges::copy(_path, std::back_inserter(r)); + + return r; + } + + template + R pathView() const + { + if (!_isValid) { + return R(); + } + + return R{_path.begin(), _path.end()}; + } + + std::string path() const + { + // + return _isValid ? _path : ""; + } + + std::string_view pathView() const + { + // + return _isValid ? std::string_view{_path.begin(), _path.end()} : std::string_view(); + } + + template + bool setURL(proto_id_t proto_id, HT&& host, int port, PTT&& path) + { + if (!std::distance(host.begin(), host.end())) { + return false; + } + + switch (proto_id) { + case PROTO_ID_LOCAL: + // ignore path (always "") and port (just skip) + _port = -1; + _path.clear(); + + break; + case PROTO_ID_TCP: + case PROTO_ID_TLS: + case PROTO_ID_UDP: + case PROTO_ID_WS: + case PROTO_ID_WSS: + if (port <= 0) { + return false; + } + + _port = port; + + _path.clear(); + std::ranges::copy(std::forward(path), std::back_inserter(_path)); + + break; + default: + break; + } + + _isValid = true; + + _protoID = proto_id; + + _host.clear(); + std::ranges::copy(std::forward(host), std::back_inserter(_host)); + + updateURLView(); + + return true; + } + + template + bool setURL(R&& r) + { + auto found = std::ranges::search(std::forward(r), protoHostDelim); + + if (found.empty()) { + return false; + } + + + + return true; + } + +protected: + bool _isValid{false}; + proto_id_t _protoID; + std::string _host, _path, _portStr; + int _port; + + std::array _urlView{protoMarkLocal, protoHostDelim, "localhost", hostPortDelim, + "7777", portPathDelim, ""}; + + void updateURLView() + { + _urlView[0] = validProtoMarks[_protoID]; + _urlView[2] = _host.c_str(); + _portStr = std::to_string(_port); + _urlView[4] = _port == -1 ? "" : _portStr.c_str(); + _urlView[6] = _path.c_str(); + } +}; + +} // namespace adc