feat: Added local proxy server

This commit is contained in:
aiamnezia
2025-08-08 06:44:18 +04:00
parent a6e6de33c8
commit 2ae97c5cda
16 changed files with 1909 additions and 2 deletions
+2 -2
View File
@@ -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));
+10
View File
@@ -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
+565
View File
@@ -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;
}
+47
View File
@@ -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};
};
+576
View File
@@ -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
);
}
+41
View File
@@ -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;
};
+28
View File
@@ -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;
};
+129
View File
@@ -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;
}
+54
View File
@@ -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
+57
View File
@@ -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();
}
+26
View File
@@ -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;
};
+176
View File
@@ -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();
}
+41
View File
@@ -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;
};
+116
View File
@@ -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;
}
+25
View File
@@ -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;
};