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
 |