#include "./request.hpp" #include #include "./curl_wrappers.hpp" namespace mijin { namespace { bool gCurlInited = false; std::mutex gCurlInitMutex; class CURLGuard { public: ~CURLGuard() MIJIN_NOEXCEPT { if (gCurlInited) { curl_global_cleanup(); } } } gCURLGuard [[maybe_unused]]; struct ReadData { mijin::TypelessBuffer* buffer; std::size_t pos; }; bool initCURL() MIJIN_NOEXCEPT { if (gCurlInited) { return true; } const std::unique_lock initLock(gCurlInitMutex); if (gCurlInited) { return true; } gCurlInited = (curl_global_init(0) == CURLE_OK); return gCurlInited; } std::size_t writeCallback(char* ptr, std::size_t /* size */, std::size_t nmemb, void* userdata) noexcept { TypelessBuffer& body = *static_cast(userdata); body.append(std::span(ptr, nmemb)); return nmemb; } std::size_t readCallback(char* ptr, std::size_t size, std::size_t nmemb, void* userdata) noexcept { ReadData& data = *static_cast(userdata); const std::size_t bytesToRead = std::min(size * nmemb, data.buffer->byteSize() - data.pos); std::memcpy(ptr, static_cast(data.buffer->data()) + data.pos, bytesToRead); data.pos += bytesToRead; return bytesToRead; } } Task> c_request(const URL& url, HTTPRequestOptions options) MIJIN_NOEXCEPT { ReadData readData; if (!initCURL()) { co_return StreamError::UNKNOWN_ERROR; } curl::CurlEasy easy; if (!easy.init()) { co_return StreamError::UNKNOWN_ERROR; } curl::SList requestHeaders; for (const auto& [name, value] : options.headers) { requestHeaders.append(std::format("{}: {}", name, value).c_str()); } #define HANDLE_CURL_RESULT(result) \ if (const curl::Error error = (result); !error.isSuccess()) \ { \ co_return StreamError::UNKNOWN_ERROR; \ } HANDLE_CURL_RESULT(easy.setHeaders(std::move(requestHeaders))) if (options.method == http_method::POST) { HANDLE_CURL_RESULT(easy.setPostFields(options.body.makeSpan().data())) } else if (options.method == http_method::PUT) { HANDLE_CURL_RESULT(easy.setUpload(true)) HANDLE_CURL_RESULT(easy.setInFileSize(options.body.byteSize())); HANDLE_CURL_RESULT(easy.setReadFunction(&readCallback)); readData.buffer = &options.body; readData.pos = 0; HANDLE_CURL_RESULT(easy.setReadData(&readData)); } else if (options.method == http_method::HEAD) { HANDLE_CURL_RESULT(easy.setNoBody(true)) } else if (options.method != http_method::GET) { // different option, let's do our best HANDLE_CURL_RESULT(easy.setCustomRequest(true)) if (!options.body.empty()) { HANDLE_CURL_RESULT(easy.setUpload(true)) HANDLE_CURL_RESULT(easy.setInFileSize(options.body.byteSize())); HANDLE_CURL_RESULT(easy.setReadFunction(&readCallback)); readData.buffer = &options.body; readData.pos = 0; HANDLE_CURL_RESULT(easy.setReadData(&readData)); } } HANDLE_CURL_RESULT(easy.setURL(url)) HANDLE_CURL_RESULT(easy.setWriteFunction(&writeCallback)) TypelessBuffer body; HANDLE_CURL_RESULT(easy.setWriteData(&body)) HANDLE_CURL_RESULT(easy.perform()) #undef HANDLE_CURL_RESULT HTTPResponse response = { .body = std::move(body) }; if (const curl::Result httpVersion = easy.getHTTPVersion(); httpVersion.isSuccess()) { switch (*httpVersion) { case CURL_HTTP_VERSION_1_0: response.version = {1, 0}; break; case CURL_HTTP_VERSION_1_1: response.version = {1, 1}; break; case CURL_HTTP_VERSION_2_0: response.version = {2, 0}; break; case CURL_HTTP_VERSION_3: response.version = {3, 0}; break; default: MIJIN_ERROR("Unknown CURL http version returned."); response.version = {1, 0}; break; } } else { co_return StreamError::UNKNOWN_ERROR; } if (const curl::Result status = easy.getStatus(); status.isSuccess()) { response.status = *status; } else { co_return StreamError::UNKNOWN_ERROR; } if (curl::Result> headers = easy.getResponseHeaders(); headers.isSuccess()) { response.headers = std::move(*headers); } else { co_return StreamError::UNKNOWN_ERROR; } co_return response; } } // namespace mijin