mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
feat: Added local proxy server
This commit is contained in:
@@ -12,7 +12,7 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen")
|
||||
set(PACKAGES
|
||||
Core Gui Network Xml
|
||||
RemoteObjects Quick Svg QuickControls2
|
||||
Core5Compat Concurrent LinguistTools
|
||||
Core5Compat Concurrent LinguistTools HttpServer
|
||||
)
|
||||
|
||||
execute_process(
|
||||
@@ -44,7 +44,7 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core Qt6::Gui
|
||||
Qt6::Network Qt6::Xml Qt6::RemoteObjects
|
||||
Qt6::Quick Qt6::Svg Qt6::QuickControls2
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
Qt6::Core5Compat Qt6::Concurrent Qt6::HttpServer
|
||||
)
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QTranslator>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "core/installedAppsImageProvider.h"
|
||||
@@ -26,11 +27,26 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
|
||||
initNotificationHandler();
|
||||
|
||||
initLocalProxy();
|
||||
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
}
|
||||
|
||||
void CoreController::initLocalProxy()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// Logger and proxy initialization
|
||||
ProxyLogger::getInstance().init(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/logs/proxy.log");
|
||||
ProxyLogger::getInstance().setLogLevel(ProxyLogger::LogLevel::INFO);
|
||||
|
||||
m_proxyServer.reset(new ProxyServer(this));
|
||||
const quint16 proxyPort = 49490;
|
||||
m_proxyServer->start(proxyPort);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::initModels()
|
||||
{
|
||||
m_containersModel.reset(new ContainersModel(this));
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/proxyserver.h"
|
||||
#include "core/local-proxy/proxylogger.h"
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
@@ -73,6 +78,7 @@ private:
|
||||
void initAndroidController();
|
||||
void initAppleController();
|
||||
void initSignalHandlers();
|
||||
void initLocalProxy();
|
||||
|
||||
void initNotificationHandler();
|
||||
|
||||
@@ -145,6 +151,10 @@ private:
|
||||
#endif
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<ProxyServer> m_proxyServer;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // CORECONTROLLER_H
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
#include "configmanager.h"
|
||||
#include "core/serialization/serialization.h"
|
||||
#include "proxylogger.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
|
||||
ConfigManager::ConfigManager()
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Initializing ConfigManager");
|
||||
|
||||
// Create configs directory if it doesn't exist
|
||||
QString configPath = getConfigsPath();
|
||||
ProxyLogger::getInstance().debug(QString("Ensuring config directory exists: %1").arg(configPath));
|
||||
QDir().mkpath(configPath);
|
||||
|
||||
// Read active config UUID and initialize config count
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
m_activeConfigUuid = configsInfo["activeConfigUuid"].toString();
|
||||
m_configCount = configsInfo["configs"].toObject().size();
|
||||
ProxyLogger::getInstance().info(QString("Active config UUID: %1, Total configs: %2")
|
||||
.arg(m_activeConfigUuid.isEmpty() ? "none" : m_activeConfigUuid)
|
||||
.arg(m_configCount));
|
||||
}
|
||||
|
||||
QString ConfigManager::getConfigsPath() const
|
||||
{
|
||||
QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||
return QDir(configDir).filePath("xray_configs");
|
||||
}
|
||||
|
||||
QString ConfigManager::getActiveConfigPath() const
|
||||
{
|
||||
return QDir(getConfigsPath()).filePath("active_config.json");
|
||||
}
|
||||
|
||||
QString ConfigManager::getConfigsInfoPath() const
|
||||
{
|
||||
return QDir(getConfigsPath()).filePath("configs_info.json");
|
||||
}
|
||||
|
||||
QJsonObject ConfigManager::readConfigsInfo() const
|
||||
{
|
||||
QFile file(getConfigsInfoPath());
|
||||
|
||||
if (!file.exists()) {
|
||||
ProxyLogger::getInstance().info("Configs info file doesn't exist, creating new one");
|
||||
// If file doesn't exist, return empty structure
|
||||
QJsonObject configsInfo;
|
||||
configsInfo["version"] = 1;
|
||||
configsInfo["configs"] = QJsonObject();
|
||||
return configsInfo;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to open configs info file: %1").arg(file.errorString()));
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to parse configs info file: %1").arg(parseError.errorString()));
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Successfully read configs info file");
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
bool ConfigManager::writeConfigsInfo(const QJsonObject &configsInfo)
|
||||
{
|
||||
QFile file(getConfigsInfoPath());
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to open configs info file for writing: %1").arg(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_configCount = configsInfo["configs"].toObject().size();
|
||||
ProxyLogger::getInstance().debug(QString("Updated config count: %1").arg(m_configCount));
|
||||
|
||||
QJsonDocument doc(configsInfo);
|
||||
file.write(doc.toJson(QJsonDocument::Indented));
|
||||
file.close();
|
||||
|
||||
ProxyLogger::getInstance().debug("Successfully wrote configs info file");
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject ConfigManager::readActiveConfig() const
|
||||
{
|
||||
QFile file(getActiveConfigPath());
|
||||
|
||||
if (!file.exists()) {
|
||||
ProxyLogger::getInstance().warning(QString("Active config file not found at: %1").arg(getActiveConfigPath()));
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to open active config file: %1").arg(file.errorString()));
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
file.close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to parse active config file: %1").arg(parseError.errorString()));
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Successfully read active config file");
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
bool ConfigManager::writeActiveConfig(const QJsonObject &config)
|
||||
{
|
||||
ProxyLogger::getInstance().info("Writing new active config");
|
||||
QFile file(getActiveConfigPath());
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to open active config file for writing: %1").arg(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc(config);
|
||||
file.write(doc.toJson(QJsonDocument::Indented));
|
||||
file.close();
|
||||
|
||||
ProxyLogger::getInstance().debug("Successfully wrote active config file");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigManager::removeActiveConfigFile()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Removing active config file");
|
||||
QFile file(getActiveConfigPath());
|
||||
|
||||
if (file.exists()) {
|
||||
if (file.remove()) {
|
||||
ProxyLogger::getInstance().debug("Successfully removed active config file");
|
||||
return true;
|
||||
} else {
|
||||
ProxyLogger::getInstance().error(QString("Failed to remove active config file: %1").arg(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Active config file does not exist, nothing to remove");
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ConfigManager::generateUuid() const
|
||||
{
|
||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
QString ConfigManager::getProtocolFromSerializedConfig(const QString &config) const
|
||||
{
|
||||
if (config.startsWith("vless://")) return "vless";
|
||||
if (config.startsWith("vmess://")) return "vmess";
|
||||
if (config.startsWith("trojan://")) return "trojan";
|
||||
if (config.startsWith("ss://")) return "ss";
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJsonObject ConfigManager::deserializeConfig(const QString &configStr, QString *prefix, QString *errorMsg)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Deserializing config");
|
||||
QJsonObject outConfig;
|
||||
|
||||
QString localPrefix;
|
||||
QString localErrorMsg;
|
||||
|
||||
QString* safePrefix = prefix ? prefix : &localPrefix;
|
||||
QString* safeErrorMsg = errorMsg ? errorMsg : &localErrorMsg;
|
||||
|
||||
if (configStr.startsWith("vless://")) {
|
||||
ProxyLogger::getInstance().debug("Deserializing VLESS config");
|
||||
outConfig = amnezia::serialization::vless::Deserialize(configStr, safePrefix, safeErrorMsg);
|
||||
if (safePrefix->contains(QRegularExpression("%[0-9A-Fa-f]{2}"))) {
|
||||
*safePrefix = QString::fromUtf8(QByteArray::fromPercentEncoding(safePrefix->toUtf8()));
|
||||
}
|
||||
}
|
||||
|
||||
if (configStr.startsWith("vmess://") && configStr.contains("@")) {
|
||||
ProxyLogger::getInstance().debug("Deserializing new VMess config");
|
||||
outConfig = amnezia::serialization::vmess_new::Deserialize(configStr, safePrefix, safeErrorMsg);
|
||||
}
|
||||
|
||||
if (configStr.startsWith("vmess://")) {
|
||||
ProxyLogger::getInstance().debug("Deserializing VMess config");
|
||||
outConfig = amnezia::serialization::vmess::Deserialize(configStr, safePrefix, safeErrorMsg);
|
||||
}
|
||||
|
||||
if (configStr.startsWith("trojan://")) {
|
||||
ProxyLogger::getInstance().debug("Deserializing Trojan config");
|
||||
outConfig = amnezia::serialization::trojan::Deserialize(configStr, safePrefix, safeErrorMsg);
|
||||
}
|
||||
|
||||
if (configStr.startsWith("ss://") && !configStr.contains("plugin=")) {
|
||||
ProxyLogger::getInstance().debug("Deserializing Shadowsocks config");
|
||||
outConfig = amnezia::serialization::ss::Deserialize(configStr, safePrefix, safeErrorMsg);
|
||||
if (safePrefix->contains(QRegularExpression("%[0-9A-Fa-f]{2}"))) {
|
||||
*safePrefix = QString::fromUtf8(QByteArray::fromPercentEncoding(safePrefix->toUtf8()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!safeErrorMsg->isEmpty()) {
|
||||
ProxyLogger::getInstance().error(QString("Config deserialization error: %1").arg(*safeErrorMsg));
|
||||
}
|
||||
|
||||
return outConfig;
|
||||
}
|
||||
|
||||
QJsonObject ConfigManager::addInbounds(const QJsonObject &config)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Adding inbounds configuration");
|
||||
QJsonObject resultConfig = config;
|
||||
|
||||
QJsonArray inbounds;
|
||||
QJsonObject socksInbound;
|
||||
socksInbound["listen"] = "127.0.0.1";
|
||||
socksInbound["port"] = 10808;
|
||||
socksInbound["protocol"] = "socks";
|
||||
|
||||
QJsonObject settings;
|
||||
settings["udp"] = true;
|
||||
socksInbound["settings"] = settings;
|
||||
|
||||
inbounds.append(socksInbound);
|
||||
resultConfig["inbounds"] = inbounds;
|
||||
|
||||
ProxyLogger::getInstance().debug("Successfully added SOCKS inbound configuration (port: 10808)");
|
||||
return resultConfig;
|
||||
}
|
||||
|
||||
bool ConfigManager::addConfigs(const QStringList &serializedConfigs)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Adding %1 new config(s)").arg(serializedConfigs.size()));
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
QJsonObject configs = configsInfo["configs"].toObject();
|
||||
QString activeUuid = getActiveConfigUuid();
|
||||
QString firstUuid;
|
||||
|
||||
QSet<QString> existingConfigs;
|
||||
for (auto it = configs.begin(); it != configs.end(); ++it) {
|
||||
QJsonObject configInfo = it.value().toObject();
|
||||
existingConfigs.insert(configInfo["serializedConfig"].toString());
|
||||
}
|
||||
|
||||
for (const QString &serializedConfig : serializedConfigs)
|
||||
{
|
||||
if (existingConfigs.contains(serializedConfig)) {
|
||||
ProxyLogger::getInstance().info("Skipping duplicate config");
|
||||
continue;
|
||||
}
|
||||
|
||||
QString uuid = generateUuid();
|
||||
ProxyLogger::getInstance().debug(QString("Generated new UUID: %1").arg(uuid));
|
||||
|
||||
if (firstUuid.isEmpty()) {
|
||||
firstUuid = uuid;
|
||||
}
|
||||
|
||||
QString prefix;
|
||||
QString errorMsg;
|
||||
ProxyLogger::getInstance().debug(QString("Deserializing config: %1").arg(serializedConfig.left(50) + "..."));
|
||||
QJsonObject config = deserializeConfig(serializedConfig, &prefix, &errorMsg);
|
||||
|
||||
if (!errorMsg.isEmpty()) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to deserialize config: %1").arg(errorMsg));
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject currentConfigInfo;
|
||||
currentConfigInfo["created"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
currentConfigInfo["lastUsed"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
currentConfigInfo["protocol"] = getProtocolFromSerializedConfig(serializedConfig);
|
||||
currentConfigInfo["serializedConfig"] = serializedConfig;
|
||||
currentConfigInfo["name"] = prefix.isEmpty() ? uuid : prefix;
|
||||
currentConfigInfo["isActive"] = false;
|
||||
|
||||
ProxyLogger::getInstance().info(QString("Adding config: UUID=%1, Protocol=%2, Name=%3")
|
||||
.arg(uuid)
|
||||
.arg(currentConfigInfo["protocol"].toString())
|
||||
.arg(currentConfigInfo["name"].toString()));
|
||||
|
||||
configs[uuid] = currentConfigInfo;
|
||||
}
|
||||
|
||||
configsInfo["configs"] = configs;
|
||||
ProxyLogger::getInstance().debug("Writing updated configs info to file");
|
||||
if (!writeConfigsInfo(configsInfo)) {
|
||||
ProxyLogger::getInstance().error("Failed to write configs info");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there's no active config, activate the first added one
|
||||
if (activeUuid.isEmpty() && !firstUuid.isEmpty())
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("No active config, activating first added config: %1").arg(firstUuid));
|
||||
return activateConfig(firstUuid);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigManager::removeConfig(const QString &uuid)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Removing config with UUID: %1").arg(uuid));
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
QJsonObject configs = configsInfo["configs"].toObject();
|
||||
|
||||
// Check if config exists
|
||||
if (!configs.contains(uuid))
|
||||
{
|
||||
ProxyLogger::getInstance().warning(QString("Config with UUID %1 not found").arg(uuid));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject configToRemove = configs[uuid].toObject();
|
||||
ProxyLogger::getInstance().info(QString("Removing config: Name=%1, Protocol=%2")
|
||||
.arg(configToRemove["name"].toString())
|
||||
.arg(configToRemove["protocol"].toString()));
|
||||
|
||||
// Store current active config UUID
|
||||
bool needToActivateNew = (getActiveConfigUuid() == uuid);
|
||||
|
||||
// Remove config from the list
|
||||
configs.remove(uuid);
|
||||
|
||||
// Save updated configs list (without changing activeConfigUuid)
|
||||
configsInfo["configs"] = configs;
|
||||
ProxyLogger::getInstance().debug("Writing updated configs info to file");
|
||||
if (!writeConfigsInfo(configsInfo))
|
||||
{
|
||||
ProxyLogger::getInstance().error("Failed to write configs info");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If active config was removed, activate a new one
|
||||
if (needToActivateNew)
|
||||
{
|
||||
ProxyLogger::getInstance().info("Removed active config, need to activate a new one");
|
||||
if (configs.isEmpty())
|
||||
{
|
||||
ProxyLogger::getInstance().info("No configs left, clearing active config");
|
||||
if (!activateConfig(QString()))
|
||||
{
|
||||
ProxyLogger::getInstance().error("Failed to clear active config");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QString newActiveUuid = configs.keys().first();
|
||||
ProxyLogger::getInstance().info(QString("Activating new config: %1").arg(newActiveUuid));
|
||||
if (!activateConfig(newActiveUuid))
|
||||
{
|
||||
ProxyLogger::getInstance().error(QString("Failed to activate new config: %1").arg(newActiveUuid));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigManager::activateConfig(const QString &uuid)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Activating config: %1").arg(uuid.isEmpty() ? "none" : uuid));
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
QJsonObject configs = configsInfo["configs"].toObject();
|
||||
|
||||
// Reset isActive flag for all configs
|
||||
for (auto it = configs.begin(); it != configs.end(); ++it) {
|
||||
QJsonObject config = it.value().toObject();
|
||||
config["isActive"] = false;
|
||||
it.value() = config;
|
||||
}
|
||||
|
||||
// If uuid is empty, just reset active config
|
||||
if (uuid.isEmpty())
|
||||
{
|
||||
ProxyLogger::getInstance().info("Resetting active config");
|
||||
m_activeConfigUuid = QString();
|
||||
configsInfo["activeConfigUuid"] = QString();
|
||||
configsInfo["configs"] = configs;
|
||||
|
||||
// Write changes to the configs info file
|
||||
if (!writeConfigsInfo(configsInfo)) {
|
||||
ProxyLogger::getInstance().error("Failed to write configs info file");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove the active config file
|
||||
ProxyLogger::getInstance().debug("Removing active config file");
|
||||
return removeActiveConfigFile();
|
||||
}
|
||||
|
||||
// Check if config exists
|
||||
if (!configs.contains(uuid))
|
||||
{
|
||||
ProxyLogger::getInstance().error(QString("Config with UUID %1 not found").arg(uuid));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject currentConfigInfo = configs[uuid].toObject();
|
||||
ProxyLogger::getInstance().info(QString("Activating config: Name=%1, Protocol=%2")
|
||||
.arg(currentConfigInfo["name"].toString())
|
||||
.arg(currentConfigInfo["protocol"].toString()));
|
||||
|
||||
QString serializedConfig = currentConfigInfo["serializedConfig"].toString();
|
||||
|
||||
// Deserialize config and add inbounds
|
||||
QString prefix;
|
||||
QString errorMsg;
|
||||
ProxyLogger::getInstance().debug("Deserializing config for activation");
|
||||
QJsonObject currentConfig = deserializeConfig(serializedConfig, &prefix, &errorMsg);
|
||||
if (currentConfig.isEmpty())
|
||||
{
|
||||
ProxyLogger::getInstance().error(QString("Failed to deserialize config: %1").arg(errorMsg));
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Adding inbounds to config");
|
||||
currentConfig = addInbounds(currentConfig);
|
||||
|
||||
// Update lastUsed and isActive
|
||||
currentConfigInfo["lastUsed"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
currentConfigInfo["isActive"] = true;
|
||||
configs[uuid] = currentConfigInfo;
|
||||
configsInfo["configs"] = configs;
|
||||
|
||||
// Update active config
|
||||
m_activeConfigUuid = uuid;
|
||||
configsInfo["activeConfigUuid"] = uuid;
|
||||
|
||||
// Save changes
|
||||
ProxyLogger::getInstance().debug("Writing updated configs info");
|
||||
if (!writeConfigsInfo(configsInfo))
|
||||
{
|
||||
ProxyLogger::getInstance().error("Failed to write configs info file");
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Writing new active config file");
|
||||
return writeActiveConfig(currentConfig);
|
||||
}
|
||||
|
||||
QJsonObject ConfigManager::getActiveConfig() const
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Getting active config info");
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
QJsonObject configs = configsInfo["configs"].toObject();
|
||||
QString activeUuid = getActiveConfigUuid();
|
||||
|
||||
if (activeUuid.isEmpty() || !configs.contains(activeUuid)) {
|
||||
ProxyLogger::getInstance().debug("No active config found");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug(QString("Retrieved active config info for UUID: %1").arg(activeUuid));
|
||||
QJsonObject result = configs[activeUuid].toObject();
|
||||
result["id"] = activeUuid;
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> ConfigManager::getAllConfigs() const
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Getting all configs");
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
QJsonObject configs = configsInfo["configs"].toObject();
|
||||
|
||||
QMap<QString, QJsonObject> result;
|
||||
for (auto it = configs.begin(); it != configs.end(); ++it)
|
||||
{
|
||||
result[it.key()] = it.value().toObject();
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug(QString("Retrieved %1 configs").arg(result.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> ConfigManager::getConfigsByUuids(const QStringList &uuids) const
|
||||
{
|
||||
ProxyLogger::getInstance().debug(QString("Getting configs for %1 UUIDs").arg(uuids.size()));
|
||||
QMap<QString, QJsonObject> allConfigs = getAllConfigs();
|
||||
if (uuids.isEmpty())
|
||||
{
|
||||
ProxyLogger::getInstance().debug("UUID list is empty, returning all configs");
|
||||
return allConfigs;
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> result;
|
||||
for (const QString &uuid : uuids)
|
||||
{
|
||||
if (allConfigs.contains(uuid))
|
||||
{
|
||||
ProxyLogger::getInstance().debug(QString("Found config for UUID: %1").arg(uuid));
|
||||
result[uuid] = allConfigs[uuid];
|
||||
}
|
||||
else
|
||||
{
|
||||
ProxyLogger::getInstance().warning(QString("Config not found for UUID: %1").arg(uuid));
|
||||
result[uuid] = QJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug(QString("Retrieved %1 configs out of %2 requested").arg(result.size()).arg(uuids.size()));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ConfigManager::updateAllConfigs(const QStringList &serializedConfigs)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Updating all configs with %1 new config(s)").arg(serializedConfigs.size()));
|
||||
|
||||
ProxyLogger::getInstance().debug("Clearing existing configs");
|
||||
if (!clearConfigs()) {
|
||||
ProxyLogger::getInstance().error("Failed to clear existing configs");
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Adding new configs");
|
||||
bool success = addConfigs(serializedConfigs);
|
||||
if (success) {
|
||||
ProxyLogger::getInstance().info("Successfully updated all configs");
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to add new configs");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ConfigManager::clearConfigs()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Clearing all configs");
|
||||
QJsonObject configsInfo = readConfigsInfo();
|
||||
configsInfo["configs"] = QJsonObject();
|
||||
|
||||
if (!writeConfigsInfo(configsInfo)) {
|
||||
ProxyLogger::getInstance().error("Failed to clear configs info");
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug("Resetting active config");
|
||||
bool success = activateConfig(QString());
|
||||
if (success) {
|
||||
ProxyLogger::getInstance().info("Successfully cleared all configs");
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to reset active config");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
#include <QMap>
|
||||
|
||||
class ConfigManager {
|
||||
public:
|
||||
ConfigManager();
|
||||
|
||||
// Main config operations
|
||||
bool addConfigs(const QStringList &serializedConfigs);
|
||||
bool removeConfig(const QString &uuid);
|
||||
bool activateConfig(const QString &uuid);
|
||||
bool updateAllConfigs(const QStringList &serializedConfigs);
|
||||
bool clearConfigs();
|
||||
|
||||
// Information retrieval
|
||||
QString getActiveConfigUuid() const { return m_activeConfigUuid; }
|
||||
QJsonObject getActiveConfig() const;
|
||||
QMap<QString, QJsonObject> getAllConfigs() const;
|
||||
QMap<QString, QJsonObject> getConfigsByUuids(const QStringList &uuids) const;
|
||||
QString getActiveConfigPath() const;
|
||||
int getConfigCount() const { return m_configCount; }
|
||||
|
||||
private:
|
||||
// File paths
|
||||
QString getConfigsPath() const;
|
||||
QString getConfigsInfoPath() const;
|
||||
|
||||
// File operations
|
||||
QJsonObject readConfigsInfo() const;
|
||||
bool writeConfigsInfo(const QJsonObject &configsInfo);
|
||||
QJsonObject readActiveConfig() const;
|
||||
bool writeActiveConfig(const QJsonObject &config);
|
||||
bool removeActiveConfigFile();
|
||||
|
||||
// Helper methods
|
||||
QString generateUuid() const;
|
||||
QString getProtocolFromSerializedConfig(const QString &config) const;
|
||||
QJsonObject deserializeConfig(const QString &configStr, QString *prefix = nullptr, QString *errorMsg = nullptr);
|
||||
QJsonObject addInbounds(const QJsonObject &config);
|
||||
|
||||
QString m_activeConfigUuid;
|
||||
int m_configCount{0};
|
||||
};
|
||||
@@ -0,0 +1,576 @@
|
||||
#include "httpapi.h"
|
||||
#include "proxylogger.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QDateTime>
|
||||
#include <QHostAddress>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
HttpApi::HttpApi(QWeakPointer<IProxyService> service, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_tcpServer(new QTcpServer(this))
|
||||
, m_service(service)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("HttpApi initialized");
|
||||
}
|
||||
|
||||
HttpApi::~HttpApi()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool HttpApi::start(quint16 port)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Starting HTTP API server on port %1").arg(port));
|
||||
|
||||
if (!m_tcpServer->listen(QHostAddress::LocalHost, port)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to start HTTP API server on port %1").arg(port));
|
||||
return false;
|
||||
}
|
||||
|
||||
setupRoutes();
|
||||
m_server.bind(m_tcpServer.data());
|
||||
|
||||
ProxyLogger::getInstance().info(QString("HTTP API server is running on localhost:%1").arg(m_tcpServer->serverPort()));
|
||||
ProxyLogger::getInstance().debug("Available endpoints:\n"
|
||||
" GET /api/v1/configs\n"
|
||||
" POST /api/v1/configs\n"
|
||||
" PUT /api/v1/configs\n"
|
||||
" DELETE /api/v1/configs\n"
|
||||
" PUT /api/v1/configs/activate\n"
|
||||
" GET /api/v1/configs/active\n"
|
||||
" POST /api/v1/up\n"
|
||||
" POST /api/v1/down\n"
|
||||
" GET /api/v1/ping");
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpApi::stop()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Stopping HTTP API server");
|
||||
if (m_tcpServer) {
|
||||
m_tcpServer->close();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpApi::setupRoutes()
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Setting up HTTP API routes");
|
||||
|
||||
// Config management routes
|
||||
m_server.route("/api/v1/configs", QHttpServerRequest::Method::Get,
|
||||
[this](const QHttpServerRequest &request) {
|
||||
ProxyLogger::getInstance().debug("Handling GET /api/v1/configs request");
|
||||
return handleGetConfigs(request);
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/configs", QHttpServerRequest::Method::Post,
|
||||
[this](const QHttpServerRequest &request) {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/configs request");
|
||||
return handleAddConfigs(request);
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/configs", QHttpServerRequest::Method::Put,
|
||||
[this](const QHttpServerRequest &request) {
|
||||
ProxyLogger::getInstance().debug("Handling PUT /api/v1/configs request");
|
||||
return handleUpdateConfigs(request);
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/configs", QHttpServerRequest::Method::Delete,
|
||||
[this](const QHttpServerRequest &request) {
|
||||
ProxyLogger::getInstance().debug("Handling DELETE /api/v1/configs request");
|
||||
return handleDeleteConfig(request);
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/configs/activate", QHttpServerRequest::Method::Put,
|
||||
[this](const QHttpServerRequest &request) {
|
||||
ProxyLogger::getInstance().debug("Handling PUT /api/v1/configs/activate request");
|
||||
return handleActivateConfig(request);
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/configs/active", QHttpServerRequest::Method::Get,
|
||||
[this](const QHttpServerRequest &request) {
|
||||
ProxyLogger::getInstance().debug("Handling GET /api/v1/configs/active request");
|
||||
return handleGetActiveConfig(request);
|
||||
});
|
||||
|
||||
// Xray control routes
|
||||
m_server.route("/api/v1/up", QHttpServerRequest::Method::Post,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/up request");
|
||||
return handlePostUp();
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/down", QHttpServerRequest::Method::Post,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/down request");
|
||||
return handlePostDown();
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/ping", QHttpServerRequest::Method::Get,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling GET /api/v1/ping request");
|
||||
return handleGetPing();
|
||||
});
|
||||
}
|
||||
|
||||
QJsonObject HttpApi::handlePostUp()
|
||||
{
|
||||
QJsonObject response;
|
||||
|
||||
if (auto service = m_service.lock()) {
|
||||
if (service->startXray()) {
|
||||
ProxyLogger::getInstance().info("Xray process started successfully");
|
||||
response["status"] = "success";
|
||||
response["message"] = "Xray process started successfully";
|
||||
|
||||
// Try to get port from inbounds configuration
|
||||
QJsonObject config = service->getConfig();
|
||||
if (config.contains("inbounds") && config["inbounds"].isArray()) {
|
||||
QJsonArray inbounds = config["inbounds"].toArray();
|
||||
if (!inbounds.isEmpty() && inbounds[0].isObject()) {
|
||||
QJsonObject firstInbound = inbounds[0].toObject();
|
||||
if (firstInbound.contains("port")) {
|
||||
int port = firstInbound["port"].toInt();
|
||||
ProxyLogger::getInstance().info(QString("Xray listening on port %1").arg(port));
|
||||
response["xray_port"] = port;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to start Xray process");
|
||||
response["status"] = "error";
|
||||
response["message"] = "Failed to start xray process";
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Service unavailable while trying to start Xray");
|
||||
response["status"] = "error";
|
||||
response["message"] = "Service unavailable";
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
QJsonObject HttpApi::handlePostDown()
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
service->stopXray();
|
||||
QJsonObject response;
|
||||
response["status"] = "success";
|
||||
response["message"] = "Xray process stopped";
|
||||
return response;
|
||||
}
|
||||
return { {"status", "error"}, {"message", "Service unavailable"} };
|
||||
}
|
||||
|
||||
QJsonObject HttpApi::handleGetPing() const
|
||||
{
|
||||
QJsonObject response;
|
||||
response["status"] = "success";
|
||||
response["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
|
||||
if (auto service = m_service.lock()) {
|
||||
bool isRunning = service->isXrayRunning();
|
||||
response["xray_running"] = isRunning;
|
||||
response["config_count"] = service->getConfigCount();
|
||||
|
||||
if (isRunning) {
|
||||
qint64 pid = service->getXrayProcessId();
|
||||
ProxyLogger::getInstance().debug(QString("Xray is running (PID: %1)").arg(pid));
|
||||
response["xray_pid"] = pid;
|
||||
response["xray_state"] = "running";
|
||||
|
||||
QJsonObject config = service->getConfig();
|
||||
if (config.contains("inbounds") && config["inbounds"].isArray()) {
|
||||
QJsonArray inbounds = config["inbounds"].toArray();
|
||||
if (!inbounds.isEmpty() && inbounds[0].isObject()) {
|
||||
QJsonObject firstInbound = inbounds[0].toObject();
|
||||
if (firstInbound.contains("port")) {
|
||||
int port = firstInbound["port"].toInt();
|
||||
ProxyLogger::getInstance().debug(QString("Xray port: %1").arg(port));
|
||||
response["xray_port"] = port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString error = service->getXrayError();
|
||||
if (!error.isEmpty()) {
|
||||
ProxyLogger::getInstance().warning(QString("Xray error: %1").arg(error));
|
||||
response["xray_error"] = error;
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().debug("Xray is not running");
|
||||
response["xray_state"] = "stopped";
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing GET /api/v1/ping request");
|
||||
response["status"] = "error";
|
||||
response["message"] = "Service unavailable";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleGetConfigs(const QHttpServerRequest &request)
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
// Get UUIDs from query parameters if present and decode URL-encoded characters
|
||||
QString uuidList = QUrl::fromPercentEncoding(request.query().queryItemValue("uuid").toUtf8());
|
||||
ProxyLogger::getInstance().debug(QString("UUID filter: %1").arg(uuidList.isEmpty() ? "none" : uuidList));
|
||||
|
||||
QMap<QString, QJsonObject> configs;
|
||||
|
||||
if (uuidList.isEmpty()) {
|
||||
ProxyLogger::getInstance().debug("Retrieving all configs");
|
||||
configs = service->getAllConfigs();
|
||||
} else {
|
||||
QStringList uuids = uuidList.split(',', Qt::SkipEmptyParts);
|
||||
ProxyLogger::getInstance().debug(QString("Retrieving configs for UUIDs: %1").arg(uuids.join(", ")));
|
||||
configs = service->getConfigsByUuids(uuids);
|
||||
}
|
||||
|
||||
QJsonObject response;
|
||||
response["status"] = "success";
|
||||
|
||||
// Convert QMap to QJsonObject manually
|
||||
QJsonObject configsJson;
|
||||
for (auto it = configs.constBegin(); it != configs.constEnd(); ++it) {
|
||||
configsJson[it.key()] = it.value();
|
||||
}
|
||||
response["configs"] = configsJson;
|
||||
|
||||
ProxyLogger::getInstance().info(QString("Successfully retrieved %1 configs").arg(configs.size()));
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing GET /api/v1/configs request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{{"status", "error"}, {"message", "Service unavailable"}},
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable
|
||||
);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleAddConfigs(const QHttpServerRequest &request)
|
||||
{
|
||||
ProxyLogger::getInstance().info("Processing POST /api/v1/configs request");
|
||||
|
||||
if (auto service = m_service.lock()) {
|
||||
// Parse request body
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
ProxyLogger::getInstance().error(QString("Invalid JSON format: %1").arg(parseError.errorString()));
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Invalid JSON format"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
if (!doc.isObject()) {
|
||||
ProxyLogger::getInstance().error("Request body is not a JSON object");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Request body must be a JSON object"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
QJsonObject root = doc.object();
|
||||
if (!root.contains("configs") || !root["configs"].isArray()) {
|
||||
ProxyLogger::getInstance().error("Request body missing 'configs' array");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Request body must contain 'configs' array"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
// Convert JSON array to string list
|
||||
QStringList configs;
|
||||
QJsonArray configsArray = root["configs"].toArray();
|
||||
ProxyLogger::getInstance().debug(QString("Processing %1 configs from request").arg(configsArray.size()));
|
||||
|
||||
for (const auto &value : configsArray) {
|
||||
if (!value.isString()) {
|
||||
ProxyLogger::getInstance().error("Invalid config format: config must be a string");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "All configs must be strings"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
configs.append(value.toString());
|
||||
}
|
||||
|
||||
if (configs.isEmpty()) {
|
||||
ProxyLogger::getInstance().error("Empty configs array in request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Configs array cannot be empty"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
// Add configs
|
||||
ProxyLogger::getInstance().info(QString("Adding %1 configs").arg(configs.size()));
|
||||
if (service->addConfigs(configs)) {
|
||||
ProxyLogger::getInstance().info("Successfully added configs");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "success"},
|
||||
{"message", "Configs added successfully"}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to add configs");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Failed to add configs"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::InternalServerError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing POST /api/v1/configs request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{{"status", "error"}, {"message", "Service unavailable"}},
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable
|
||||
);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleUpdateConfigs(const QHttpServerRequest &request)
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
// Parse request body
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(request.body(), &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
ProxyLogger::getInstance().error(QString("Invalid JSON format: %1").arg(parseError.errorString()));
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Invalid JSON format"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
if (!doc.isObject()) {
|
||||
ProxyLogger::getInstance().error("Request body is not a JSON object");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Request body must be a JSON object"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
QJsonObject root = doc.object();
|
||||
if (!root.contains("configs") || !root["configs"].isArray()) {
|
||||
ProxyLogger::getInstance().error("Request body missing 'configs' array");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Request body must contain 'configs' array"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
// Convert JSON array to string list
|
||||
QStringList configs;
|
||||
QJsonArray configsArray = root["configs"].toArray();
|
||||
ProxyLogger::getInstance().debug(QString("Processing %1 configs from request").arg(configsArray.size()));
|
||||
|
||||
for (const auto &value : configsArray) {
|
||||
if (!value.isString()) {
|
||||
ProxyLogger::getInstance().error("Invalid config format: config must be a string");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "All configs must be strings"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
configs.append(value.toString());
|
||||
}
|
||||
|
||||
if (configs.isEmpty()) {
|
||||
ProxyLogger::getInstance().error("Empty configs array in request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Configs array cannot be empty"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
// Update configs
|
||||
ProxyLogger::getInstance().info(QString("Updating all configs with %1 new config(s)").arg(configs.size()));
|
||||
if (service->updateAllConfigs(configs)) {
|
||||
ProxyLogger::getInstance().info("Successfully updated all configs");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "success"},
|
||||
{"message", "Configs updated successfully"}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to update configs");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Failed to update configs"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::InternalServerError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing PUT /api/v1/configs request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{{"status", "error"}, {"message", "Service unavailable"}},
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable
|
||||
);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleDeleteConfig(const QHttpServerRequest &request)
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
// Get UUID from query parameters
|
||||
QString uuid = request.query().queryItemValue("uuid");
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
ProxyLogger::getInstance().error("Missing UUID parameter in request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "UUID parameter is required"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info(QString("Attempting to delete config with UUID: %1").arg(uuid));
|
||||
// Delete config
|
||||
if (service->removeConfig(uuid)) {
|
||||
ProxyLogger::getInstance().info(QString("Successfully deleted config with UUID: %1").arg(uuid));
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "success"},
|
||||
{"message", "Config deleted successfully"}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
ProxyLogger::getInstance().error(QString("Failed to delete config with UUID: %1").arg(uuid));
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Failed to delete config"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::InternalServerError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing DELETE /api/v1/configs request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{{"status", "error"}, {"message", "Service unavailable"}},
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable
|
||||
);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleActivateConfig(const QHttpServerRequest &request)
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
// Get UUID from query parameters
|
||||
QString uuid = request.query().queryItemValue("uuid");
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
ProxyLogger::getInstance().error("Missing UUID parameter in request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "UUID parameter is required"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::BadRequest
|
||||
);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info(QString("Attempting to activate config with UUID: %1").arg(uuid));
|
||||
// Activate config
|
||||
if (service->activateConfig(uuid)) {
|
||||
ProxyLogger::getInstance().info(QString("Successfully activated config with UUID: %1").arg(uuid));
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "success"},
|
||||
{"message", "Config activated successfully"}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
ProxyLogger::getInstance().error(QString("Failed to activate config with UUID: %1").arg(uuid));
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "Failed to activate config"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::InternalServerError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing PUT /api/v1/configs/activate request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{{"status", "error"}, {"message", "Service unavailable"}},
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable
|
||||
);
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleGetActiveConfig(const QHttpServerRequest &request)
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
QJsonObject activeConfig = service->getActiveConfig();
|
||||
|
||||
if (!activeConfig.isEmpty()) {
|
||||
ProxyLogger::getInstance().info("Successfully retrieved active config");
|
||||
QJsonObject response;
|
||||
response["status"] = "success";
|
||||
response["config"] = activeConfig;
|
||||
return QHttpServerResponse(response);
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("No active config found");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{
|
||||
{"status", "error"},
|
||||
{"message", "No active config found"}
|
||||
},
|
||||
QHttpServerResponse::StatusCode::NotFound
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable while processing GET /api/v1/configs/active request");
|
||||
return QHttpServerResponse(
|
||||
QJsonObject{{"status", "error"}, {"message", "Service unavailable"}},
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerRequest>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QTcpServer>
|
||||
#include <QWeakPointer>
|
||||
#include "iproxyservice.h"
|
||||
|
||||
class HttpApi : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpApi(QWeakPointer<IProxyService> service, QObject* parent = nullptr);
|
||||
~HttpApi();
|
||||
|
||||
bool start(quint16 port);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void setupRoutes();
|
||||
|
||||
// Config management endpoints
|
||||
QHttpServerResponse handleGetConfigs(const QHttpServerRequest &request);
|
||||
QHttpServerResponse handleAddConfigs(const QHttpServerRequest &request);
|
||||
QHttpServerResponse handleUpdateConfigs(const QHttpServerRequest &request);
|
||||
QHttpServerResponse handleDeleteConfig(const QHttpServerRequest &request);
|
||||
QHttpServerResponse handleActivateConfig(const QHttpServerRequest &request);
|
||||
QHttpServerResponse handleGetActiveConfig(const QHttpServerRequest &request);
|
||||
|
||||
// Xray control endpoints
|
||||
QJsonObject handlePostUp();
|
||||
QJsonObject handlePostDown();
|
||||
QJsonObject handleGetPing() const;
|
||||
|
||||
QHttpServer m_server;
|
||||
QScopedPointer<QTcpServer> m_tcpServer;
|
||||
QWeakPointer<IProxyService> m_service;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
|
||||
class IProxyService {
|
||||
public:
|
||||
virtual ~IProxyService() = default;
|
||||
|
||||
// Config operations
|
||||
virtual QJsonObject getConfig() const = 0;
|
||||
virtual bool updateConfig(const QString& configStr) = 0;
|
||||
virtual QMap<QString, QJsonObject> getAllConfigs() const = 0;
|
||||
virtual QMap<QString, QJsonObject> getConfigsByUuids(const QStringList &uuids) const = 0;
|
||||
virtual bool addConfigs(const QStringList &serializedConfigs) = 0;
|
||||
virtual bool removeConfig(const QString &uuid) = 0;
|
||||
virtual bool activateConfig(const QString &uuid) = 0;
|
||||
virtual QJsonObject getActiveConfig() const = 0;
|
||||
virtual bool updateAllConfigs(const QStringList &serializedConfigs) = 0;
|
||||
virtual int getConfigCount() const = 0;
|
||||
|
||||
// Xray process operations
|
||||
virtual bool startXray() = 0;
|
||||
virtual bool stopXray() = 0;
|
||||
virtual bool isXrayRunning() const = 0;
|
||||
virtual qint64 getXrayProcessId() const = 0;
|
||||
virtual QString getXrayError() const = 0;
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "proxylogger.h"
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
|
||||
ProxyLogger::ProxyLogger() : m_maxFileSize(0), m_currentLevel(LogLevel::INFO)
|
||||
{
|
||||
}
|
||||
|
||||
ProxyLogger::~ProxyLogger()
|
||||
{
|
||||
}
|
||||
|
||||
ProxyLogger& ProxyLogger::getInstance()
|
||||
{
|
||||
static ProxyLogger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ProxyLogger::init(const QString& logPath, qint64 maxFileSize)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_logPath = logPath;
|
||||
m_maxFileSize = maxFileSize;
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
QDir dir = QFileInfo(m_logPath).dir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyLogger::setLogLevel(LogLevel level)
|
||||
{
|
||||
m_currentLevel = level;
|
||||
}
|
||||
|
||||
void ProxyLogger::log(LogLevel level, const QString& message)
|
||||
{
|
||||
logInternal(level, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::debug(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::DEBUG, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::info(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::INFO, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::warning(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::WARNING, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::error(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::ERROR, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::logInternal(LogLevel level, const QString& message)
|
||||
{
|
||||
if (level < m_currentLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
checkRotation();
|
||||
|
||||
QFile file(m_logPath);
|
||||
if (!openLogFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
|
||||
stream << QString("[%1] [%2] %3\n").arg(timestamp, levelToString(level), message);
|
||||
stream.flush();
|
||||
file.close();
|
||||
}
|
||||
|
||||
QString ProxyLogger::levelToString(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::DEBUG: return "DEBUG";
|
||||
case LogLevel::INFO: return "INFO";
|
||||
case LogLevel::WARNING: return "WARNING";
|
||||
case LogLevel::ERROR: return "ERROR";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
qint64 ProxyLogger::getCurrentFileSize() const
|
||||
{
|
||||
QFile file(m_logPath);
|
||||
if (file.exists()) {
|
||||
return file.size();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ProxyLogger::checkRotation()
|
||||
{
|
||||
if (m_maxFileSize > 0 && getCurrentFileSize() >= m_maxFileSize) {
|
||||
// Delete the oldest file
|
||||
QFile::remove(QString("%1.%2").arg(m_logPath).arg(MAX_BACKUP_FILES));
|
||||
|
||||
// Shift existing files
|
||||
for (int i = MAX_BACKUP_FILES - 1; i >= 1; --i) {
|
||||
QString oldName = QString("%1.%2").arg(m_logPath).arg(i);
|
||||
QString newName = QString("%1.%2").arg(m_logPath).arg(i + 1);
|
||||
QFile::rename(oldName, newName);
|
||||
}
|
||||
|
||||
// Rename current file
|
||||
QFile::rename(m_logPath, m_logPath + ".1");
|
||||
}
|
||||
}
|
||||
|
||||
bool ProxyLogger::openLogFile(QFile& file)
|
||||
{
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
qDebug() << "Failed to open log file:" << m_logPath;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef LOCAL_PROXY_LOGGER_H
|
||||
#define LOCAL_PROXY_LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
class ProxyLogger
|
||||
{
|
||||
|
||||
public:
|
||||
enum class LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR
|
||||
};
|
||||
|
||||
static ProxyLogger& getInstance();
|
||||
|
||||
void init(const QString& logPath, qint64 maxFileSize = 1024 * 1024 * 10); // 10MB by default
|
||||
void setLogLevel(LogLevel level);
|
||||
|
||||
// Main logging method
|
||||
void log(LogLevel level, const QString& message);
|
||||
|
||||
// Helper methods for convenience
|
||||
void debug(const QString& message);
|
||||
void info(const QString& message);
|
||||
void warning(const QString& message);
|
||||
void error(const QString& message);
|
||||
|
||||
private:
|
||||
ProxyLogger();
|
||||
~ProxyLogger();
|
||||
ProxyLogger(const ProxyLogger&) = delete;
|
||||
ProxyLogger& operator=(const ProxyLogger&) = delete;
|
||||
|
||||
void logInternal(LogLevel level, const QString& message);
|
||||
void checkRotation();
|
||||
QString levelToString(LogLevel level);
|
||||
bool openLogFile(QFile& file);
|
||||
qint64 getCurrentFileSize() const;
|
||||
|
||||
QString m_logPath;
|
||||
qint64 m_maxFileSize;
|
||||
LogLevel m_currentLevel;
|
||||
QMutex m_mutex;
|
||||
static const int MAX_BACKUP_FILES = 3;
|
||||
};
|
||||
|
||||
#endif // LOCAL_PROXY_LOGGER_H
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "proxyserver.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
ProxyServer::ProxyServer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_service(new ProxyService(this))
|
||||
{
|
||||
connect(m_service.data(), &ProxyService::configsChanged, this, &ProxyServer::startXrayProcess);
|
||||
}
|
||||
|
||||
ProxyServer::~ProxyServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool ProxyServer::start(quint16 port)
|
||||
{
|
||||
m_api.reset(new HttpApi(m_service.toWeakRef()));
|
||||
bool apiStarted = m_api->start(port);
|
||||
if (!apiStarted) {
|
||||
qWarning() << "Local proxy: port is busy:" << port;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Auto-start Xray if config exists
|
||||
QJsonObject config = m_service->getConfig();
|
||||
if (!config.isEmpty()) {
|
||||
startXrayProcess();
|
||||
} else {
|
||||
qDebug() << "No config found, Xray will not start automatically";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxyServer::stop()
|
||||
{
|
||||
stopXrayProcess();
|
||||
if (m_api) {
|
||||
m_api->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool ProxyServer::startXrayProcess()
|
||||
{
|
||||
return m_service->startXray();
|
||||
}
|
||||
|
||||
void ProxyServer::stopXrayProcess()
|
||||
{
|
||||
m_service->stopXray();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QSharedPointer>
|
||||
#include "proxyservice.h"
|
||||
#include "httpapi.h"
|
||||
|
||||
class ProxyServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProxyServer(QObject *parent = nullptr);
|
||||
~ProxyServer();
|
||||
|
||||
bool start(quint16 port = 8080);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
bool startXrayProcess();
|
||||
void stopXrayProcess();
|
||||
|
||||
QScopedPointer<HttpApi> m_api;
|
||||
QSharedPointer<ProxyService> m_service;
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "proxyservice.h"
|
||||
#include "proxylogger.h"
|
||||
|
||||
ProxyService::ProxyService(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_configManager(new ConfigManager())
|
||||
, m_xrayController(new XrayController())
|
||||
{
|
||||
ProxyLogger::getInstance().debug("ProxyService initialized");
|
||||
}
|
||||
|
||||
QJsonObject ProxyService::getConfig() const
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Getting active config");
|
||||
return m_configManager->getActiveConfig();
|
||||
}
|
||||
|
||||
bool ProxyService::updateConfig(const QString& configStr)
|
||||
{
|
||||
ProxyLogger::getInstance().info("Updating config");
|
||||
bool success = m_configManager->updateAllConfigs({configStr});
|
||||
if (success) {
|
||||
ProxyLogger::getInstance().info("Config updated successfully");
|
||||
emit configsChanged();
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to update config");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ProxyService::startXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Starting Xray");
|
||||
auto activeConfig = m_configManager->getActiveConfigPath();
|
||||
qDebug() << activeConfig;
|
||||
bool success = m_xrayController->start(m_configManager->getActiveConfigPath());
|
||||
if (success) {
|
||||
ProxyLogger::getInstance().info("Xray started successfully");
|
||||
emit xrayStatusChanged(true);
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to start Xray");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ProxyService::stopXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Stopping Xray");
|
||||
m_xrayController->stop();
|
||||
ProxyLogger::getInstance().info("Xray stopped");
|
||||
emit xrayStatusChanged(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProxyService::isXrayRunning() const
|
||||
{
|
||||
return m_xrayController->isXrayRunning();
|
||||
}
|
||||
|
||||
qint64 ProxyService::getXrayProcessId() const
|
||||
{
|
||||
return m_xrayController->getProcessId();
|
||||
}
|
||||
|
||||
QString ProxyService::getXrayError() const
|
||||
{
|
||||
return m_xrayController->getError();
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> ProxyService::getAllConfigs() const
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Getting all configs");
|
||||
return m_configManager->getAllConfigs();
|
||||
}
|
||||
|
||||
QMap<QString, QJsonObject> ProxyService::getConfigsByUuids(const QStringList &uuids) const
|
||||
{
|
||||
ProxyLogger::getInstance().debug(QString("Getting configs for UUIDs: %1").arg(uuids.join(", ")));
|
||||
return m_configManager->getConfigsByUuids(uuids);
|
||||
}
|
||||
|
||||
bool ProxyService::addConfigs(const QStringList &serializedConfigs)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Adding %1 new config(s)").arg(serializedConfigs.size()));
|
||||
bool success = m_configManager->addConfigs(serializedConfigs);
|
||||
if (success) {
|
||||
ProxyLogger::getInstance().info("Configs added successfully");
|
||||
emit configsChanged();
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to add configs");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ProxyService::removeConfig(const QString &uuid)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Removing config with UUID: %1").arg(uuid));
|
||||
|
||||
// Store current active config UUID before removal
|
||||
QString activeUuid = m_configManager->getActiveConfigUuid();
|
||||
|
||||
// Try to remove the config
|
||||
bool removed = m_configManager->removeConfig(uuid);
|
||||
if (removed) {
|
||||
ProxyLogger::getInstance().info("Config removed successfully");
|
||||
emit configsChanged();
|
||||
if (uuid == activeUuid && isXrayRunning()) {
|
||||
ProxyLogger::getInstance().info("Removed active config, restarting Xray");
|
||||
stopXray();
|
||||
|
||||
// Check if there are any configs left
|
||||
QString newActiveUuid = m_configManager->getActiveConfigUuid();
|
||||
if (!newActiveUuid.isEmpty()) {
|
||||
ProxyLogger::getInstance().info(QString("Starting Xray with new active config: %1").arg(newActiveUuid));
|
||||
return startXray();
|
||||
} else {
|
||||
ProxyLogger::getInstance().info("No configs left after removal");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().error(QString("Failed to remove config with UUID: %1").arg(uuid));
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
bool ProxyService::activateConfig(const QString &uuid)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Activating config with UUID: %1").arg(uuid));
|
||||
if (m_configManager->activateConfig(uuid)) {
|
||||
ProxyLogger::getInstance().info("Config activated successfully");
|
||||
emit configsChanged();
|
||||
// If config is successfully activated, restart Xray
|
||||
if (isXrayRunning()) {
|
||||
ProxyLogger::getInstance().info("Restarting Xray with new config");
|
||||
stopXray();
|
||||
return startXray();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ProxyLogger::getInstance().error(QString("Failed to activate config with UUID: %1").arg(uuid));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject ProxyService::getActiveConfig() const
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Getting active config");
|
||||
return m_configManager->getActiveConfig();
|
||||
}
|
||||
|
||||
bool ProxyService::updateAllConfigs(const QStringList &serializedConfigs)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Updating all configs with %1 new config(s)").arg(serializedConfigs.size()));
|
||||
bool success = m_configManager->updateAllConfigs(serializedConfigs);
|
||||
if (success) {
|
||||
ProxyLogger::getInstance().info("All configs updated successfully");
|
||||
emit configsChanged();
|
||||
if (isXrayRunning()) {
|
||||
ProxyLogger::getInstance().info("Restarting Xray with updated configs");
|
||||
stopXray();
|
||||
return startXray();
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().error("Failed to update all configs");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
QString ProxyService::getActiveConfigUuid() const
|
||||
{
|
||||
return m_configManager->getActiveConfigUuid();
|
||||
}
|
||||
|
||||
int ProxyService::getConfigCount() const
|
||||
{
|
||||
return m_configManager->getConfigCount();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "iproxyservice.h"
|
||||
#include "configmanager.h"
|
||||
#include "xraycontroller.h"
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class ProxyService : public QObject, public IProxyService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProxyService(QObject* parent = nullptr);
|
||||
~ProxyService() = default;
|
||||
|
||||
QJsonObject getConfig() const override;
|
||||
bool updateConfig(const QString& configStr) override;
|
||||
QMap<QString, QJsonObject> getAllConfigs() const override;
|
||||
QMap<QString, QJsonObject> getConfigsByUuids(const QStringList &uuids) const override;
|
||||
bool addConfigs(const QStringList &serializedConfigs) override;
|
||||
bool removeConfig(const QString &uuid) override;
|
||||
bool activateConfig(const QString &uuid) override;
|
||||
QJsonObject getActiveConfig() const override;
|
||||
bool updateAllConfigs(const QStringList &serializedConfigs) override;
|
||||
QString getActiveConfigUuid() const;
|
||||
int getConfigCount() const override;
|
||||
|
||||
bool startXray() override;
|
||||
bool stopXray() override;
|
||||
bool isXrayRunning() const override;
|
||||
qint64 getXrayProcessId() const override;
|
||||
QString getXrayError() const override;
|
||||
|
||||
signals:
|
||||
void configsChanged();
|
||||
void xrayStatusChanged(bool running);
|
||||
|
||||
private:
|
||||
QScopedPointer<ConfigManager> m_configManager;
|
||||
QScopedPointer<XrayController> m_xrayController;
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "xraycontroller.h"
|
||||
#include "proxylogger.h"
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
XrayController::XrayController(QObject *parent)
|
||||
: QObject(parent), m_process(nullptr)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("XrayController initialized");
|
||||
}
|
||||
|
||||
XrayController::~XrayController()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool XrayController::start(const QString &configPath)
|
||||
{
|
||||
if (isXrayRunning())
|
||||
{
|
||||
ProxyLogger::getInstance().info("Xray process is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
QString xrayPath = getXrayExecutablePath();
|
||||
ProxyLogger::getInstance().debug(QString("Xray executable path: %1").arg(xrayPath));
|
||||
|
||||
if (!QFile::exists(xrayPath))
|
||||
{
|
||||
ProxyLogger::getInstance().error(QString("Xray binary not found at: %1").arg(xrayPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!QFile::exists(configPath))
|
||||
{
|
||||
ProxyLogger::getInstance().error(QString("Config file not found at: %1").arg(configPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Starting Xray process");
|
||||
m_process.reset(new QProcess(this));
|
||||
m_process->setWorkingDirectory(QFileInfo(xrayPath).dir().absolutePath());
|
||||
m_process->setProgram(xrayPath);
|
||||
m_process->setArguments(getXrayArguments(configPath));
|
||||
|
||||
m_process->start();
|
||||
if (!m_process->waitForStarted())
|
||||
{
|
||||
ProxyLogger::getInstance().error(QString("Failed to start Xray process: %1").arg(m_process->errorString()));
|
||||
m_process.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info(QString("Xray process started successfully (PID: %1)").arg(m_process->processId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void XrayController::stop()
|
||||
{
|
||||
if (!m_process.isNull())
|
||||
{
|
||||
if (m_process->state() == QProcess::Running)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Killing Xray process (PID: %1)").arg(m_process->processId()));
|
||||
m_process->kill();
|
||||
m_process->waitForFinished(3000);
|
||||
}
|
||||
m_process.reset();
|
||||
ProxyLogger::getInstance().info("Xray process stopped");
|
||||
}
|
||||
}
|
||||
|
||||
bool XrayController::isXrayRunning() const
|
||||
{
|
||||
return !m_process.isNull() && m_process->state() == QProcess::Running;
|
||||
}
|
||||
|
||||
qint64 XrayController::getProcessId() const
|
||||
{
|
||||
return m_process ? m_process->processId() : -1;
|
||||
}
|
||||
|
||||
QString XrayController::getError() const
|
||||
{
|
||||
if (m_process && m_process->error() != QProcess::UnknownError)
|
||||
{
|
||||
QString error = m_process->errorString();
|
||||
ProxyLogger::getInstance().error(QString("Xray process error: %1").arg(error));
|
||||
return error;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString XrayController::getXrayExecutablePath() const
|
||||
{
|
||||
QString xrayDir = QCoreApplication::applicationDirPath() + "/bin";
|
||||
QString xrayPath;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
xrayPath = QDir(xrayDir).filePath("xray.exe");
|
||||
#else
|
||||
xrayPath = QDir(xrayDir).filePath("xray");
|
||||
#endif
|
||||
|
||||
ProxyLogger::getInstance().debug(QString("Resolved Xray executable path: %1").arg(xrayPath));
|
||||
return xrayPath;
|
||||
}
|
||||
|
||||
QStringList XrayController::getXrayArguments(const QString &configPath) const
|
||||
{
|
||||
QStringList args = QStringList() << "-c" << configPath << "-format=json";
|
||||
ProxyLogger::getInstance().debug(QString("Xray arguments: %1").arg(args.join(' ')));
|
||||
return args;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class XrayController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XrayController(QObject* parent = nullptr);
|
||||
~XrayController();
|
||||
|
||||
bool start(const QString& configPath);
|
||||
void stop();
|
||||
bool isXrayRunning() const;
|
||||
qint64 getProcessId() const;
|
||||
QString getError() const;
|
||||
|
||||
private:
|
||||
QString getXrayExecutablePath() const;
|
||||
QStringList getXrayArguments(const QString& configPath) const;
|
||||
|
||||
QScopedPointer<QProcess> m_process;
|
||||
};
|
||||
Reference in New Issue
Block a user