Add post & custom requests and user agents to AsyncWebRequest

This commit is contained in:
altalk23 2023-09-08 17:17:45 +03:00
parent 7816c435c4
commit c256207457
5 changed files with 119 additions and 21 deletions
CHANGELOG.md
loader
include/Geode/utils
src

View file

@ -8,6 +8,7 @@
* Implement UI for selection of downloading specific mod versions (5d15eb0)
* Change install & uninstall popups to reflect the new changes (d40f467)
* Keep the scroll when enabling, installing etc. a mod (b3d444a)
* Update MacOS crashlog to include base and offset (7816c43)
## v1.2.1
* Mods now target macOS 10.13 instead of 10.14 (7cc1cd4)

View file

@ -104,6 +104,16 @@ 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::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
std::string m_userAgent;
std::string m_customRequest;
bool m_isPostRequest = false;
std::string m_postFields;
bool m_isJsonRequest = false;
};
/**
* An asynchronous, thread-safe web request. Downloads data from the
* internet without slowing the main thread. All callbacks are run in the
@ -111,6 +121,9 @@ 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;
@ -118,9 +131,12 @@ namespace geode::utils::web {
AsyncProgress m_progress = nullptr;
AsyncCancelled m_cancelled = nullptr;
bool m_sent = false;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
mutable std::variant<std::monostate, AsyncWebRequestData> m_extra;
std::vector<std::string> m_httpHeaders;
AsyncWebRequestData& extra();
AsyncWebRequestData const& extra() const;
template <class T>
friend class AsyncWebResult;
friend class SentAsyncWebRequest;
@ -151,6 +167,29 @@ namespace geode::utils::web {
* Can be called more than once.
*/
AsyncWebRequest& header(std::string const& header);
/**
* In order to specify an user agent to the request, give it here.
*/
AsyncWebRequest& userAgent(std::string const& userAgent);
/**
* Specify that the request is a POST request.
*/
AsyncWebRequest& postRequest();
/**
* Specify that the request is a custom request like PUT and DELETE.
*/
AsyncWebRequest& customRequest(std::string const& request);
/**
* Specify the post fields to send with the request. Only valid if
* `postRequest` or `customRequest` was called before.
*/
AsyncWebRequest& postFields(std::string const& fields);
/**
* Specify the post fields to send with the request. Only valid if
* `postRequest` or `customRequest` was called before. Additionally
* sets the content type to application/json.
*/
AsyncWebRequest& postFields(json::Value const& fields);
/**
* URL to fetch from the internet asynchronously
* @param url URL of the data to download. Redirects will be

View file

@ -361,6 +361,7 @@ void Index::Impl::checkForUpdates() {
auto oldSHA = file::readString(checksum).unwrapOr("");
web::AsyncWebRequest()
.join("index-update")
.userAgent("github_api/1.0")
.header(fmt::format("If-None-Match: \"{}\"", oldSHA))
.header("Accept: application/vnd.github.sha")
.fetch("https://api.github.com/repos/geode-sdk/mods/commits/main")

View file

@ -700,6 +700,7 @@ void Loader::Impl::fetchLatestGithubRelease(
// TODO: add header to not get rate limited
web::AsyncWebRequest()
.join("loader-auto-update-check")
.userAgent("github_api/1.0")
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
.json()
.then([this, then](json::Value const& json) {
@ -768,6 +769,7 @@ void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
if (!useLatestRelease) {
web::AsyncWebRequest()
.join("loader-tag-exists-check")
.userAgent("github_api/1.0")
.fetch(fmt::format(
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
this->getVersion().toString()

View file

@ -48,7 +48,6 @@ Result<> web::fetchFile(
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
if (prog) {
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, utils::fetch::progress);
@ -81,7 +80,6 @@ Result<ByteVector> web::fetchBytes(std::string const& url) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
auto res = curl_easy_perform(curl);
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
@ -120,7 +118,6 @@ Result<std::string> web::fetch(std::string const& url) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
auto res = curl_easy_perform(curl);
if (res != CURLE_OK) {
curl_easy_cleanup(curl);
@ -162,9 +159,9 @@ private:
SentAsyncWebRequest* m_self;
mutable std::mutex m_mutex;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target =
std::monostate();
AsyncWebRequestData m_extra;
std::vector<std::string> m_httpHeaders;
template <class T>
friend class AsyncWebResult;
@ -187,7 +184,7 @@ static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUES
static std::mutex RUNNING_REQUESTS_MUTEX;
SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) :
m_id(id), m_url(req.m_url), m_target(req.m_target), m_httpHeaders(req.m_httpHeaders) {
m_id(id), m_url(req.m_url), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
#define AWAIT_RESUME() \
{\
@ -221,16 +218,16 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
std::unique_ptr<std::ofstream> file = nullptr;
// into file
if (std::holds_alternative<ghc::filesystem::path>(m_target)) {
if (std::holds_alternative<ghc::filesystem::path>(m_extra.m_target)) {
file = std::make_unique<std::ofstream>(
std::get<ghc::filesystem::path>(m_target), std::ios::out | std::ios::binary
std::get<ghc::filesystem::path>(m_extra.m_target), std::ios::out | std::ios::binary
);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file.get());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
}
// into stream
else if (std::holds_alternative<std::ostream*>(m_target)) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, std::get<std::ostream*>(m_target));
else if (std::holds_alternative<std::ostream*>(m_extra.m_target)) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, std::get<std::ostream*>(m_extra.m_target));
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
}
// into memory
@ -242,8 +239,30 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
// No need to verify SSL, we trust our domains :-)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
// Github User Agent
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
// User Agent
curl_easy_setopt(curl, CURLOPT_USERAGENT, m_extra.m_userAgent.c_str());
// Headers
curl_slist* headers = nullptr;
for (auto& header : m_httpHeaders) {
headers = curl_slist_append(headers, header.c_str());
}
// Post request
if (m_extra.m_isPostRequest || m_extra.m_customRequest.size()) {
if (m_extra.m_isPostRequest) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
}
else {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, m_extra.m_customRequest.c_str());
}
if (m_extra.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());
}
// Track progress
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
// Follow redirects
@ -251,10 +270,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
// Fail if response code is 4XX or 5XX
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_slist* headers = nullptr;
for (auto& header : m_httpHeaders) {
headers = curl_slist_append(headers, header.c_str());
}
// Headers end
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
struct ProgressData {
@ -318,8 +334,8 @@ void SentAsyncWebRequest::Impl::doCancel() {
m_cleanedUp = true;
// remove file if downloaded to one
if (std::holds_alternative<ghc::filesystem::path>(m_target)) {
auto path = std::get<ghc::filesystem::path>(m_target);
if (std::holds_alternative<ghc::filesystem::path>(m_extra.m_target)) {
auto path = std::get<ghc::filesystem::path>(m_extra.m_target);
if (ghc::filesystem::exists(path)) {
try {
ghc::filesystem::remove(path);
@ -410,11 +426,50 @@ void SentAsyncWebRequest::error(std::string const& error, int code) {
return m_impl->error(error, code);
}
AsyncWebRequestData& AsyncWebRequest::extra() {
if (!std::holds_alternative<AsyncWebRequestData>(m_extra)) {
m_extra = AsyncWebRequestData();
}
return std::get<AsyncWebRequestData>(m_extra);
}
AsyncWebRequestData const& AsyncWebRequest::extra() const {
if (!std::holds_alternative<AsyncWebRequestData>(m_extra)) {
m_extra = AsyncWebRequestData();
}
return std::get<AsyncWebRequestData>(m_extra);
}
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
m_joinID = requestID;
return *this;
}
AsyncWebRequest& AsyncWebRequest::userAgent(std::string const& userAgent) {
this->extra().m_userAgent = userAgent;
return *this;
}
AsyncWebRequest& AsyncWebRequest::postRequest() {
this->extra().m_isPostRequest = true;
return *this;
}
AsyncWebRequest& AsyncWebRequest::customRequest(std::string const& request) {
this->extra().m_customRequest = request;
return *this;
}
AsyncWebRequest& AsyncWebRequest::postFields(std::string const& fields) {
this->extra().m_postFields = fields;
return *this;
}
AsyncWebRequest& AsyncWebRequest::postFields(json::Value const& fields) {
this->extra().m_isJsonRequest = true;
return this->postFields(fields.dump());
}
AsyncWebRequest& AsyncWebRequest::header(std::string const& header) {
m_httpHeaders.push_back(header);
return *this;
@ -489,14 +544,14 @@ AsyncWebRequest::~AsyncWebRequest() {
}
AsyncWebResult<std::monostate> AsyncWebResponse::into(std::ostream& stream) {
m_request.m_target = &stream;
m_request.extra().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.extra().m_target = path;
return this->as(+[](ByteVector const&) -> Result<std::monostate> {
return Ok(std::monostate());
});