mijin2/source/mijin/net/request.cpp

183 lines
4.8 KiB
C++

#include "./request.hpp"
#include <mutex>
#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<TypelessBuffer*>(userdata);
body.append(std::span<const char>(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<ReadData*>(userdata);
const std::size_t bytesToRead = std::min(size * nmemb, data.buffer->byteSize() - data.pos);
std::memcpy(ptr, static_cast<std::byte*>(data.buffer->data()) + data.pos, bytesToRead);
data.pos += bytesToRead;
return bytesToRead;
}
}
Task<StreamResult<HTTPResponse>> 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<const char>().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<int> 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<int> status = easy.getStatus(); status.isSuccess())
{
response.status = *status;
}
else
{
co_return StreamError::UNKNOWN_ERROR;
}
if (curl::Result<std::multimap<std::string, std::string>> headers = easy.getResponseHeaders(); headers.isSuccess())
{
response.headers = std::move(*headers);
}
else
{
co_return StreamError::UNKNOWN_ERROR;
}
co_return response;
}
} // namespace mijin