#pragma once #include #include #include #include #include namespace rublon { namespace { size_t WriteMemoryCallback(void * contents, size_t size, size_t nmemb, void * userp) { const size_t realsize = size * nmemb; reinterpret_cast< std::pmr::string * >(userp)->append(static_cast< const char * >(contents), realsize); return realsize; } } // namespace struct Request { std::pmr::memory_resource * _mr; std::pmr::map< std::pmr::string, std::pmr::string > headers; std::pmr::string body; public: Request(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {}; Request(const Request & res) = delete; Request & operator=(const Request & res) = delete; Request(Request && res) = delete; Request & operator=(Request &&) = delete; }; struct Response { std::pmr::memory_resource * _mr; std::pmr::map< std::pmr::string, std::pmr::string, ci_less > headers; std::pmr::string body; public: Response(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {}; Response(const Response & res) = delete; Response & operator=(const Response & res) = delete; Response(Response && res) noexcept = default; Response & operator=(Response && res) noexcept = default; }; class CURL { std::unique_ptr< ::CURL, void (*)(::CURL *) > curl; std::reference_wrapper< const Configuration > _config; const Configuration & conf() const noexcept { return _config.get(); } public: CURL(const Configuration & config) : curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)}, _config{config} {} CURL(const CURL &) = delete; CURL(CURL &&) = delete; CURL & operator=(const CURL &) = delete; CURL & operator=(CURL &&) = delete; tl::expected< std::reference_wrapper< Response >, ConnectionError > request(std::string_view uri, const Request & request, Response & response) const { using namespace memory::literals; memory::Monotonic_8k_Resource memoryResource; std::pmr::string response_data{&memoryResource}; response_data.reserve(4_kB); auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all); std::for_each(request.headers.begin(), request.headers.end(), [&](auto header) { log(LogLevel::Debug, "%s header: %s: %s", "CURL", header.first.c_str(), header.second.c_str()); curl_headers.reset(curl_slist_append(curl_headers.release(), (header.first + ": " + header.second).c_str())); }); // Optional: Build full proxy URL if proxy is enabled if(conf().proxyEnabled) { // configuration reader check if proxy has needed fields assert(conf().proxyType.has_value()); assert(conf().proxyHost.has_value()); log(LogLevel::Debug, "CURL using proxy"); std::pmr::string proxyUrl{&memoryResource}; proxyUrl.reserve(conservative_estimate(conf().proxyType, conf().proxyHost, conf().proxyPort) + 10); if(conf().proxyType == "http" || conf().proxyType == "https" || conf().proxyType == "socks4" || conf().proxyType == "socks5") { proxyUrl = *conf().proxyType; proxyUrl += "://"; proxyUrl += *conf().proxyHost; if(conf().proxyPort.value_or(0) > 0) { proxyUrl += ":"; proxyUrl += std::to_string(*conf().proxyPort); } log(LogLevel::Debug, "CURL using %s", proxyUrl.c_str()); curl_easy_setopt(curl.get(), CURLOPT_PROXY, proxyUrl.c_str()); if(conf().proxyType == "socks4") { curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); } else if(conf().proxyType == "socks5") { curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); } else { curl_easy_setopt(curl.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP); } if(conf().proxyAuthRequired) { assert(conf().proxyUsername.has_value()); assert(conf().proxyPass.has_value()); std::pmr::string proxyAuth{&memoryResource}; proxyAuth.reserve(conservative_estimate(conf().proxyUsername, conf().proxyPass)); proxyAuth += *conf().proxyUsername; if(conf().proxyPass->size()) { // can proxy have name but no pass? proxyAuth += ":"; proxyAuth += *conf().proxyPass; } curl_easy_setopt(curl.get(), CURLOPT_PROXYUSERPWD, proxyAuth.c_str()); } } } curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 0); curl_easy_setopt(curl.get(), CURLOPT_URL, uri.data()); curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get()); curl_easy_setopt(curl.get(), CURLOPT_POST, 1); curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, request.body.data()); curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, static_cast< uint32_t >(request.body.size())); curl_easy_setopt(curl.get(), CURLOPT_HEADER, 1); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); log(LogLevel::Debug, "Sending request to %s", uri.data()); for(const auto & [name, value] : request.headers) { log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str()); } log(LogLevel::Debug, "Body %s", request.body.c_str()); const auto res = curl_easy_perform(curl.get()); if(res != CURLE_OK) { log(LogLevel::Error, "%s no response from Rublon server err:{%s}", "CURL", curl_easy_strerror(res)); return tl::unexpected{ConnectionError{ConnectionError::Timeout, 0}}; } long http_code = 0; curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code); if(http_code >= 500) { log(LogLevel::Error, "%s response with code %d ", "CURL", http_code); return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}}; } long size{}; curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size); details::headers(response_data, response.headers); response.body = response_data.substr(size); log(LogLevel::Debug, "Received %d bytes", response_data.size()); for(const auto & [name, value] : response.headers) { log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str()); } log(LogLevel::Debug, "Body %s", response.body.c_str()); return response; } }; } // namespace rublon