1 parent
ebf8ff7869
commit
41599fa82f
67 files changed
+8775
-4
No files matched your search
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
// «Фейковый интернет» для тестов сетевых сервисов без правки их кода.
|
||||
//
|
||||
// Сервисы лаунчера ходят на захардкоженные https-адреса через собственные
|
||||
// QNetworkAccessManager. Прокси и SSL-конфигурация по умолчанию у Qt общие
|
||||
// на весь процесс, поэтому start():
|
||||
// - ставит QNetworkProxy::setApplicationProxy() на локальный порт;
|
||||
// - отключает проверку сертификата в QSslConfiguration::defaultConfiguration().
|
||||
// Дальше каждое https-соединение приходит сюда как CONNECT host:443; сокет
|
||||
// переводится в TLS с тестовым самоподписанным сертификатом и обслуживается
|
||||
// как обычный HTTP/1.1 — по таблице маршрутов (host + path).
|
||||
//
|
||||
// Запрос на неизвестный адрес получает 404 и попадает в unmatched(): тест
|
||||
// может проверить, что код не ушёл куда-то, куда не ожидали.
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTcpServer>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <functional>
|
||||
|
||||
class QSslSocket;
|
||||
class QTcpSocket;
|
||||
|
||||
struct FakeRequest
|
||||
{
|
||||
QByteArray method;
|
||||
QString scheme; // https | http
|
||||
QString host;
|
||||
QString path;
|
||||
QString query; // без «?»
|
||||
QHash<QByteArray, QByteArray> headers; // имена в нижнем регистре
|
||||
QByteArray body;
|
||||
|
||||
QUrl url() const;
|
||||
QUrlQuery queryItems() const { return QUrlQuery(query); }
|
||||
QUrlQuery formItems() const { return QUrlQuery(QString::fromUtf8(body)); }
|
||||
QByteArray header(const QByteArray &name) const { return headers.value(name.toLower()); }
|
||||
};
|
||||
|
||||
struct FakeResponse
|
||||
{
|
||||
int status = 200;
|
||||
QByteArray body;
|
||||
QByteArray contentType = "application/octet-stream";
|
||||
QList<QPair<QByteArray, QByteArray>> headers;
|
||||
int delayMs = 0; // ответить с задержкой
|
||||
bool hang = false; // не отвечать вовсе (для отмены и таймаутов)
|
||||
bool drop = false; // оборвать соединение без ответа
|
||||
int truncateTo = -1; // объявить полный размер, отдать столько байт и оборвать
|
||||
|
||||
static FakeResponse json(const QByteArray &body, int status = 200);
|
||||
static FakeResponse json(const class QJsonObject &object, int status = 200);
|
||||
static FakeResponse text(const QByteArray &body, int status = 200);
|
||||
static FakeResponse bytes(const QByteArray &body);
|
||||
static FakeResponse error(int status, const QByteArray &body = {});
|
||||
};
|
||||
|
||||
class FakeInternet : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Handler = std::function<FakeResponse(const FakeRequest &)>;
|
||||
|
||||
explicit FakeInternet(QObject *parent = nullptr);
|
||||
~FakeInternet() override;
|
||||
|
||||
// Слушает 127.0.0.1 и перенаправляет на себя весь трафик процесса.
|
||||
// При ошибке пишет причину в error (например, нет TLS-бэкенда с поддержкой сервера).
|
||||
bool start(QString *error = nullptr);
|
||||
void stop();
|
||||
|
||||
// Есть ли TLS вообще (без него сетевые тесты пропускаются).
|
||||
static bool tlsAvailable(QString *why = nullptr);
|
||||
|
||||
// Маршрут: "https://host/path" — точное совпадение пути (query не учитывается),
|
||||
// "https://host/prefix/*" — всё, что начинается с prefix/.
|
||||
void route(const QString &pattern, const Handler &handler);
|
||||
void route(const QString &pattern, const FakeResponse &response);
|
||||
void unroute(const QString &pattern);
|
||||
|
||||
// Ответ на всё, что не подошло ни к одному маршруту (по умолчанию 404).
|
||||
void setFallback(const FakeResponse &response) { m_fallback = response; }
|
||||
|
||||
// Сбросить маршруты и журнал.
|
||||
void reset();
|
||||
|
||||
QList<FakeRequest> requests() const;
|
||||
// Сколько раз был запрошен адрес "https://host/path" (query не учитывается).
|
||||
int hits(const QString &url) const;
|
||||
QStringList unmatched() const;
|
||||
QStringList requestedUrls() const;
|
||||
|
||||
signals:
|
||||
void requestServed(const QString &url);
|
||||
|
||||
protected:
|
||||
void incomingConnection(qintptr descriptor) override;
|
||||
|
||||
private:
|
||||
struct Connection;
|
||||
struct Route
|
||||
{
|
||||
QString pattern;
|
||||
QString host;
|
||||
QString path;
|
||||
bool prefix = false;
|
||||
Handler handler;
|
||||
};
|
||||
|
||||
void onReadyRead(Connection *connection);
|
||||
void processBuffer(Connection *connection);
|
||||
void dispatch(Connection *connection, FakeRequest request);
|
||||
void respond(QPointer<QTcpSocket> socket, const FakeResponse &response, bool keepAlive);
|
||||
void startTls(Connection *connection);
|
||||
const Route *match(const FakeRequest &request) const;
|
||||
|
||||
QList<Route> m_routes;
|
||||
FakeResponse m_fallback;
|
||||
QList<FakeRequest> m_requests;
|
||||
QStringList m_unmatched;
|
||||
QByteArray m_certificate;
|
||||
QByteArray m_key;
|
||||
bool m_started = false;
|
||||
};
|
||||
Reference in new issue
Block a user