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
 |