http: Always use/require progress in requests.

This fixes several cases where we weren't passing cancel flags
consistently.
This commit is contained in:
Unknown W. Brackets 2021-05-01 10:19:27 -07:00
parent f762fbc53d
commit 3379f33882
9 changed files with 69 additions and 59 deletions

View File

@ -32,7 +32,8 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector<File::
// Start by requesting the list of files from the server.
if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) {
if (http.Connect(2, 20.0, cancel)) {
code = http.GET(baseURL.Resource().c_str(), &result, responseHeaders);
http::RequestProgress progress(cancel);
code = http.GET(baseURL.Resource().c_str(), &result, responseHeaders, &progress);
http.Disconnect();
}
}

View File

@ -243,35 +243,35 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog
}
}
int Client::GET(const char *resource, Buffer *output, std::vector<std::string> &responseHeaders, float *progress, bool *cancelled) {
int Client::GET(const char *resource, Buffer *output, std::vector<std::string> &responseHeaders, RequestProgress *progress) {
const char *otherHeaders =
"Accept: */*\r\n"
"Accept-Encoding: gzip\r\n";
int err = SendRequest("GET", resource, otherHeaders, progress, cancelled);
int err = SendRequest("GET", resource, otherHeaders, progress);
if (err < 0) {
return err;
}
net::Buffer readbuf;
int code = ReadResponseHeaders(&readbuf, responseHeaders, progress, cancelled);
int code = ReadResponseHeaders(&readbuf, responseHeaders, progress);
if (code < 0) {
return code;
}
err = ReadResponseEntity(&readbuf, responseHeaders, output, progress, cancelled);
err = ReadResponseEntity(&readbuf, responseHeaders, output, progress);
if (err < 0) {
return err;
}
return code;
}
int Client::GET(const char *resource, Buffer *output, float *progress, bool *cancelled) {
int Client::GET(const char *resource, Buffer *output, RequestProgress *progress) {
std::vector<std::string> responseHeaders;
int code = GET(resource, output, responseHeaders, progress, cancelled);
int code = GET(resource, output, responseHeaders, progress);
return code;
}
int Client::POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, float *progress) {
int Client::POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress) {
char otherHeaders[2048];
if (mime.empty()) {
snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\n", (long long)data.size());
@ -297,18 +297,16 @@ int Client::POST(const char *resource, const std::string &data, const std::strin
return code;
}
int Client::POST(const char *resource, const std::string &data, Buffer *output, float *progress) {
int Client::POST(const char *resource, const std::string &data, Buffer *output, RequestProgress *progress) {
return POST(resource, data, "", output, progress);
}
int Client::SendRequest(const char *method, const char *resource, const char *otherHeaders, float *progress, bool *cancelled) {
return SendRequestWithData(method, resource, "", otherHeaders, progress, cancelled);
int Client::SendRequest(const char *method, const char *resource, const char *otherHeaders, RequestProgress *progress) {
return SendRequestWithData(method, resource, "", otherHeaders, progress);
}
int Client::SendRequestWithData(const char *method, const char *resource, const std::string &data, const char *otherHeaders, float *progress, bool *cancelled) {
if (progress) {
*progress = 0.01f;
}
int Client::SendRequestWithData(const char *method, const char *resource, const std::string &data, const char *otherHeaders, RequestProgress *progress) {
progress->progress = 0.01f;
net::Buffer buffer;
const char *tpl =
@ -325,20 +323,20 @@ int Client::SendRequestWithData(const char *method, const char *resource, const
userAgent_.c_str(),
otherHeaders ? otherHeaders : "");
buffer.Append(data);
bool flushed = buffer.FlushSocket(sock(), dataTimeout_);
bool flushed = buffer.FlushSocket(sock(), dataTimeout_, progress->cancelled);
if (!flushed) {
return -1; // TODO error code.
}
return 0;
}
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, float *progress, bool *cancelled) {
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, RequestProgress *progress) {
// Snarf all the data we can into RAM. A little unsafe but hey.
static constexpr float CANCEL_INTERVAL = 0.25f;
bool ready = false;
double endTimeout = time_now_d() + dataTimeout_;
while (!ready) {
if (cancelled && *cancelled)
if (progress->cancelled && *progress->cancelled)
return -1;
ready = fd_util::WaitUntilReady(sock(), CANCEL_INTERVAL, false);
if (!ready && time_now_d() > endTimeout) {
@ -385,7 +383,7 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &
return code;
}
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, float *progress, bool *cancelled) {
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, RequestProgress *progress) {
bool gzip = false;
bool chunked = false;
int contentLength = 0;
@ -417,25 +415,25 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
contentLength = 0;
}
if (!contentLength && progress) {
if (!contentLength) {
// Content length is unknown.
// Set progress to 1% so it looks like something is happening...
*progress = 0.1f;
progress->progress = 0.1f;
}
if (!contentLength || !progress) {
if (!contentLength) {
// No way to know how far along we are. Let's just not update the progress counter.
if (!readbuf->ReadAllWithProgress(sock(), contentLength, nullptr, cancelled))
if (!readbuf->ReadAllWithProgress(sock(), contentLength, nullptr, progress->cancelled))
return -1;
} else {
// Let's read in chunks, updating progress between each.
if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress, cancelled))
if (!readbuf->ReadAllWithProgress(sock(), contentLength, &progress->progress, progress->cancelled))
return -1;
}
// output now contains the rest of the reply. Dechunk it.
if (chunked) {
DeChunk(readbuf, output, contentLength, progress);
DeChunk(readbuf, output, contentLength, &progress->progress);
} else {
output->Append(*readbuf);
}
@ -447,21 +445,18 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
bool result = decompress_string(compressed, &decompressed);
if (!result) {
ERROR_LOG(IO, "Error decompressing using zlib");
if (progress)
*progress = 0.0f;
progress->progress = 0.0f;
return -1;
}
output->Append(decompressed);
}
if (progress) {
*progress = 1.0f;
}
progress->progress = 1.0f;
return 0;
}
Download::Download(const std::string &url, const std::string &outfile)
: url_(url), outfile_(outfile) {
: progress_(&cancelled_), url_(url), outfile_(outfile) {
}
Download::~Download() {
@ -482,7 +477,7 @@ void Download::Join() {
void Download::SetFailed(int code) {
failed_ = true;
progress_ = 1.0f;
progress_.progress = 1.0f;
completed_ = true;
}
@ -511,7 +506,7 @@ int Download::PerformGET(const std::string &url) {
return -1;
}
return client.GET(fileUrl.Resource().c_str(), &buffer_, responseHeaders_, &progress_, &cancelled_);
return client.GET(fileUrl.Resource().c_str(), &buffer_, responseHeaders_, &progress_);
}
std::string Download::RedirectLocation(const std::string &baseUrl) {
@ -565,7 +560,7 @@ void Download::Do() {
resultCode_ = resultCode;
}
progress_ = 1.0f;
progress_.progress = 1.0f;
// Set this last to ensure no race conditions when checking Done. Users must always check
// Done before looking at the result code.

View File

@ -43,26 +43,34 @@ namespace http {
bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::string &header, std::string *value);
struct RequestProgress {
RequestProgress() {}
explicit RequestProgress(bool *c) : cancelled(c) {}
float progress = 0.0f;
bool *cancelled = nullptr;
};
class Client : public net::Connection {
public:
Client();
~Client();
// Return value is the HTTP return code. 200 means OK. < 0 means some local error.
int GET(const char *resource, Buffer *output, float *progress = nullptr, bool *cancelled = nullptr);
int GET(const char *resource, Buffer *output, std::vector<std::string> &responseHeaders, float *progress = nullptr, bool *cancelled = nullptr);
int GET(const char *resource, Buffer *output, RequestProgress *progress);
int GET(const char *resource, Buffer *output, std::vector<std::string> &responseHeaders, RequestProgress *progress);
// Return value is the HTTP return code.
int POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, float *progress = nullptr);
int POST(const char *resource, const std::string &data, Buffer *output, float *progress = nullptr);
int POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress);
int POST(const char *resource, const std::string &data, Buffer *output, RequestProgress *progress);
// HEAD, PUT, DELETE aren't implemented yet, but can be done with SendRequest.
int SendRequest(const char *method, const char *resource, const char *otherHeaders = nullptr, float *progress = nullptr, bool *cancelled = nullptr);
int SendRequestWithData(const char *method, const char *resource, const std::string &data, const char *otherHeaders = nullptr, float *progress = nullptr, bool *cancelled = nullptr);
int ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, float *progress = nullptr, bool *cancelled = nullptr);
int SendRequest(const char *method, const char *resource, const char *otherHeaders, RequestProgress *progress);
int SendRequestWithData(const char *method, const char *resource, const std::string &data, const char *otherHeaders, RequestProgress *progress);
int ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, RequestProgress *progress);
// If your response contains a response, you must read it.
int ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, float *progress = nullptr, bool *cancelled = nullptr);
int ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, RequestProgress *progress);
void SetDataTimeout(double t) {
dataTimeout_ = t;
@ -89,7 +97,7 @@ public:
void Join();
// Returns 1.0 when done. That one value can be compared exactly - or just use Done().
float Progress() const { return progress_; }
float Progress() const { return progress_.progress; }
bool Done() const { return completed_; }
bool Failed() const { return failed_; }
@ -133,7 +141,7 @@ private:
int PerformGET(const std::string &url);
std::string RedirectLocation(const std::string &baseUrl);
void SetFailed(int code);
float progress_ = 0.0f;
RequestProgress progress_;
Buffer buffer_;
std::vector<std::string> responseHeaders_;
std::string url_;

View File

@ -50,6 +50,7 @@ bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) {
bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, bool *cancelled) {
static constexpr float CANCEL_INTERVAL = 0.25f;
std::vector<char> buf;
// We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time.
if (knownSize >= 65536 * 16) {
buf.resize(65536);
} else if (knownSize >= 1024 * 16) {

View File

@ -24,7 +24,7 @@
#include "Core/FileLoaders/HTTPFileLoader.h"
HTTPFileLoader::HTTPFileLoader(const std::string &filename)
: url_(filename), filename_(filename) {
: url_(filename), progress_(&cancel_), filename_(filename) {
}
void HTTPFileLoader::Prepare() {
@ -137,7 +137,7 @@ int HTTPFileLoader::SendHEAD(const Url &url, std::vector<std::string> &responseH
return -400;
}
int err = client_.SendRequest("HEAD", url.Resource().c_str());
int err = client_.SendRequest("HEAD", url.Resource().c_str(), nullptr, &progress_);
if (err < 0) {
ERROR_LOG(LOADER, "HTTP request failed, failed to send request: %s port %d", url.Host().c_str(), url.Port());
latestError_ = "Could not connect (could not request data)";
@ -146,7 +146,7 @@ int HTTPFileLoader::SendHEAD(const Url &url, std::vector<std::string> &responseH
}
net::Buffer readbuf;
return client_.ReadResponseHeaders(&readbuf, responseHeaders);
return client_.ReadResponseHeaders(&readbuf, responseHeaders, &progress_);
}
HTTPFileLoader::~HTTPFileLoader() {
@ -205,7 +205,7 @@ size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags f
net::Buffer readbuf;
std::vector<std::string> responseHeaders;
int code = client_.ReadResponseHeaders(&readbuf, responseHeaders);
int code = client_.ReadResponseHeaders(&readbuf, responseHeaders, &progress_);
if (code != 206) {
ERROR_LOG(LOADER, "HTTP server did not respond with range, received code=%03d", code);
latestError_ = "Invalid response reading data";
@ -236,7 +236,7 @@ size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags f
// TODO: Would be nice to read directly.
net::Buffer output;
int res = client_.ReadResponseEntity(&readbuf, responseHeaders, &output);
int res = client_.ReadResponseEntity(&readbuf, responseHeaders, &output, &progress_);
if (res != 0) {
ERROR_LOG(LOADER, "Unable to read HTTP response entity: %d", res);
// Let's take anything we got anyway. Not worse than returning nothing?
@ -259,8 +259,8 @@ size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags f
void HTTPFileLoader::Connect() {
if (!connected_) {
cancelConnect_ = false;
cancel_ = false;
// Latency is important here, so reduce the timeout.
connected_ = client_.Connect(3, 10.0, &cancelConnect_);
connected_ = client_.Connect(3, 10.0, &cancel_);
}
}

View File

@ -46,7 +46,7 @@ public:
virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags = Flags::NONE) override;
void Cancel() override {
cancelConnect_ = true;
cancel_ = true;
}
std::string LatestError() const override {
@ -70,9 +70,10 @@ private:
s64 filepos_ = 0;
Url url_;
http::Client client_;
http::RequestProgress progress_;
std::string filename_;
bool connected_ = false;
bool cancelConnect_ = false;
bool cancel_ = false;
const char *latestError_ = "";
std::once_flag preparedFlag_;

View File

@ -261,6 +261,7 @@ namespace Reporting
bool SendReportRequest(const char *uri, const std::string &data, const std::string &mimeType, Buffer *output = NULL)
{
http::Client http;
http::RequestProgress progress;
Buffer theVoid;
http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));
@ -274,7 +275,7 @@ namespace Reporting
if (http.Resolve(serverHost, ServerPort())) {
http.Connect();
int result = http.POST(uri, data, mimeType, output);
int result = http.POST(uri, data, mimeType, output, &progress);
http.Disconnect();
return result >= 200 && result < 300;

View File

@ -63,6 +63,7 @@ static ServerStatus RetrieveStatus() {
static bool RegisterServer(int port) {
bool success = false;
http::Client http;
http::RequestProgress progress;
Buffer theVoid;
http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));
@ -73,7 +74,7 @@ static bool RegisterServer(int port) {
std::string ip = fd_util::GetLocalIP(http.sock());
snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port);
if (http.GET(resource4, &theVoid) > 0)
if (http.GET(resource4, &theVoid, &progress) > 0)
success = true;
theVoid.Skip(theVoid.size());
http.Disconnect();
@ -86,7 +87,7 @@ static bool RegisterServer(int port) {
// We register both IPv4 and IPv6 in case the other client is using a different one.
if (resource4[0] != 0 && http.Connect(timeout)) {
if (http.GET(resource4, &theVoid) > 0)
if (http.GET(resource4, &theVoid, &progress) > 0)
success = true;
theVoid.Skip(theVoid.size());
http.Disconnect();
@ -98,7 +99,7 @@ static bool RegisterServer(int port) {
std::string ip = fd_util::GetLocalIP(http.sock());
snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port);
if (http.GET(resource6, &theVoid) > 0)
if (http.GET(resource6, &theVoid, &progress) > 0)
success = true;
theVoid.Skip(theVoid.size());
http.Disconnect();

View File

@ -136,7 +136,8 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
}
SetStatus("Loading game list from [URL]...", host, port);
code = http.GET(subdir.c_str(), &result);
http::RequestProgress progress(&scanCancelled);
code = http.GET(subdir.c_str(), &result, &progress);
http.Disconnect();
if (code != 200) {
@ -189,7 +190,8 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
SetStatus("Looking for peers...", "", 0);
if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) {
if (http.Connect(2, 20.0, &scanCancelled)) {
code = http.GET("/match/list", &result);
http::RequestProgress progress(&scanCancelled);
code = http.GET("/match/list", &result, &progress);
http.Disconnect();
}
}