184 lines
4.8 KiB
C++
184 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 = {
|
|
.version = {},
|
|
.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
|