/* 88888888 888888888888 88888888888888 8888888888888888 888888888888888888 888888 8888 888888 88888 88 88888 888888 8888 888888 88888888888888888888 88888888888888888888 8888888888888888888888 8888888888888888888888888888 88888888888888888888888888888888 88888888888888888888 888888888888888888888888 888888 8888888888 888888 888 8888 8888 888 888 888 OCTOBANANA Belle 0.6.0-develop-stripped An HTTP / Websocket library in C++17 using Boost.Beast and Boost.ASIO. https://octobanana.com/software/belle Licensed under the MIT License Copyright (c) 2018-2019 Brett Robinson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef OB_BELLE_HH #define OB_BELLE_HH #define OB_BELLE_VERSION_MAJOR 0 #define OB_BELLE_VERSION_MINOR 6 #define OB_BELLE_VERSION_PATCH 0 // Config Begin // compile with -DOB_BELLE_CONFIG_ or // comment out defines to alter the library // zlib support #ifndef OB_BELLE_CONFIG_ZLIB_OFF #define OB_BELLE_CONFIG_ZLIB_ON #endif // OB_BELLE_CONFIG_ZLIB_OFF // signal support #ifndef OB_BELLE_CONFIG_SIGNAL_OFF #define OB_BELLE_CONFIG_SIGNAL_ON #endif // OB_BELLE_CONFIG_SIGNAL_OFF // ssl support #ifndef OB_BELLE_CONFIG_SSL_OFF #define OB_BELLE_CONFIG_SSL_ON #endif // OB_BELLE_CONFIG_SSL_OFF // client support #ifndef OB_BELLE_CONFIG_CLIENT_OFF #define OB_BELLE_CONFIG_CLIENT_ON #endif // OB_BELLE_CONFIG_CLIENT_OFF // Config End #include #include #include #include #include #include #include #ifdef OB_BELLE_CONFIG_ZLIB_ON #include #include #include #include #include #include #endif // OB_BELLE_CONFIG_ZLIB_ON #ifdef OB_BELLE_CONFIG_CLIENT_ON #include #include #include #include #include #include #endif // OB_BELLE_CONFIG_CLIENT_ON #ifdef OB_BELLE_CONFIG_SSL_ON #include #include #endif // OB_BELLE_CONFIG_SSL_ON #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace OB::Belle { // aliases namespace fs = std::filesystem; namespace net = boost::asio; namespace beast = boost::beast; namespace http = boost::beast::http; #ifdef OB_BELLE_CONFIG_SSL_ON namespace ssl = boost::asio::ssl; #endif // OB_BELLE_CONFIG_SSL_ON using io = boost::asio::io_context; using tcp = boost::asio::ip::tcp; using error_code = boost::system::error_code; using Method = boost::beast::http::verb; using Status = boost::beast::http::status; using Header = boost::beast::http::field; using Headers = boost::beast::http::fields; namespace Detail { // prototypes inline std::string lowercase(std::string str); inline std::vector split(std::string const& str, std::string const& delim, std::size_t size = std::numeric_limits::max()); inline std::string hex_encode(char const c); inline char hex_decode(std::string const& s); inline std::string base64_encode(std::string const& val); inline std::string base64_decode(std::string const& val); // string to lowercase inline std::string lowercase(std::string str) { auto const to_lower = [](char& c) { if (c >= 'A' && c <= 'Z') { c += 'a' - 'A'; } return c; }; for (char& c : str) { c = to_lower(c); } return str; } // split a string by a delimiter 'n' times inline std::vector split(std::string const& str, std::string const& delim, std::size_t size) { std::vector vtok; std::size_t start {0}; auto end = str.find(delim); while ((size-- > 0) && (end != std::string::npos)) { vtok.emplace_back(str.substr(start, end - start)); start = end + delim.length(); end = str.find(delim, start); } vtok.emplace_back(str.substr(start, end)); return vtok; } // convert object into a string template inline std::string to_string(T const& t) { std::stringstream ss; ss << t; return ss.str(); } inline std::string hex_encode(char const c) { char s[3]; if (c & 0x80) { std::snprintf(&s[0], 3, "%02X", static_cast(c & 0xff) ); } else { std::snprintf(&s[0], 3, "%02X", static_cast(c) ); } return std::string(s); } inline char hex_decode(std::string const& s) { unsigned int n; std::sscanf(s.data(), "%x", &n); return static_cast(n); } inline std::string base64_encode(std::string const& val) { using it = boost::archive::iterators::base64_from_binary>; return std::string(it(val.begin()), it(val.end())).append((3 - val.size() % 3) % 3, '='); } inline std::string base64_decode(std::string const& val) { using it = boost::archive::iterators::transform_width, 8, 6>; return boost::algorithm::trim_right_copy_if(std::string(it(val.begin()), it(val.end())), [](char const c) { return c == '\0'; }); } #ifdef OB_BELLE_CONFIG_SSL_ON // TODO switch to boost::beast::ssl_stream when it moves out of experimental template class ssl_stream final : public ssl::stream_base { // This class (ssl_stream) is a derivative work based on Boost.Beast, // orignal copyright below: /* Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using stream_type = ssl::stream; public: using native_handle_type = typename stream_type::native_handle_type; using impl_struct = typename stream_type::impl_struct; using next_layer_type = typename stream_type::next_layer_type; using lowest_layer_type = typename stream_type::lowest_layer_type; using executor_type = typename stream_type::executor_type; ssl_stream(Next_Layer&& arg, ssl::context& ctx) : _ptr {std::make_unique(std::move(arg), ctx)} { } executor_type get_executor() noexcept { return _ptr->get_executor(); } native_handle_type native_handle() { return _ptr->native_handle(); } next_layer_type const& next_layer() const { return _ptr->next_layer(); } next_layer_type& next_layer() { return _ptr->next_layer(); } lowest_layer_type& lowest_layer() { return _ptr->lowest_layer(); } lowest_layer_type const& lowest_layer() const { return _ptr->lowest_layer(); } void set_verify_mode(ssl::verify_mode v) { _ptr->set_verify_mode(v); } void set_verify_mode(ssl::verify_mode v, error_code& ec) { _ptr->set_verify_mode(v, ec); } void set_verify_depth(int depth) { _ptr->set_verify_depth(depth); } void set_verify_depth(int depth, error_code& ec) { _ptr->set_verify_depth(depth, ec); } template void set_verify_callback(VerifyCallback callback) { _ptr->set_verify_callback(callback); } template void set_verify_callback(VerifyCallback callback, error_code& ec) { _ptr->set_verify_callback(callback, ec); } void handshake(handshake_type type) { _ptr->handshake(type); } void handshake(handshake_type type, error_code& ec) { _ptr->handshake(type, ec); } template void handshake(handshake_type type, ConstBufferSequence const& buffers) { _ptr->handshake(type, buffers); } template void handshake(handshake_type type, ConstBufferSequence const& buffers, error_code& ec) { _ptr->handshake(type, buffers, ec); } template BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void(error_code)) async_handshake(handshake_type type, BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler) { return _ptr->async_handshake(type, BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler)); } template BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void (error_code, std::size_t)) async_handshake(handshake_type type, ConstBufferSequence const& buffers, BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler) { return _ptr->async_handshake(type, buffers, BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler)(handler)); } void shutdown() { _ptr->shutdown(); } void shutdown(error_code& ec) { _ptr->shutdown(ec); } template BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler, void (error_code)) async_shutdown(BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler) { return _ptr->async_shutdown(BOOST_ASIO_MOVE_CAST(ShutdownHandler)(handler)); } template std::size_t write_some(ConstBufferSequence const& buffers) { return _ptr->write_some(buffers); } template std::size_t write_some(ConstBufferSequence const& buffers, error_code& ec) { return _ptr->write_some(buffers, ec); } template BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void (error_code, std::size_t)) async_write_some(ConstBufferSequence const& buffers, BOOST_ASIO_MOVE_ARG(WriteHandler) handler) { return _ptr->async_write_some(buffers, BOOST_ASIO_MOVE_CAST(WriteHandler)(handler)); } template std::size_t read_some(MutableBufferSequence const& buffers) { return _ptr->read_some(buffers); } template std::size_t read_some(MutableBufferSequence const& buffers, error_code& ec) { return _ptr->read_some(buffers, ec); } template BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void(error_code, std::size_t)) async_read_some(MutableBufferSequence const& buffers, BOOST_ASIO_MOVE_ARG(ReadHandler) handler) { return _ptr->async_read_some(buffers, BOOST_ASIO_MOVE_CAST(ReadHandler)(handler)); } private: std::unique_ptr _ptr; }; // class ssl_stream #endif // OB_BELLE_CONFIG_SSL_ON } // namespace Detail namespace Util { // Ordered_Map: an insert ordered map // enables random lookup and insert ordered iterators // unordered map stores key value pairs // queue holds insert ordered iterators to each key in the unordered map template class Ordered_Map final { public: // map iterators using m_iterator = typename std::unordered_map::iterator; using m_const_iterator = typename std::unordered_map::const_iterator; // index iterators using i_iterator = typename std::deque::iterator; using i_const_iterator = typename std::deque::const_iterator; Ordered_Map() { } Ordered_Map(std::initializer_list> const& lst) { for (auto const& [key, val] : lst) { _it.emplace_back(_map.insert({key, val}).first); } } ~Ordered_Map() { } Ordered_Map& operator()(K const& k, V const& v) { auto it = _map.insert_or_assign(k, v); if (it.second) { _it.emplace_back(it.first); } return *this; } i_iterator operator[](std::size_t index) { return _it[index]; } i_const_iterator const operator[](std::size_t index) const { return _it[index]; } V& at(K const& k) { return _map.at(k); } V const& at(K const& k) const { return _map.at(k); } m_iterator find(K const& k) { return _map.find(k); } m_const_iterator find(K const& k) const { return _map.find(k); } std::size_t size() const { return _it.size(); } bool empty() const { return _it.empty(); } Ordered_Map& clear() { _it.clear(); _map.clear(); return *this; } Ordered_Map& erase(K const& k) { auto it = _map.find(k); if (it != _map.end()) { for (auto e = _it.begin(); e < _it.end(); ++e) { if ((*e) == it) { _it.erase(e); break; } } _map.erase(it); } return *this; } i_iterator begin() { return _it.begin(); } i_const_iterator begin() const { return _it.begin(); } i_const_iterator cbegin() const { return _it.cbegin(); } i_iterator end() { return _it.end(); } i_const_iterator end() const { return _it.end(); } i_const_iterator cend() const { return _it.cend(); } m_iterator map_begin() { return _map.begin(); } m_const_iterator map_begin() const { return _map.begin(); } m_const_iterator map_cbegin() const { return _map.cbegin(); } m_iterator map_end() { return _map.end(); } m_const_iterator map_end() const { return _map.end(); } m_const_iterator map_cend() const { return _map.cend(); } private: std::unordered_map _map; std::deque _it; }; // class Ordered_Map std::unordered_map const mime_types { {"html", "text/html"}, {"htm", "text/html"}, {"shtml", "text/html"}, {"css", "text/css"}, {"xml", "text/xml"}, {"gif", "image/gif"}, {"jpg", "image/jpg"}, {"jpeg", "image/jpg"}, {"js", "application/javascript"}, {"atom", "application/atom+xml"}, {"rss", "application/rss+xml"}, {"mml", "text/mathml"}, {"txt", "text/plain"}, {"jad", "text/vnd.sun.j2me.app-descriptor"}, {"wml", "text/vnd.wap.wml"}, {"htc", "text/x-component"}, {"png", "image/png"}, {"tif", "image/tiff"}, {"tiff", "image/tiff"}, {"wbmp", "image/vnd.wap.wbmp"}, {"ico", "image/x-icon"}, {"jng", "image/x-jng"}, {"bmp", "image/x-ms-bmp"}, {"svg", "image/svg+xml"}, {"svgz", "image/svg+xml"}, {"webp", "image/webp"}, {"woff", "application/font-woff"}, {"jar", "application/java-archive"}, {"war", "application/java-archive"}, {"ear", "application/java-archive"}, {"json", "application/json"}, {"hqx", "application/mac-binhex40"}, {"doc", "application/msword"}, {"pdf", "application/pdf"}, {"ps", "application/postscript"}, {"eps", "application/postscript"}, {"ai", "application/postscript"}, {"rtf", "application/rtf"}, {"m3u8", "application/vnd.apple.mpegurl"}, {"xls", "application/vnd.ms-excel"}, {"eot", "application/vnd.ms-fontobject"}, {"ppt", "application/vnd.ms-powerpoint"}, {"wmlc", "application/vnd.wap.wmlc"}, {"kml", "application/vnd.google-earth.kml+xml"}, {"kmz", "application/vnd.google-earth.kmz"}, {"7z", "application/x-7z-compressed"}, {"cco", "application/x-cocoa"}, {"jardiff", "application/x-java-archive-diff"}, {"jnlp", "application/x-java-jnlp-file"}, {"run", "application/x-makeself"}, {"pm", "application/x-perl"}, {"pl", "application/x-perl"}, {"pdb", "application/x-pilot"}, {"prc", "application/x-pilot"}, {"rar", "application/x-rar-compressed"}, {"rpm", "application/x-redhat-package-manager"}, {"sea", "application/x-sea"}, {"swf", "application/x-shockwave-flash"}, {"sit", "application/x-stuffit"}, {"tk", "application/x-tcl"}, {"tcl", "application/x-tcl"}, {"crt", "application/x-x509-ca-cert"}, {"pem", "application/x-x509-ca-cert"}, {"der", "application/x-x509-ca-cert"}, {"xpi", "application/x-xpinstall"}, {"xhtml", "application/xhtml+xml"}, {"xspf", "application/xspf+xml"}, {"zip", "application/zip"}, {"dll", "application/octet-stream"}, {"exe", "application/octet-stream"}, {"bin", "application/octet-stream"}, {"deb", "application/octet-stream"}, {"dmg", "application/octet-stream"}, {"img", "application/octet-stream"}, {"iso", "application/octet-stream"}, {"msm", "application/octet-stream"}, {"msp", "application/octet-stream"}, {"msi", "application/octet-stream"}, {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, {"kar", "audio/midi"}, {"midi", "audio/midi"}, {"mid", "audio/midi"}, {"mp3", "audio/mpeg"}, {"ogg", "audio/ogg"}, {"m4a", "audio/x-m4a"}, {"ra", "audio/x-realaudio"}, {"3gp", "video/3gpp"}, {"3gpp", "video/3gpp"}, {"ts", "video/mp2t"}, {"mp4", "video/mp4"}, {"mpg", "video/mpeg"}, {"mpeg", "video/mpeg"}, {"mov", "video/quicktime"}, {"webm", "video/webm"}, {"flv", "video/x-flv"}, {"m4v", "video/x-m4v"}, {"mng", "video/x-mng"}, {"asf", "video/x-ms-asf"}, {"asx", "video/x-ms-asf"}, {"wmv", "video/x-ms-wmv"}, {"avi", "video/x-msvideo"}, }; // prototypes inline std::string mime_type(fs::path const& path); inline std::string url_encode(std::string const& str); inline std::string url_decode(std::string const& str); #ifdef OB_BELLE_CONFIG_ZLIB_ON inline std::string zlib_encode(std::string const& val); inline std::string zlib_decode(std::string const& val); inline std::string gzip_encode(std::string const& val); inline std::string gzip_decode(std::string const& val); #endif // OB_BELLE_CONFIG_ZLIB_ON // find the mime type of a string path inline std::string mime_type(fs::path const& path) { if (std::string ext = path.extension(); ext.size()) { auto const str = Detail::lowercase(ext); if (mime_types.find(str) != mime_types.end()) { return mime_types.at(str); } } return "application/octet-stream"; } inline std::string url_encode(std::string const& str) { std::string res; res.reserve(str.size()); for (auto const& e : str) { if (e == ' ') { res += "+"; } else if (std::isalnum(static_cast(e)) || e == '-' || e == '_' || e == '.' || e == '~') { res += e; } else { res += "%" + Detail::hex_encode(e); } } return res; } inline std::string url_decode(std::string const& str) { std::string res; res.reserve(str.size()); for (std::size_t i = 0; i < str.size(); ++i) { if (str[i] == '+') { res += " "; } else if (str[i] == '%' && i + 2 < str.size() && std::isxdigit(static_cast(str[i + 1])) && std::isxdigit(static_cast(str[i + 2]))) { res += Detail::hex_decode(str.substr(i + 1, 2)); i += 2; } else { res += str[i]; } } return res; } #ifdef OB_BELLE_CONFIG_ZLIB_ON inline std::string zlib_encode(std::string const& str) { boost::iostreams::array_source src {str.data(), str.size()}; boost::iostreams::filtering_istream is; is.push(boost::iostreams::zlib_compressor(MAX_WBITS)); is.push(src); std::stringstream body; boost::iostreams::copy(is, body); return body.str(); } inline std::string zlib_decode(std::string const& str) { boost::iostreams::array_source src {str.data(), str.size()}; boost::iostreams::filtering_istream is; is.push(boost::iostreams::zlib_decompressor(MAX_WBITS)); is.push(src); std::stringstream body; boost::iostreams::copy(is, body); return body.str(); } inline std::string gzip_encode(std::string const& str) { boost::iostreams::array_source src {str.data(), str.size()}; boost::iostreams::filtering_istream is; is.push(boost::iostreams::gzip_compressor()); is.push(src); std::stringstream body; boost::iostreams::copy(is, body); return body.str(); } inline std::string gzip_decode(std::string const& str) { boost::iostreams::array_source src {str.data(), str.size()}; boost::iostreams::filtering_istream is; is.push(boost::iostreams::gzip_decompressor()); is.push(src); std::stringstream body; boost::iostreams::copy(is, body); return body.str(); } #endif // OB_BELLE_CONFIG_ZLIB_ON } // namespace Util class Request final : public http::request { using Base = http::request; public: using Path = std::vector; using Params = std::unordered_multimap; // inherit base constructors using http::request::message; // default constructor Request() = default; // copy constructor Request(Request const&) = default; // move constructor Request(Request&&) = default; // copy assignment Request& operator=(Request const&) = default; // move assignment Request& operator=(Request&&) = default; // default deconstructor ~Request() = default; Request&& move() noexcept { return std::move(*this); } // get the path Path& path() { return _path; } // get the query parameters Params& params() { return _params; } // serialize path and query parameters to the target void params_serialize() { std::string path {target().to_string()}; _path.clear(); _path.emplace_back(path); if (! _params.empty()) { path += "?"; auto it = _params.begin(); for (; it != _params.end(); ++it) { path += Util::url_encode(it->first) + "=" + Util::url_encode(it->second) + "&"; } path.pop_back(); } target(path); } // parse the query parameters from the target void params_parse() { std::string path {target().to_string()}; // separate the query params auto params = Detail::split(path, "?", 1); // set params if (params.size() == 2) { auto kv = Detail::split(params.at(1), "&"); for (auto const& e : kv) { if (e.empty()) { continue; } auto k_v = Detail::split(e, "=", 1); if (k_v.size() == 1) { _params.emplace(Util::url_decode(e), ""); } else if (k_v.size() == 2) { _params.emplace(Util::url_decode(k_v.at(0)), Util::url_decode(k_v.at(1))); } continue; } } } private: Path _path {}; Params _params {}; }; // Request #ifdef OB_BELLE_CONFIG_CLIENT_ON namespace Client { class Http final { public: struct Status { enum { closed = 0, closing, resolving, connecting, handshaking, error, open, reading, writing, }; }; struct Session_Ctx { // http request Request req {}; // http response http::response res {}; }; // struct Session_Ctx struct Error_Ctx { // error code error_code const& ec; }; // struct Error_Ctx // callbacks using fn_on_open = std::function; using fn_on_read = std::function; using fn_on_write = std::function; using fn_on_close = std::function; using fn_on_error = std::function; struct Attr { #ifdef OB_BELLE_CONFIG_SSL_ON // use ssl bool ssl {false}; // ssl context ssl::context ssl_context {ssl::context::tlsv12_client}; #endif // OB_BELLE_CONFIG_SSL_ON // socket status int status {Status::closed}; // socket timeout std::chrono::seconds timeout {10}; // address to connect to std::string address {"127.0.0.1"}; // port to connect to unsigned short port {8080}; // callbacks fn_on_open on_open {}; fn_on_read on_read {}; fn_on_write on_write {}; fn_on_close on_close {}; fn_on_error on_error {}; }; // struct Attr struct Session_Type { virtual ~Session_Type() = default; virtual void read() = 0; virtual void write(Request&&) = 0; virtual void close() = 0; virtual void error(error_code const&) = 0; }; // struct Session_Type template class Session_Base : public Session_Type { Derived& derived() { return static_cast(*this); } public: Session_Base(net::io_context& io_, std::shared_ptr const attr_) : _resolver {io_}, _strand {io_.get_executor()}, _timer {io_, (std::chrono::steady_clock::time_point::max)()}, _attr {attr_} { } ~Session_Base() { _attr->status = Status::closed; } void cancel_timer() { // set the timer to expire immediately _timer.expires_at((std::chrono::steady_clock::time_point::min)()); } void do_timer() { _timer.cancel(); _timer.expires_after(_attr->timeout); // wait on the timer _timer.async_wait( net::bind_executor(_strand, [self = derived().shared_from_this()](error_code ec) { self->on_timer(ec); } ) ); } void on_timer(error_code ec_) { if (ec_ && ec_ != net::error::operation_aborted) { on_error(ec_); return; } // check if socket has been closed if (_timer.expires_at() == (std::chrono::steady_clock::time_point::min)()) { return; } // check expiry if (_timer.expiry() <= std::chrono::steady_clock::now()) { derived().do_close(); return; } if (_close) { return; } } void do_resolve() { _attr->status = Status::resolving; // domain name server lookup _resolver.async_resolve(_attr->address, Detail::to_string(_attr->port), net::bind_executor(_strand, [self = derived().shared_from_this()] (error_code ec, tcp::resolver::results_type results) { self->on_resolve(ec, results); } ) ); } void on_resolve(error_code ec_, tcp::resolver::results_type results_) { if (ec_) { on_error(ec_); return; } _attr->status = Status::connecting; // connect to the endpoint net::async_connect(derived().socket().lowest_layer(), results_.begin(), results_.end(), net::bind_executor(_strand, [self = derived().shared_from_this()](error_code ec, auto) { self->derived().on_connect(ec); } ) ); } void prepare_req() { // serialize target and params _ctx.req.params_serialize(); // set default user-agent header value if not present if (_ctx.req.find(Header::user_agent) == _ctx.req.end()) { _ctx.req.set(Header::user_agent, "Belle"); } // set default host header value if not present if (_ctx.req.find(Header::host) == _ctx.req.end()) { _ctx.req.set(Header::host, _attr->address); } #ifdef OB_BELLE_CONFIG_ZLIB_ON // set default accept-encoding header value if not present if (_ctx.req.find(Header::accept_encoding) == _ctx.req.end()) { _ctx.req.set(Header::accept_encoding, "gzip, deflate"); } #endif // OB_BELLE_CONFIG_ZLIB_ON // prepare the payload _ctx.req.prepare_payload(); } void do_write() { prepare_req(); do_timer(); _attr->status = Status::writing; // Send the HTTP request http::async_write(derived().socket(), _ctx.req, net::bind_executor(_strand, [self = derived().shared_from_this()](error_code ec, std::size_t bytes) { self->on_write(ec, bytes); } ) ); } void on_write(error_code ec_, std::size_t bytes_) { boost::ignore_unused(bytes_); _attr->status = Status::open; if (ec_) { on_error(ec_); return; } if (_attr->on_write) { try { // run user function _attr->on_write(_ctx); } catch (...) { on_error(); } } } void do_read() { // clear the HTTP response _ctx.res = {}; do_timer(); _attr->status = Status::reading; // Receive the HTTP response http::async_read(derived().socket(), _buf, _ctx.res, net::bind_executor(_strand, [self = derived().shared_from_this()](error_code ec, std::size_t bytes) { self->on_read(ec, bytes); } ) ); } void on_read(error_code ec_, std::size_t bytes_) { boost::ignore_unused(bytes_); _attr->status = Status::open; if (ec_) { on_error(ec_); return; } if (_attr->on_read) { try { #ifdef OB_BELLE_CONFIG_ZLIB_ON if (_ctx.res.body().size() && _ctx.res["content-encoding"] != "" && _ctx.res["content-encoding"] != "identity") { if (_ctx.res["content-encoding"] == "gzip") { _ctx.res.body() = OB::Belle::Util::gzip_decode(_ctx.res.body()); } else if (_ctx.res["content-encoding"] == "deflate") { _ctx.res.body() = OB::Belle::Util::zlib_decode(_ctx.res.body()); } } #endif // OB_BELLE_CONFIG_ZLIB_ON // run user function _attr->on_read(_ctx); } catch (...) { on_error(); } } } void on_error(error_code const& ec_ = {}) { _attr->status = Status::error; if (_attr->on_error) { try { // run user function Error_Ctx err {ec_}; _attr->on_error(err); } catch (...) { } } } void read() { do_read(); } void write(Request&& req_) { _ctx.req = std::move(req_); do_write(); } void close() { derived().do_close(); } void error(error_code const& ec_) { on_error(ec_); } tcp::resolver _resolver; net::strand _strand; net::steady_timer _timer; std::shared_ptr const _attr; Session_Ctx _ctx {}; beast::flat_buffer _buf {}; bool _close {false}; }; // class Session_Base class Session final : public Session_Base, public std::enable_shared_from_this { public: Session(net::io_context& io_, std::shared_ptr attr_) : Session_Base(io_, attr_), _socket {io_} { } ~Session() { } tcp::socket& socket() { return _socket; } tcp::socket&& socket_move() { return std::move(_socket); } void run() { do_timer(); do_resolve(); } void on_connect(error_code ec_) { if (ec_) { on_error(ec_); return; } _attr->status = Status::open; if (_attr->on_open) { try { // run user function _attr->on_open(_ctx); } catch (...) { on_error(); } } } void do_close() { _attr->status = Status::closing; cancel_timer(); error_code ec; // shutdown the socket _socket.shutdown(tcp::socket::shutdown_both, ec); _socket.close(ec); _attr->status = Status::closed; // ignore not_connected error if (ec && ec != boost::system::errc::not_connected) { on_error(ec); return; } if (_attr->on_close) { try { // run user function _attr->on_close(_ctx); } catch (...) { on_error(); } } // the connection is now closed } private: tcp::socket _socket; }; // class Session #ifdef OB_BELLE_CONFIG_SSL_ON class Session_Secure final : public Session_Base, public std::enable_shared_from_this { public: Session_Secure(net::io_context& io_, std::shared_ptr attr_) : Session_Base(io_, attr_), _socket {tcp::socket(io_), attr_->ssl_context} { _close = true; } ~Session_Secure() { } Detail::ssl_stream& socket() { return _socket; } Detail::ssl_stream&& socket_move() { return std::move(_socket); } void run() { // start the timer do_timer(); // set server name indication // use SSL_ctrl instead of SSL_set_tlsext_host_name macro // to avoid old style C cast to char* // if (! SSL_set_tlsext_host_name(_socket.native_handle(), _attr->address.data())) if (! SSL_ctrl(_socket.native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, _attr->address.data())) { error_code ec { static_cast(ERR_get_error()), net::error::get_ssl_category() }; on_error(ec); return; } do_resolve(); } void on_connect(error_code ec_) { if (ec_) { on_error(ec_); return; } do_handshake(); } void do_handshake() { _attr->status = Status::handshaking; // perform the ssl handshake _socket.async_handshake(ssl::stream_base::client, net::bind_executor(_strand, [self = shared_from_this()](error_code ec) { self->on_handshake(ec); } ) ); } void on_handshake(error_code ec_) { if (ec_) { on_error(ec_); return; } _close = false; _attr->status = Status::open; if (_attr->on_open) { try { // run user function _attr->on_open(_ctx); } catch (...) { on_error(); } } } void do_close() { if (_close) { return; } _attr->status = Status::closing; _close = true; // shutdown the socket _socket.async_shutdown( net::bind_executor(_strand, [self = shared_from_this()](error_code ec) { self->on_shutdown(ec); } ) ); } void on_shutdown(error_code ec_) { cancel_timer(); // ignore errors ec_.assign(0, ec_.category()); // close the socket _socket.next_layer().close(ec_); _attr->status = Status::closed; if (_attr->on_close) { try { // run user function _attr->on_close(_ctx); } catch (...) { on_error(); } } // the connection is now closed } private: Detail::ssl_stream _socket; }; // class Session_Secure #endif // OB_BELLE_CONFIG_SSL_ON // default constructor Http(net::io_context& io_) : _io {io_} { } Http(net::io_context& io_, std::string const& url_) : _io {io_} { url(url_); } // constructor with address and port Http(net::io_context& io_, std::string address_, unsigned short port_) : _io {io_} { _attr->address = address_; _attr->port = port_; } #ifdef OB_BELLE_CONFIG_SSL_ON // constructor with address, port, and ssl Http(net::io_context& io_, std::string address_, unsigned short port_, bool ssl_) : _io {io_} { _attr->address = address_; _attr->port = port_; _attr->ssl = ssl_; } #endif // OB_BELLE_CONFIG_SSL_ON // destructor ~Http() { } Http& url(std::string const& url_) { std::smatch rx_match; auto const rx_opts {std::regex::ECMAScript | std::regex::icase}; std::regex const rx_str {"^(http|https)://(.+?)(?::(\\d*)|)$", rx_opts}; auto const rx_flgs {std::regex_constants::match_not_null}; if (std::regex_match(url_, rx_match, rx_str, rx_flgs)) { _attr->address = rx_match[2].str(); _attr->port = static_cast(rx_match[3].str().size() ? std::stoi(rx_match[3].str()) : (rx_match[1].str().size() == 5 ? 443 : 80)); #ifdef OB_BELLE_CONFIG_SSL_ON _attr->ssl = rx_match[1].str().size() == 5 ? true : false; #endif // OB_BELLE_CONFIG_SSL_ON } return *this; } // set the address to connect to Http& address(std::string address_) { _attr->address = address_; return *this; } // get the address to connect to std::string address() { return _attr->address; } // set the port to connect to Http& port(unsigned short port_) { _attr->port = port_; return *this; } // get the port to connect to unsigned short port() { return _attr->port; } // set the socket timeout Http& timeout(std::chrono::seconds timeout_) { _attr->timeout = timeout_; return *this; } // get the socket timeout std::chrono::seconds timeout() { return _attr->timeout; } // get the io_context net::io_context& io() { return _io; } #ifdef OB_BELLE_CONFIG_SSL_ON // set ssl Http& ssl(bool ssl_) { _attr->ssl = ssl_; return *this; } // get ssl bool ssl() { return _attr->ssl; } // get the ssl context ssl::context& ssl_context() { return _attr->ssl_context; } // set the ssl context Http& ssl_context(ssl::context&& ctx_) { _attr->ssl_context = std::move(ctx_); return *this; } #endif // OB_BELLE_CONFIG_SSL_ON Http& on_open(fn_on_open on_open_) { _attr->on_open = on_open_; return *this; } Http& on_read(fn_on_read on_read_) { _attr->on_read = on_read_; return *this; } Http& on_write(fn_on_write on_write_) { _attr->on_write = on_write_; return *this; } Http& on_close(fn_on_close on_close_) { _attr->on_close = on_close_; return *this; } Http& on_error(fn_on_error on_error_) { _attr->on_error = on_error_; return *this; } Http& run() { #ifdef OB_BELLE_CONFIG_SSL_ON if (_attr->ssl) { // use secure auto tmp = std::make_shared(_io, _attr); tmp->run(); _session = tmp.get(); } else #endif // OB_BELLE_CONFIG_SSL_ON { // use plain auto tmp = std::make_shared(_io, _attr); tmp->run(); _session = tmp.get(); } return *this; } int status() { return _attr->status; } std::string const& status_string() { return _status_string.at(static_cast(_attr->status)); } bool read() { if (_session) { _session->read(); } return _session; } bool write(Request req_) { if (_session) { _session->write(std::move(req_)); } return _session; } bool close() { if (_session) { _session->close(); } return _session; } bool error(error_code const& ec_ = {}) { if (_session) { _session->error(ec_); } return _session; } private: std::vector const _status_string { "closed", "closing", "resolving", "connecting", "handshaking", "error", "open", "reading", "writing", }; // the io context net::io_context& _io; // hold the client attributes std::shared_ptr const _attr {std::make_shared()}; // the session Session_Type* _session {nullptr}; }; // class Http } // namespace Client #endif // OB_BELLE_CONFIG_CLIENT_ON #ifdef OB_BELLE_CONFIG_SIGNAL_ON class Signal final { public: using fn_on_signal = std::function; Signal(net::io_context& io_) noexcept : _io {io_} { } void wait() { _set.async_wait(_callback); } Signal& on_signal(int const sig_, fn_on_signal const fn_) { _set.add(sig_); _event[sig_] = fn_; return *this; } Signal& on_signal(std::vector const sig_, fn_on_signal const fn_) { for (auto const& e : sig_) { _set.add(e); _event[e] = fn_; } return *this; } static std::string str(int sig_) { std::string res; if (_str.find(sig_) != _str.end()) { res = _str.at(sig_); } return res; } private: std::function const _callback = [&](auto ec_, auto sig_) { if (_event.find(sig_) != _event.end()) { _event.at(sig_)(ec_, sig_); } }; net::io_context& _io; Belle::net::signal_set _set {_io}; std::unordered_map _event; static inline std::unordered_map const _str { {SIGHUP, "SIGHUP"}, {SIGINT, "SIGINT"}, {SIGQUIT, "SIGQUIT"}, {SIGILL, "SIGILL"}, {SIGTRAP, "SIGTRAP"}, {SIGABRT, "SIGABRT"}, {SIGBUS, "SIGBUS"}, {SIGFPE, "SIGFPE"}, {SIGKILL, "SIGKILL"}, {SIGUSR1, "SIGUSR1"}, {SIGSEGV, "SIGSEGV"}, {SIGUSR2, "SIGUSR2"}, {SIGPIPE, "SIGPIPE"}, {SIGALRM, "SIGALRM"}, {SIGTERM, "SIGTERM"}, {SIGSTKFLT, "SIGSTKFLT"}, {SIGCHLD, "SIGCHLD"}, {SIGCONT, "SIGCONT"}, {SIGSTOP, "SIGSTOP"}, {SIGTSTP, "SIGTSTP"}, {SIGTTIN, "SIGTTIN"}, {SIGTTOU, "SIGTTOU"}, {SIGURG, "SIGURG"}, {SIGXCPU, "SIGXCPU"}, {SIGXFSZ, "SIGXFSZ"}, {SIGVTALRM, "SIGVTALRM"}, {SIGPROF, "SIGPROF"}, {SIGWINCH, "SIGWINCH"}, {SIGPOLL, "SIGPOLL"}, {SIGPWR, "SIGPWR"}, {SIGSYS, "SIGSYS"}, }; }; // class Signal #endif // OB_BELLE_CONFIG_SIGNAL_ON } // namespace OB::Belle #endif // OB_BELLE_HH