refactor: move Network files from src/common/ to src/common/network/ (#5089)

This commit is contained in:
pajlada
2024-01-15 21:28:44 +01:00
committed by GitHub
parent ad69755bbb
commit 93e2bc18fa
42 changed files with 72 additions and 71 deletions
+35
View File
@@ -0,0 +1,35 @@
#include "common/network/NetworkCommon.hpp"
#include <QStringList>
namespace chatterino {
std::vector<std::pair<QByteArray, QByteArray>> parseHeaderList(
const QString &headerListString)
{
std::vector<std::pair<QByteArray, QByteArray>> res;
// Split the string into a list of header pairs
// e.g. "Authorization:secretkey;NextHeader:boo" turning into ["Authorization:secretkey","NextHeader:boo"]
auto headerPairs = headerListString.split(";");
for (const auto &headerPair : headerPairs)
{
const auto headerName =
headerPair.section(":", 0, 0).trimmed().toUtf8();
const auto headerValue = headerPair.section(":", 1).trimmed().toUtf8();
if (headerName.isEmpty() || headerValue.isEmpty())
{
// The header part either didn't contain a : or the name/value was empty
// Skip the value
continue;
}
res.emplace_back(headerName, headerValue);
}
return res;
}
} // namespace chatterino
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <QString>
#include <functional>
#include <vector>
class QNetworkReply;
namespace chatterino {
class NetworkResult;
using NetworkSuccessCallback = std::function<void(NetworkResult)>;
using NetworkErrorCallback = std::function<void(NetworkResult)>;
using NetworkFinallyCallback = std::function<void()>;
enum class NetworkRequestType {
Get,
Post,
Put,
Delete,
Patch,
};
// parseHeaderList takes a list of headers in string form,
// where each header pair is separated by semicolons (;) and the header name and value is divided by a colon (:)
//
// We return a vector of pairs, where the first value is the header name and the second value is the header value
//
// e.g. "Authorization:secretkey;NextHeader:boo" will return [{"Authorization", "secretkey"}, {"NextHeader", "boo"}]
std::vector<std::pair<QByteArray, QByteArray>> parseHeaderList(
const QString &headerListString);
} // namespace chatterino
+22
View File
@@ -0,0 +1,22 @@
#include "common/network/NetworkManager.hpp"
#include <QNetworkAccessManager>
namespace chatterino {
QThread NetworkManager::workerThread;
QNetworkAccessManager NetworkManager::accessManager;
void NetworkManager::init()
{
NetworkManager::accessManager.moveToThread(&NetworkManager::workerThread);
NetworkManager::workerThread.start();
}
void NetworkManager::deinit()
{
NetworkManager::workerThread.quit();
NetworkManager::workerThread.wait();
}
} // namespace chatterino
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <QNetworkAccessManager>
#include <QThread>
namespace chatterino {
class NetworkManager : public QObject
{
Q_OBJECT
public:
static QThread workerThread;
static QNetworkAccessManager accessManager;
static void init();
static void deinit();
};
} // namespace chatterino
+203
View File
@@ -0,0 +1,203 @@
#include "common/network/NetworkPrivate.hpp"
#include "common/network/NetworkManager.hpp"
#include "common/network/NetworkResult.hpp"
#include "common/network/NetworkTask.hpp"
#include "common/QLogging.hpp"
#include "singletons/Paths.hpp"
#include "util/AbandonObject.hpp"
#include "util/DebugCount.hpp"
#include "util/PostToThread.hpp"
#include <magic_enum/magic_enum.hpp>
#include <QCryptographicHash>
#include <QElapsedTimer>
#include <QFile>
#include <QNetworkReply>
#include <QtConcurrent>
#ifdef NDEBUG
constexpr qsizetype SLOW_HTTP_THRESHOLD = 30;
#else
constexpr qsizetype SLOW_HTTP_THRESHOLD = 90;
#endif
using namespace chatterino::network::detail;
namespace {
using namespace chatterino;
void runCallback(bool concurrent, auto &&fn)
{
if (concurrent)
{
std::ignore = QtConcurrent::run(std::forward<decltype(fn)>(fn));
}
else
{
runInGuiThread(std::forward<decltype(fn)>(fn));
}
}
void loadUncached(std::shared_ptr<NetworkData> &&data)
{
DebugCount::increase("http request started");
NetworkRequester requester;
auto *worker = new NetworkTask(std::move(data));
worker->moveToThread(&NetworkManager::workerThread);
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
&NetworkTask::run);
emit requester.requestUrl();
}
void loadCached(std::shared_ptr<NetworkData> &&data)
{
QFile cachedFile(getPaths()->cacheDirectory() + "/" + data->getHash());
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
{
loadUncached(std::move(data));
return;
}
// XXX: check if bytes is empty?
QByteArray bytes = cachedFile.readAll();
qCDebug(chatterinoHTTP).noquote() << data->typeString() << "[CACHED] 200"
<< data->request.url().toString();
data->emitSuccess(
{NetworkResult::NetworkError::NoError, QVariant(200), bytes});
data->emitFinally();
}
} // namespace
namespace chatterino {
NetworkData::NetworkData()
{
DebugCount::increase("NetworkData");
}
NetworkData::~NetworkData()
{
DebugCount::decrease("NetworkData");
}
QString NetworkData::getHash()
{
if (this->hash_.isEmpty())
{
QByteArray bytes;
bytes.append(this->request.url().toString().toUtf8());
for (const auto &header : this->request.rawHeaderList())
{
bytes.append(header);
}
QByteArray hashBytes(
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
this->hash_ = hashBytes.toHex();
}
return this->hash_;
}
void NetworkData::emitSuccess(NetworkResult &&result)
{
if (!this->onSuccess)
{
return;
}
runCallback(this->executeConcurrently,
[cb = std::move(this->onSuccess), result = std::move(result),
url = this->request.url(), hasCaller = this->hasCaller,
caller = this->caller]() {
if (hasCaller && caller.isNull())
{
return;
}
QElapsedTimer timer;
timer.start();
cb(result);
if (timer.elapsed() > SLOW_HTTP_THRESHOLD)
{
qCWarning(chatterinoHTTP)
<< "Slow HTTP success handler for" << url.toString()
<< timer.elapsed()
<< "ms (threshold:" << SLOW_HTTP_THRESHOLD << "ms)";
}
});
}
void NetworkData::emitError(NetworkResult &&result)
{
if (!this->onError)
{
return;
}
runCallback(this->executeConcurrently,
[cb = std::move(this->onError), result = std::move(result),
hasCaller = this->hasCaller, caller = this->caller]() {
if (hasCaller && caller.isNull())
{
return;
}
cb(result);
});
}
void NetworkData::emitFinally()
{
if (!this->finally)
{
return;
}
runCallback(this->executeConcurrently,
[cb = std::move(this->finally), hasCaller = this->hasCaller,
caller = this->caller]() {
if (hasCaller && caller.isNull())
{
return;
}
cb();
});
}
QLatin1String NetworkData::typeString() const
{
auto view = magic_enum::enum_name<NetworkRequestType>(this->requestType);
return QLatin1String{view.data(),
static_cast<QLatin1String::size_type>(view.size())};
}
void load(std::shared_ptr<NetworkData> &&data)
{
if (data->cache)
{
std::ignore = QtConcurrent::run([data = std::move(data)]() mutable {
loadCached(std::move(data));
});
}
else
{
loadUncached(std::move(data));
}
}
} // namespace chatterino
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include "common/Common.hpp"
#include "common/network/NetworkCommon.hpp"
#include <QHttpMultiPart>
#include <QNetworkRequest>
#include <QPointer>
#include <QTimer>
#include <memory>
#include <optional>
class QNetworkReply;
namespace chatterino {
class NetworkResult;
class NetworkRequester : public QObject
{
Q_OBJECT
signals:
void requestUrl();
};
class NetworkData
{
public:
NetworkData();
~NetworkData();
NetworkData(const NetworkData &) = delete;
NetworkData(NetworkData &&) = delete;
NetworkData &operator=(const NetworkData &) = delete;
NetworkData &operator=(NetworkData &&) = delete;
QNetworkRequest request;
bool hasCaller{};
QPointer<QObject> caller;
bool cache{};
bool executeConcurrently{};
NetworkSuccessCallback onSuccess;
NetworkErrorCallback onError;
NetworkFinallyCallback finally;
NetworkRequestType requestType = NetworkRequestType::Get;
QByteArray payload;
std::unique_ptr<QHttpMultiPart, DeleteLater> multiPartPayload;
/// By default, there's no explicit timeout for the request.
/// To set a timeout, use NetworkRequest's timeout method
std::optional<std::chrono::milliseconds> timeout{};
QString getHash();
void emitSuccess(NetworkResult &&result);
void emitError(NetworkResult &&result);
void emitFinally();
QLatin1String typeString() const;
private:
QString hash_;
};
void load(std::shared_ptr<NetworkData> &&data);
} // namespace chatterino
+210
View File
@@ -0,0 +1,210 @@
#include "common/network/NetworkRequest.hpp"
#include "common/network/NetworkPrivate.hpp"
#include "common/QLogging.hpp"
#include "common/Version.hpp"
#include <QDebug>
#include <QFile>
#include <QtConcurrent>
#include <cassert>
namespace chatterino {
NetworkRequest::NetworkRequest(const std::string &url,
NetworkRequestType requestType)
: data(new NetworkData)
{
this->data->request.setUrl(QUrl(QString::fromStdString(url)));
this->data->requestType = requestType;
this->initializeDefaultValues();
}
NetworkRequest::NetworkRequest(const QUrl &url, NetworkRequestType requestType)
: data(new NetworkData)
{
this->data->request.setUrl(url);
this->data->requestType = requestType;
this->initializeDefaultValues();
}
NetworkRequest::~NetworkRequest() = default;
NetworkRequest NetworkRequest::type(NetworkRequestType newRequestType) &&
{
this->data->requestType = newRequestType;
return std::move(*this);
}
NetworkRequest NetworkRequest::caller(const QObject *caller) &&
{
if (caller)
{
// Caller must be in gui thread
assert(caller->thread() == qApp->thread());
this->data->caller = const_cast<QObject *>(caller);
this->data->hasCaller = true;
}
return std::move(*this);
}
NetworkRequest NetworkRequest::onError(NetworkErrorCallback cb) &&
{
this->data->onError = std::move(cb);
return std::move(*this);
}
NetworkRequest NetworkRequest::onSuccess(NetworkSuccessCallback cb) &&
{
this->data->onSuccess = std::move(cb);
return std::move(*this);
}
NetworkRequest NetworkRequest::finally(NetworkFinallyCallback cb) &&
{
this->data->finally = std::move(cb);
return std::move(*this);
}
NetworkRequest NetworkRequest::header(const char *headerName,
const char *value) &&
{
this->data->request.setRawHeader(headerName, value);
return std::move(*this);
}
NetworkRequest NetworkRequest::header(const char *headerName,
const QByteArray &value) &&
{
this->data->request.setRawHeader(headerName, value);
return std::move(*this);
}
NetworkRequest NetworkRequest::header(const char *headerName,
const QString &value) &&
{
this->data->request.setRawHeader(headerName, value.toUtf8());
return std::move(*this);
}
NetworkRequest NetworkRequest::header(QNetworkRequest::KnownHeaders header,
const QVariant &value) &&
{
this->data->request.setHeader(header, value);
return std::move(*this);
}
NetworkRequest NetworkRequest::headerList(
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&
{
for (const auto &[headerName, headerValue] : headers)
{
this->data->request.setRawHeader(headerName, headerValue);
}
return std::move(*this);
}
NetworkRequest NetworkRequest::timeout(int ms) &&
{
this->data->timeout = std::chrono::milliseconds(ms);
return std::move(*this);
}
NetworkRequest NetworkRequest::concurrent() &&
{
this->data->executeConcurrently = true;
return std::move(*this);
}
NetworkRequest NetworkRequest::multiPart(QHttpMultiPart *payload) &&
{
this->data->multiPartPayload = {payload, {}};
return std::move(*this);
}
NetworkRequest NetworkRequest::followRedirects(bool on) &&
{
if (on)
{
this->data->request.setAttribute(
QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::NoLessSafeRedirectPolicy);
}
else
{
this->data->request.setAttribute(
QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::ManualRedirectPolicy);
}
return std::move(*this);
}
NetworkRequest NetworkRequest::payload(const QByteArray &payload) &&
{
this->data->payload = payload;
return std::move(*this);
}
NetworkRequest NetworkRequest::cache() &&
{
this->data->cache = true;
return std::move(*this);
}
void NetworkRequest::execute()
{
this->executed_ = true;
// Only allow caching for GET request
if (this->data->cache && this->data->requestType != NetworkRequestType::Get)
{
qCDebug(chatterinoCommon) << "Can only cache GET requests!";
this->data->cache = false;
}
// Can not have a caller and be concurrent at the same time.
assert(!(this->data->caller && this->data->executeConcurrently));
load(std::move(this->data));
}
void NetworkRequest::initializeDefaultValues()
{
const auto userAgent = QStringLiteral("chatterino/%1 (%2)")
.arg(Version::instance().version(),
Version::instance().commitHash())
.toUtf8();
this->data->request.setRawHeader("User-Agent", userAgent);
}
NetworkRequest NetworkRequest::json(const QJsonArray &root) &&
{
return std::move(*this).json(QJsonDocument(root));
}
NetworkRequest NetworkRequest::json(const QJsonObject &root) &&
{
return std::move(*this).json(QJsonDocument(root));
}
NetworkRequest NetworkRequest::json(const QJsonDocument &document) &&
{
return std::move(*this).json(document.toJson(QJsonDocument::Compact));
}
NetworkRequest NetworkRequest::json(const QByteArray &payload) &&
{
return std::move(*this)
.payload(payload)
.header(QNetworkRequest::ContentTypeHeader, "application/json")
.header(QNetworkRequest::ContentLengthHeader, payload.length())
.header("Accept", "application/json");
}
} // namespace chatterino
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include "common/network/NetworkCommon.hpp"
#include <QHttpMultiPart>
#include <memory>
class QJsonArray;
class QJsonObject;
class QJsonDocument;
namespace chatterino {
class NetworkData;
class NetworkRequest final
{
// Stores all data about the request that needs to be passed around to each
// part of the request
std::shared_ptr<NetworkData> data;
// The NetworkRequest destructor will assert if executed_ hasn't been set to
// true before dying
bool executed_ = false;
public:
explicit NetworkRequest(
const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(const QUrl &url, NetworkRequestType requestType =
NetworkRequestType::Get);
// Enable move
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
// Disable copy
NetworkRequest(const NetworkRequest &other) = delete;
NetworkRequest &operator=(const NetworkRequest &other) = delete;
~NetworkRequest();
NetworkRequest type(NetworkRequestType newRequestType) &&;
NetworkRequest onError(NetworkErrorCallback cb) &&;
NetworkRequest onSuccess(NetworkSuccessCallback cb) &&;
NetworkRequest finally(NetworkFinallyCallback cb) &&;
NetworkRequest payload(const QByteArray &payload) &&;
NetworkRequest cache() &&;
/// NetworkRequest makes sure that the `caller` object still exists when the
/// callbacks are executed. Cannot be used with concurrent() since we can't
/// make sure that the object doesn't get deleted while the callback is
/// running.
NetworkRequest caller(const QObject *caller) &&;
NetworkRequest header(const char *headerName, const char *value) &&;
NetworkRequest header(const char *headerName, const QByteArray &value) &&;
NetworkRequest header(const char *headerName, const QString &value) &&;
NetworkRequest header(QNetworkRequest::KnownHeaders header,
const QVariant &value) &&;
NetworkRequest headerList(
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&;
NetworkRequest timeout(int ms) &&;
NetworkRequest concurrent() &&;
NetworkRequest multiPart(QHttpMultiPart *payload) &&;
/**
* This will change `RedirectPolicyAttribute`.
* `QNetworkRequest`'s defaults are used by default (Qt 5: no-follow, Qt 6: follow).
*/
NetworkRequest followRedirects(bool on) &&;
NetworkRequest json(const QJsonObject &root) &&;
NetworkRequest json(const QJsonArray &root) &&;
NetworkRequest json(const QJsonDocument &document) &&;
NetworkRequest json(const QByteArray &payload) &&;
void execute();
private:
void initializeDefaultValues();
};
} // namespace chatterino
+85
View File
@@ -0,0 +1,85 @@
#include "common/network/NetworkResult.hpp"
#include "common/QLogging.hpp"
#include <QJsonDocument>
#include <QMetaEnum>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
namespace chatterino {
NetworkResult::NetworkResult(NetworkError error, const QVariant &httpStatusCode,
QByteArray data)
: data_(std::move(data))
, error_(error)
{
if (httpStatusCode.isValid())
{
this->status_ = httpStatusCode.toInt();
}
}
QJsonObject NetworkResult::parseJson() const
{
QJsonDocument jsonDoc(QJsonDocument::fromJson(this->data_));
if (jsonDoc.isNull())
{
return QJsonObject{};
}
return jsonDoc.object();
}
QJsonArray NetworkResult::parseJsonArray() const
{
QJsonDocument jsonDoc(QJsonDocument::fromJson(this->data_));
if (jsonDoc.isNull())
{
return QJsonArray{};
}
return jsonDoc.array();
}
rapidjson::Document NetworkResult::parseRapidJson() const
{
rapidjson::Document ret(rapidjson::kObjectType);
rapidjson::ParseResult result =
ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone)
{
qCWarning(chatterinoCommon)
<< "JSON parse error:" << rapidjson::GetParseError_En(result.Code())
<< "(" << result.Offset() << ")";
return ret;
}
return ret;
}
const QByteArray &NetworkResult::getData() const
{
return this->data_;
}
QString NetworkResult::formatError() const
{
if (this->status_)
{
return QString::number(*this->status_);
}
const auto *name =
QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(
this->error_);
if (name == nullptr)
{
return QStringLiteral("unknown error (%1)").arg(this->error_);
}
return name;
}
} // namespace chatterino
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <QJsonArray>
#include <QJsonObject>
#include <QNetworkReply>
#include <rapidjson/document.h>
#include <optional>
namespace chatterino {
class NetworkResult
{
public:
using NetworkError = QNetworkReply::NetworkError;
NetworkResult(NetworkError error, const QVariant &httpStatusCode,
QByteArray data);
/// Parses the result as json and returns the root as an object.
/// Returns empty object if parsing failed.
QJsonObject parseJson() const;
/// Parses the result as json and returns the root as an array.
/// Returns empty object if parsing failed.
QJsonArray parseJsonArray() const;
/// Parses the result as json and returns the document.
rapidjson::Document parseRapidJson() const;
const QByteArray &getData() const;
/// The error code of the reply.
/// In case of a successful reply, this will be NoError (0)
NetworkError error() const
{
return this->error_;
}
/// The HTTP status code if a response was received.
std::optional<int> status() const
{
return this->status_;
}
/// Formats the error.
/// If a reply is received, returns the HTTP status otherwise, the network error.
QString formatError() const;
private:
QByteArray data_;
NetworkError error_;
std::optional<int> status_;
};
} // namespace chatterino
+3 -3
View File
@@ -1,8 +1,8 @@
#include "common/network/NetworkTask.hpp"
#include "common/NetworkManager.hpp"
#include "common/NetworkPrivate.hpp"
#include "common/NetworkResult.hpp"
#include "common/network/NetworkManager.hpp"
#include "common/network/NetworkPrivate.hpp"
#include "common/network/NetworkResult.hpp"
#include "common/QLogging.hpp"
#include "singletons/Paths.hpp"
#include "util/AbandonObject.hpp"