make AsyncWebRequest pimpl

This commit is contained in:
mat 2024-01-03 22:17:27 -03:00
parent 3187a07181
commit 1ee1352d70
2 changed files with 103 additions and 92 deletions

View file

@ -104,16 +104,6 @@ namespace geode::utils::web {
template <class T>
using DataConverter = Result<T> (*)(ByteVector const&);
// Hack until 2.0.0 to store extra members in AsyncWebRequest
struct AsyncWebRequestData {
std::string m_userAgent;
std::string m_customRequest;
bool m_isPostRequest = false;
std::string m_postFields;
bool m_isJsonRequest = false;
bool m_sent = false;
};
/**
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
@ -121,34 +111,39 @@ namespace geode::utils::web {
*/
class GEODE_DLL AsyncWebRequest {
private:
// i want to cry whose idea was to not make this pimpl
// For 2.0.0: make this pimpl
std::optional<std::string> m_joinID;
std::string m_url;
AsyncThen m_then = nullptr;
AsyncExpectCode m_expect = nullptr;
AsyncProgress m_progress = nullptr;
AsyncCancelled m_cancelled = nullptr;
mutable AsyncWebRequestData* m_extra = nullptr;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
std::vector<std::string> m_httpHeaders;
AsyncWebRequestData& extra();
AsyncWebRequestData const& extra() const;
class Impl;
std::unique_ptr<Impl> m_impl;
template <class T>
friend class AsyncWebResult;
friend class SentAsyncWebRequest;
friend class AsyncWebResponse;
friend class SentAsyncWebRequest::Impl;
AsyncWebRequest& setThen(AsyncThen);
public:
/**
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
* GD thread, so interacting with the Cocos2d UI is perfectly safe
*/
AsyncWebRequest() = default;
AsyncWebRequest();
~AsyncWebRequest();
/**
* URL to fetch from the internet asynchronously
* @param url URL of the data to download. Redirects will be
* automatically followed
* @returns An AsyncWebResponse object
*/
AsyncWebResponse fetch(std::string const& url);
/**
* Begin the web request. It's not always necessary to call this as the
* destructor calls it automatically, but if you need access to the
* handle of the sent request, use this
* @returns Handle to the sent web request
*/
SentAsyncWebRequestHandle send();
/**
* If you only want one instance of this web request to run (for example,
@ -190,13 +185,6 @@ namespace geode::utils::web {
* sets the content type to application/json.
*/
AsyncWebRequest& postFields(matjson::Value const& fields);
/**
* URL to fetch from the internet asynchronously
* @param url URL of the data to download. Redirects will be
* automatically followed
* @returns An AsyncWebResponse object
*/
AsyncWebResponse fetch(std::string const& url);
/**
* Specify a callback to run if the download fails. The callback is
* always ran in the GD thread, so interacting with UI is safe
@ -228,14 +216,6 @@ namespace geode::utils::web {
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& cancelled(AsyncCancelled handler);
/**
* Begin the web request. It's not always necessary to call this as the
* destructor calls it automatically, but if you need access to the
* handle of the sent request, use this
* @returns Handle to the sent web request
*/
SentAsyncWebRequestHandle send();
~AsyncWebRequest();
};
template <class T>
@ -335,7 +315,7 @@ namespace geode::utils::web {
template <class T>
AsyncWebRequest& AsyncWebResult<T>::then(utils::MiniFunction<void(T)> handle) {
m_request.m_then = [converter = m_converter,
return m_request.setThen([converter = m_converter,
handle](SentAsyncWebRequest& req, ByteVector const& arr) {
auto conv = converter(arr);
if (conv) {
@ -344,13 +324,12 @@ namespace geode::utils::web {
else {
req.error("Unable to convert value: " + conv.unwrapErr(), -1);
}
};
return m_request;
});
}
template <class T>
AsyncWebRequest& AsyncWebResult<T>::then(utils::MiniFunction<void(SentAsyncWebRequest&, T)> handle) {
m_request.m_then = [converter = m_converter,
return m_request.setThen([converter = m_converter,
handle](SentAsyncWebRequest& req, ByteVector const& arr) {
auto conv = converter(arr);
if (conv) {
@ -359,7 +338,6 @@ namespace geode::utils::web {
else {
req.error("Unable to convert value: " + conv.error(), -1);
}
};
return m_request;
});
}
}

View file

@ -161,7 +161,12 @@ private:
SentAsyncWebRequest* m_self;
mutable std::mutex m_mutex;
AsyncWebRequestData m_extra;
std::string m_userAgent;
std::string m_customRequest;
bool m_isPostRequest = false;
std::string m_postFields;
bool m_isJsonRequest = false;
bool m_sent = false;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
std::vector<std::string> m_httpHeaders;
@ -183,11 +188,41 @@ public:
friend class SentAsyncWebRequest;
};
class AsyncWebRequest::Impl {
public:
std::optional<std::string> m_joinID;
std::string m_url;
AsyncThen m_then = nullptr;
AsyncExpectCode m_expect = nullptr;
AsyncProgress m_progress = nullptr;
AsyncCancelled m_cancelled = nullptr;
std::string m_userAgent;
std::string m_customRequest;
bool m_isPostRequest = false;
std::string m_postFields;
bool m_isJsonRequest = false;
bool m_sent = false;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
std::vector<std::string> m_httpHeaders;
SentAsyncWebRequestHandle send(AsyncWebRequest&);
};
static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUESTS{};
static std::mutex RUNNING_REQUESTS_MUTEX;
SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) :
m_self(self), m_id(id), m_url(req.m_url), m_target(req.m_target), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
m_self(self),
m_id(id),
m_url(req.m_impl->m_url),
m_target(req.m_impl->m_target),
m_userAgent(req.m_impl->m_userAgent),
m_customRequest(req.m_impl->m_customRequest),
m_isPostRequest(req.m_impl->m_isPostRequest),
m_postFields(req.m_impl->m_postFields),
m_isJsonRequest(req.m_impl->m_isJsonRequest),
m_sent(req.m_impl->m_sent),
m_httpHeaders(req.m_impl->m_httpHeaders) {
#define AWAIT_RESUME() \
{\
@ -201,10 +236,10 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
}\
}\
if (req.m_then) m_thens.push_back(req.m_then);
if (req.m_progress) m_progresses.push_back(req.m_progress);
if (req.m_cancelled) m_cancelleds.push_back(req.m_cancelled);
if (req.m_expect) m_expects.push_back(req.m_expect);
if (req.m_impl->m_then) m_thens.push_back(req.m_impl->m_then);
if (req.m_impl->m_progress) m_progresses.push_back(req.m_impl->m_progress);
if (req.m_impl->m_cancelled) m_cancelleds.push_back(req.m_impl->m_cancelled);
if (req.m_impl->m_expect) m_expects.push_back(req.m_impl->m_expect);
std::thread([this]() {
AWAIT_RESUME();
@ -243,7 +278,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
// User Agent
curl_easy_setopt(curl, CURLOPT_USERAGENT, m_extra.m_userAgent.c_str());
curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgent.c_str());
// Headers
curl_slist* headers = nullptr;
@ -252,18 +287,18 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
}
// Post request
if (m_extra.m_isPostRequest || m_extra.m_customRequest.size()) {
if (m_extra.m_isPostRequest) {
if (m_isPostRequest || m_customRequest.size()) {
if (m_isPostRequest) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
}
else {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, m_extra.m_customRequest.c_str());
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, m_customRequest.c_str());
}
if (m_extra.m_isJsonRequest) {
if (m_isJsonRequest) {
headers = curl_slist_append(headers, "Content-Type: application/json");
}
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, m_extra.m_postFields.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, m_extra.m_postFields.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, m_postFields.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, m_postFields.size());
}
// Track progress
@ -443,85 +478,88 @@ void SentAsyncWebRequest::error(std::string const& error, int code) {
return m_impl->error(error, code);
}
AsyncWebRequestData& AsyncWebRequest::extra() {
if (!m_extra) {
m_extra = new AsyncWebRequestData();
}
return *m_extra;
AsyncWebRequest::AsyncWebRequest() {
m_impl = std::make_unique<AsyncWebRequest::Impl>();
}
AsyncWebRequestData const& AsyncWebRequest::extra() const {
if (!m_extra) {
m_extra = new AsyncWebRequestData();
}
return *m_extra;
AsyncWebRequest::~AsyncWebRequest() {
this->send();
}
AsyncWebRequest& AsyncWebRequest::setThen(AsyncThen then) {
m_impl->m_then = then;
return *this;
}
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
m_joinID = requestID;
m_impl->m_joinID = requestID;
return *this;
}
AsyncWebRequest& AsyncWebRequest::userAgent(std::string const& userAgent) {
this->extra().m_userAgent = userAgent;
m_impl->m_userAgent = userAgent;
return *this;
}
AsyncWebRequest& AsyncWebRequest::postRequest() {
this->extra().m_isPostRequest = true;
m_impl->m_isPostRequest = true;
return *this;
}
AsyncWebRequest& AsyncWebRequest::customRequest(std::string const& request) {
this->extra().m_customRequest = request;
m_impl->m_customRequest = request;
return *this;
}
AsyncWebRequest& AsyncWebRequest::postFields(std::string const& fields) {
this->extra().m_postFields = fields;
m_impl->m_postFields = fields;
return *this;
}
AsyncWebRequest& AsyncWebRequest::postFields(matjson::Value const& fields) {
this->extra().m_isJsonRequest = true;
m_impl->m_isJsonRequest = true;
return this->postFields(fields.dump());
}
AsyncWebRequest& AsyncWebRequest::header(std::string const& header) {
m_httpHeaders.push_back(header);
m_impl->m_httpHeaders.push_back(header);
return *this;
}
AsyncWebResponse AsyncWebRequest::fetch(std::string const& url) {
m_url = url;
m_impl->m_url = url;
return AsyncWebResponse(*this);
}
AsyncWebRequest& AsyncWebRequest::expect(AsyncExpect handler) {
m_expect = [handler](std::string const& info, auto) {
m_impl->m_expect = [handler](std::string const& info, auto) {
return handler(info);
};
return *this;
}
AsyncWebRequest& AsyncWebRequest::expect(AsyncExpectCode handler) {
m_expect = handler;
m_impl->m_expect = handler;
return *this;
}
AsyncWebRequest& AsyncWebRequest::progress(AsyncProgress progress) {
m_progress = progress;
m_impl->m_progress = progress;
return *this;
}
AsyncWebRequest& AsyncWebRequest::cancelled(AsyncCancelled cancelledFunc) {
m_cancelled = cancelledFunc;
m_impl->m_cancelled = cancelledFunc;
return *this;
}
SentAsyncWebRequestHandle AsyncWebRequest::send() {
if (this->extra().m_sent) return nullptr;
this->extra().m_sent = true;
return m_impl->send(*this);
}
SentAsyncWebRequestHandle AsyncWebRequest::Impl::send(AsyncWebRequest& reqObj) {
if (m_sent) return nullptr;
m_sent = true;
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
@ -544,7 +582,7 @@ SentAsyncWebRequestHandle AsyncWebRequest::send() {
}
else {
auto id = m_joinID.value_or("__anon_request_" + std::to_string(COUNTER++));
ret = SentAsyncWebRequest::create(*this, id);
ret = SentAsyncWebRequest::create(reqObj, id);
RUNNING_REQUESTS.insert({id, ret});
}
@ -556,20 +594,15 @@ SentAsyncWebRequestHandle AsyncWebRequest::send() {
return ret;
}
AsyncWebRequest::~AsyncWebRequest() {
this->send();
delete m_extra;
}
AsyncWebResult<std::monostate> AsyncWebResponse::into(std::ostream& stream) {
m_request.m_target = &stream;
m_request.m_impl->m_target = &stream;
return this->as(+[](ByteVector const&) -> Result<std::monostate> {
return Ok(std::monostate());
});
}
AsyncWebResult<std::monostate> AsyncWebResponse::into(ghc::filesystem::path const& path) {
m_request.m_target = path;
m_request.m_impl->m_target = path;
return this->as(+[](ByteVector const&) -> Result<std::monostate> {
return Ok(std::monostate());
});