mirror of
https://github.com/geode-sdk/geode.git
synced 2025-02-17 00:30:26 -05:00
make AsyncWebRequest pimpl
This commit is contained in:
parent
3187a07181
commit
1ee1352d70
2 changed files with 103 additions and 92 deletions
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue