HTTP Server Component Implemented in QTC++

This component implements a lightweight, high-performance HTTP server based on the Qt framework, supporting three communication protocols: TCP/HTTP, SSL/HTTPS, and local sockets. This library provides complete HTTP protocol parsing capabilities, automatically handling request methods, URL paths, query parameters, request headers, and request bodies, and supports various response formats including text, JSON, files, images, and binary data. The multi-threaded architecture and connection management ensure high concurrency performance, allowing users to quickly build stable and secure web services, RESTful APIs, or embedded HTTP interfaces.1. Core Configuration and Utility ClassesServerConfig Structure: Used for server configuration container, controlling server behavior parameters.

struct ServerConfig {    int maxThreads{4};                    // Maximum number of threads    int sessionTimeoutMs{30000};          // Session timeout (30 seconds)    qint64 maxRequestSize{10 * 1024 * 1024}; // Maximum request size (10MB)    bool enableCompression{true};         // Enable compression (reserved)    bool enableCors{true};                // Enable CORS cross-domain    LogLevel logLevel{LogLevel::Info};    // Log level    int maxConnections{1000};             // Maximum number of connections};

ConnectionManager: Connection manager for global connection count management, preventing server overload.

class ConnectionManager {    std::atomic activeConnections_{0};    // Current active connections    std::atomic maxConnections_{1000};    // Maximum connection limit
public:    bool canAcceptNewConnection() const;       // Check if new connections can be accepted    void connectionEstablished();              // Called when a connection is established    void connectionFinished();                 // Called when a connection ends};

Logger: Logging system: Hierarchical logging, supports session identification, thread-safe.

class Logger {    static LogLevel logLevel_;  // Current log level
public:    static void log(LogLevel level, const QString &message,                    const QObject *session = nullptr);};

2. Server Management Class HierarchyAbstractManage: Abstract base class for providing server lifecycle management and thread model.

class AbstractManage : public QObject {protected:    std::shared_ptr serverThreadPool_;  // Server thread pool (1 thread)    std::shared_ptr handleThreadPool_;  // Handling thread pool (configurable)    QSet availableSessions_;              // Active session collection
public:    bool start();        // Start server    void stop();         // Stop server    void newSession(const std::shared_ptr &session); // Create new session};

TcpServerManage: TCP server: HTTP server implementation based on QTcpServer.

class TcpServerManage : public AbstractManage {    QPointer tcpServer_;
public:    bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);};

SslServerManage: SSL server: HTTPS server implementation based on QSslSocket, supports certificate configuration.

class SslServerManage : public AbstractManage {    QPointer tcpServer_;    std::shared_ptr sslConfiguration_;
public:    bool listen(const QHostAddress &address, quint16 port,                const QString &crtFilePath, const QString &keyFilePath,                const QList<qpair> &caFileList);};</qpair

LocalServerManage: Local server: Inter-process communication server based on QLocalServer.

class LocalServerManage : public AbstractManage {    QPointer localServer_;
public:    bool listen(const QString &name);};

3. Convenient Wrapper ClassHttpServer: A convenient class that simplifies server creation and management.

class HttpServer {    std::unique_ptr tcpManage_;#ifndef QT_NO_SSL    std::unique_ptr sslManage_;#endif
public:    bool startTcpServer(const QHostAddress &address, quint16 port);    bool startHttpsServer(...);  // SSL parameters    void setRequestHandler(std::function<void(const std::shared_ptr &)> handler);};

4. All core code is as follows:1.JQHttpServer.h

#ifndef JQHTTPSERVER_H_#define JQHTTPSERVER_H_
#include #include #include #include #include #include #include #include #include #include #include #include #include 
class QIODevice;class QThreadPool;class QHostAddress;class QTimer;class QImage;class QTcpServer;class QLocalServer;class QSslCertificate;class QSslKey;class QSslConfiguration;
namespace JQHttpServer{
// Error code enumerationenum class ErrorCode {    Success = 0,    InvalidRequest = 1,    SessionExpired = 2,    IODeviceError = 3,    FileNotFound = 4,    BufferError = 5,    SSLConfigurationError = 6};
// Log levelsenum class LogLevel {    Debug = 0,    Info = 1,    Warning = 2,    Error = 3};
// Server configurationstruct ServerConfig {    int maxThreads{4};    int sessionTimeoutMs{30000};    qint64 maxRequestSize{10 * 1024 * 1024}; // 10MB    bool enableCompression{true};    bool enableCors{true};    LogLevel logLevel{LogLevel::Info};    int maxConnections{1000};
    static ServerConfig loadFromFile(const QString &path);};
// Connection managerclass ConnectionManager {public:    static ConnectionManager& instance() {        static ConnectionManager manager;        return manager;    }
    bool canAcceptNewConnection() const {        return activeConnections_.load() < maxConnections_;    }
    void connectionEstablished() { ++activeConnections_; }    void connectionFinished() { --activeConnections_; }
    void setMaxConnections(int max) { maxConnections_ = max; }    int activeConnections() const { return activeConnections_.load(); }
private:    std::atomic activeConnections_{0};    std::atomic maxConnections_{1000};};
// Logging systemclass Logger {public:    static void setLogLevel(LogLevel level) { logLevel_ = level; }
    static void log(LogLevel level, const QString &message,                    const QObject *session = nullptr) {        if (level < logLevel_) return;
        QString logEntry = QString("[%1] [%2] %3")            .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"))            .arg(levelToString(level))            .arg(message);
        if (session) {            logEntry += QString(" [Session:0x%1]").arg(reinterpret_cast(session), 0, 16);        }
        switch (level) {        case LogLevel::Debug:        case LogLevel::Info:            qDebug() << logEntry;            break;        case LogLevel::Warning:            qWarning() << logEntry;            break;        case LogLevel::Error:            qCritical() << logEntry;            break;        }    }
private:    static QString levelToString(LogLevel level) {        switch (level) {        case LogLevel::Debug: return "DEBUG";        case LogLevel::Info: return "INFO";        case LogLevel::Warning: return "WARN";        case LogLevel::Error: return "ERROR";        }        return "UNKNOWN";    }
    static LogLevel logLevel_;};
class Session: public QObject{    Q_OBJECT    Q_DISABLE_COPY(Session)
public:    Session(const std::shared_ptr &ioDevice, const ServerConfig &config);    ~Session();
    inline void setHandleAcceptedCallback(const std::function<void(const std::shared_ptr &)> &callback) {         handleAcceptedCallback_ = callback;     }
    inline QString requestMethod() const { return requestMethod_; }    inline QString requestUrl() const { return requestUrl_; }    inline QString requestCrlf() const { return requestCrlf_; }    inline QMap requestHeader() const { return requestHeader_; }    inline QByteArray requestBody() const { return requestBody_; }
    QString requestUrlPath() const;    QStringList requestUrlPathSplitToList() const;    QMap requestUrlQuery() const;
public slots:    void replyText(QString replyData, int httpStatusCode = 200);    void replyRedirects(const QUrl &targetUrl, int httpStatusCode = 200);    void replyJsonObject(const QJsonObject &jsonObject, int httpStatusCode = 200);    void replyJsonArray(const QJsonArray &jsonArray, int httpStatusCode = 200);    void replyFile(const QString &filePath, int httpStatusCode = 200);    void replyImage(const QImage ℑ, int httpStatusCode = 200);    void replyImage(const QString &imageFilePath, int httpStatusCode = 200);    void replyBytes(QByteArray bytes, int httpStatusCode = 200);    void replyOptions();
private slots:    void onReadyRead();    void onBytesWritten(qint64 bytes);    void onTimeout();
private:    void inspectionBufferSetup1();    void inspectionBufferSetup2();    void addSecurityHeaders(QString &headers);    void safeDeleteLater();
private:    std::shared_ptr ioDevice_;    std::function<void(const std::shared_ptr &)> handleAcceptedCallback_;    std::shared_ptr timerForClose_;
    ServerConfig config_;    QByteArray buffer_;
    QString requestMethod_;    QString requestUrl_;    QString requestCrlf_;
    QMap requestHeader_;    bool headerAcceptedFinish_ = false;    qint64 contentLength_ = -1;    bool alreadyReply_ = false;
    QByteArray requestBody_;
    qint64 waitWrittenByteCount_ = 0;    std::shared_ptr ioDeviceForReply_;};
class AbstractManage: public QObject{    Q_OBJECT    Q_DISABLE_COPY(AbstractManage)
public:    AbstractManage(const ServerConfig &config = {});    virtual ~AbstractManage();
    inline void setHttpAcceptedCallback(const std::function<void(const std::shared_ptr &session)> &callback) {         httpAcceptedCallback_ = callback;     }
    inline std::shared_ptr handleThreadPool() { return handleThreadPool_; }    inline std::shared_ptr serverThreadPool() { return serverThreadPool_; }    inline ServerConfig config() const { return config_; }
    virtual bool isRunning() = 0;
public slots:    bool start();    void stop();
protected:    virtual bool onStart() = 0;    virtual void onFinish() = 0;
    bool startServerThread();    void stopHandleThread();    void stopServerThread();    void newSession(const std::shared_ptr &session);    void handleAccepted(const std::shared_ptr &session);
signals:    void readyToClose();
protected:    std::shared_ptr serverThreadPool_;    std::shared_ptr handleThreadPool_;
    QMutex mutex_;    ServerConfig config_;
    std::function<void(const std::shared_ptr &session)> httpAcceptedCallback_;    QSet availableSessions_;};
class TcpServerManage: public AbstractManage{    Q_OBJECT    Q_DISABLE_COPY(TcpServerManage)
public:    TcpServerManage(const ServerConfig &config = {});    ~TcpServerManage();
    bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
private:    bool isRunning() override;    bool onStart() override;    void onFinish() override;
private:    QPointer tcpServer_;    QHostAddress listenAddress_ = QHostAddress::Any;    quint16 listenPort_ = 0;};
#ifndef QT_NO_SSLclass SslServerHelper: public QTcpServer{    Q_OBJECT
public:    SslServerHelper(std::function callback)         : onIncomingConnectionCallback_(std::move(callback)) {}
protected:    void incomingConnection(qintptr socketDescriptor) override {        if (onIncomingConnectionCallback_) {            onIncomingConnectionCallback_(socketDescriptor);        }
    }
private:    std::function onIncomingConnectionCallback_;};
class SslServerManage: public AbstractManage{    Q_OBJECT    Q_DISABLE_COPY(SslServerManage)
public:    SslServerManage(const ServerConfig &config = {});    ~SslServerManage();
    bool listen(            const QHostAddress &address = QHostAddress::Any,            quint16 port = 0,            const QString &crtFilePath = "",            const QString &keyFilePath = "",            const QList<qpair> &caFileList = {} // [ { filePath, isPem } ]        );
private:    bool isRunning() override;    bool onStart() override;    void onFinish() override;
private:    QPointer tcpServer_;    QHostAddress listenAddress_ = QHostAddress::Any;    quint16 listenPort_ = 0;    std::shared_ptr sslConfiguration_;};#endif
class LocalServerManage: public AbstractManage{    Q_OBJECT    Q_DISABLE_COPY(LocalServerManage)
public:    LocalServerManage(const ServerConfig &config = {});    ~LocalServerManage();
    bool listen(const QString &name);
private:    bool isRunning() override;    bool onStart() override;    void onFinish() override;
private:    QPointer localServer_;    QString listenName_;
// Convenient HTTP server classclass HttpServer {public:    HttpServer(const ServerConfig &config = {}) : config_(config) {}
    bool startTcpServer(const QHostAddress &address = QHostAddress::Any, quint16 port = 8080) {        tcpManage_ = std::make_unique(config_);        return tcpManage_->listen(address, port);    }
#ifndef QT_NO_SSL    bool startHttpsServer(const QHostAddress &address = QHostAddress::Any,                          quint16 port = 8443,                         const QString &crtPath = "",                         const QString &keyPath = "") {        sslManage_ = std::make_unique(config_);        return sslManage_->listen(address, port, crtPath, keyPath);    }#endif
    void setRequestHandler(std::function<void(const std::shared_ptr &)> handler) {        if (tcpManage_) tcpManage_->setHttpAcceptedCallback(handler);#ifndef QT_NO_SSL        if (sslManage_) sslManage_->setHttpAcceptedCallback(handler);#endif    }
    void stop() {        if (tcpManage_) tcpManage_->stop();#ifndef QT_NO_SSL        if (sslManage_) sslManage_->stop();#endif    }
private:    ServerConfig config_;    std::unique_ptr tcpManage_;#ifndef QT_NO_SSL    std::unique_ptr sslManage_;#endif};
} // namespace JQHttpServer
#endif // JQHTTPSERVER_H_</qpair

2:JQHttpServer.cpp

#include "JQHttpServer.h"
#include #include #include #include #include #include #include #include #include #include #include #include #include #include 
#include #include #include #include #include #ifndef QT_NO_SSL#   include #   include #   include #   include #endif
// Static member initializationJQHttpServer::LogLevel JQHttpServer::Logger::logLevel_ = LogLevel::Info;
// Static string definitionsnamespace {    const QString REPLY_TEXT_FORMAT =        "HTTP/1.1 %1 OK\r\n"        "Content-Type: %2\r\n"        "Content-Length: %3\r\n"        "%4"        "\r\n"        "%5";
    const QString REPLY_REDIRECTS_FORMAT =        "HTTP/1.1 %1 OK\r\n"        "Content-Type: %2\r\n"        "Content-Length: %3\r\n"        "%4"        "\r\n"        "%5";
    const QString REPLY_FILE_FORMAT =        "HTTP/1.1 %1 OK\r\n"        "Content-Disposition: attachment;filename=%2\r\n"        "Content-Length: %3\r\n"        "%4"        "\r\n";
    const QString REPLY_IMAGE_FORMAT =        "HTTP/1.1 %1\r\n"        "Content-Type: image/png\r\n"        "Content-Length: %2\r\n"        "%4"        "\r\n";
    const QString REPLY_BYTES_FORMAT =        "HTTP/1.1 %1 OK\r\n"        "Content-Type: application/octet-stream\r\n"        "Content-Length: %2\r\n"        "%4"        "\r\n";
    const QString REPLY_OPTIONS_FORMAT =        "HTTP/1.1 200 OK\r\n"        "Allow: OPTIONS, GET, POST, PUT, HEAD\r\n"        "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, HEAD\r\n"        "Content-Length: 0\r\n"        "%2"        "\r\n";}
#define JQHTTPSERVER_SESSION_PROTECTION(functionName, ...) \    if (!this || contentLength_ < -1 || waitWrittenByteCount_ < -1) { \        Logger::log(LogLevel::Error, \                   QString("JQHttpServer::Session::%1: current session this is null").arg(functionName)); \        return __VA_ARGS__; \    }
// ServerConfig implementationJQHttpServer::ServerConfig JQHttpServer::ServerConfig::loadFromFile(const QString &path) {    ServerConfig config;    // Logic to load from configuration file can be added here    // Currently returns default configuration    return config;}
// Session implementationJQHttpServer::Session::Session(const std::shared_ptr &ioDevice, const ServerConfig &config)    : ioDevice_(ioDevice)    , config_(config)    , timerForClose_(std::make_shared()){    if (!ioDevice_) {        Logger::log(LogLevel::Error, "Session created with null IO device");        return;
    }
    timerForClose_->setInterval(config_.sessionTimeoutMs);    timerForClose_->setSingleShot(true);
    // Check connection limit    if (!ConnectionManager::instance().canAcceptNewConnection()) {        Logger::log(LogLevel::Warning, "Connection limit reached, rejecting new connection");        safeDeleteLater();        return;
    }    ConnectionManager::instance().connectionEstablished();
    // Use QPointer to wrap the raw pointer to avoid dangling pointers    QPointer self(this);
    connect(ioDevice_.get(), &QIODevice::readyRead, this, &Session::onReadyRead);    connect(ioDevice_.get(), &QIODevice::bytesWritten, this, &Session::onBytesWritten);    connect(timerForClose_.get(), &QTimer::timeout, this, &Session::onTimeout);
    Logger::log(LogLevel::Debug, "New session created", this);}
JQHttpServer::Session::~Session() {    ConnectionManager::instance().connectionFinished();    Logger::log(LogLevel::Debug, "Session destroyed", this);}
void JQHttpServer::Session::onReadyRead() {    if (timerForClose_->isActive()) {        timerForClose_->stop();
    }
    const auto data = ioDevice_->readAll();    buffer_.append(data);
    // Check request size limit    if (buffer_.size() > config_.maxRequestSize) {        Logger::log(LogLevel::Warning, "Request size exceeded limit", this);        replyText("Request too large", 413);        return;
    }
    inspectionBufferSetup1();    timerForClose_->start();}
void JQHttpServer::Session::onBytesWritten(qint64 bytes) {    waitWrittenByteCount_ -= bytes;
    if (waitWrittenByteCount_ == 0) {        safeDeleteLater();        return;
    }
    if (ioDeviceForReply_ && !ioDeviceForReply_->atEnd()) {        ioDevice_->write(ioDeviceForReply_->read(512 * 1024));
    }
    if (timerForClose_->isActive()) {        timerForClose_->stop();
    }    timerForClose_->start();}
void JQHttpServer::Session::onTimeout() {    Logger::log(LogLevel::Info, "Session timeout, closing", this);    safeDeleteLater();}
void JQHttpServer::Session::safeDeleteLater() {    if (ioDevice_) {        ioDevice_->close();
    }    deleteLater();}
QString JQHttpServer::Session::requestUrlPath() const {    JQHTTPSERVER_SESSION_PROTECTION("requestUrlPath", {});
    static const QRegularExpression queryPattern("\?.*$");    static const QRegularExpression trailingSlash("/+$");    static const QVector<qpair> replacements = {        {"%5B", "["}, {"%5D", "]"}, {"%7B", "{"}, {"%7D", "}"}, {"%5E", "^"}};
    QString result = requestUrl_;    result.remove(queryPattern);    result.remove(trailingSlash);
    if (result.startsWith("//")) {        result = result.mid(1);
    }
    for (const auto &rep : replacements) {        result.replace(rep.first, rep.second);
    }
    return result;}
QStringList JQHttpServer::Session::requestUrlPathSplitToList() const {    auto list = requestUrlPath().split("/", Qt::SkipEmptyParts);    return list;}
QMap JQHttpServer::Session::requestUrlQuery() const {    const auto indexForQueryStart = requestUrl_.indexOf("?");    if (indexForQueryStart < 0) return {};
    QMap result;    auto queryString = requestUrl_.mid(indexForQueryStart + 1);    auto lines = QUrl::fromEncoded(queryString.toUtf8()).toString().split("&");
    static const QVector<qpair> replacements = {        {"%5B", "["}, {"%5D", "]"}, {"%7B", "{"}, {"%7D", "}"}, {"%5E", "^"}};
    for (auto line : lines) {        for (const auto &rep : replacements) {            line.replace(rep.first, rep.second);
        }
        auto indexOf = line.indexOf("=");        if (indexOf > 0) {            result[line.mid(0, indexOf)] = line.mid(indexOf + 1);
        }
    }
    return result;}
void JQHttpServer::Session::addSecurityHeaders(QString &headers) {    headers += "X-Content-Type-Options: nosniff\r\n";    headers += "X-Frame-Options: DENY\r\n";    headers += "X-XSS-Protection: 1; mode=block\r\n";
    if (config_.enableCors) {        headers += "Access-Control-Allow-Origin: *\r\n";        headers += "Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS\r\n";        headers += "Access-Control-Allow-Headers: Content-Type, Authorization\r\n";
    }}
void JQHttpServer::Session::replyText(QString replyData, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyText");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyText", Qt::QueuedConnection,                                  Q_ARG(QString, std::move(replyData)),                                  Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_TEXT_FORMAT                         .arg(QString::number(httpStatusCode),                              "text/plain;charset=UTF-8",                              QString::number(replyData.toUtf8().size()),                              securityHeaders,                              replyData)                         .toUtf8();
    waitWrittenByteCount_ = data.size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info,                QString("Replied with text, status: %1, size: %2")                   .arg(httpStatusCode)                   .arg(replyData.size()),                this);}
void JQHttpServer::Session::replyRedirects(const QUrl &targetUrl, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyRedirects");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyRedirects", Qt::QueuedConnection,                                  Q_ARG(QUrl, targetUrl), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    const auto buffer = QString("").arg(targetUrl.toString());
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_REDIRECTS_FORMAT                         .arg(QString::number(httpStatusCode),                              "text/html;charset=UTF-8",                              QString::number(buffer.toUtf8().size()),                              securityHeaders,                              buffer)                         .toUtf8();
    waitWrittenByteCount_ = data.size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info,                QString("Redirected to: %1, status: %2").arg(targetUrl.toString()).arg(httpStatusCode),                this);}
void JQHttpServer::Session::replyJsonObject(const QJsonObject &jsonObject, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyJsonObject");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyJsonObject", Qt::QueuedConnection,                                  Q_ARG(QJsonObject, jsonObject), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    const auto data = QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto replyData = REPLY_TEXT_FORMAT                              .arg(QString::number(httpStatusCode),                                   "application/json;charset=UTF-8",                                   QString::number(data.size()),                                   securityHeaders,                                   QString(data))                              .toUtf8();
    waitWrittenByteCount_ = replyData.size();    ioDevice_->write(replyData);
    Logger::log(LogLevel::Info,                QString("Replied with JSON object, status: %1, size: %2")                   .arg(httpStatusCode)                   .arg(data.size()),                this);}
void JQHttpServer::Session::replyJsonArray(const QJsonArray &jsonArray, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyJsonArray");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyJsonArray", Qt::QueuedConnection,                                  Q_ARG(QJsonArray, jsonArray), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    const auto data = QJsonDocument(jsonArray).toJson(QJsonDocument::Compact);
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto replyData = REPLY_TEXT_FORMAT                              .arg(QString::number(httpStatusCode),                                   "application/json;charset=UTF-8",                                   QString::number(data.size()),                                   securityHeaders,                                   QString(data))                              .toUtf8();
    waitWrittenByteCount_ = replyData.size();    ioDevice_->write(replyData);
    Logger::log(LogLevel::Info,                QString("Replied with JSON array, status: %1, size: %2")                   .arg(httpStatusCode)                   .arg(data.size()),                this);}
void JQHttpServer::Session::replyFile(const QString &filePath, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyFile");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyFile", Qt::QueuedConnection,                                  Q_ARG(QString, filePath), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    ioDeviceForReply_ = std::make_shared(filePath);    auto file = std::dynamic_pointer_cast(ioDeviceForReply_);
    if (!file || !file->open(QIODevice::ReadOnly)) {        Logger::log(LogLevel::Error, QString("Failed to open file: %1").arg(filePath), this);        ioDeviceForReply_.reset();        safeDeleteLater();        return;
    }
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_FILE_FORMAT                         .arg(QString::number(httpStatusCode),                              QFileInfo(filePath).fileName(),                              QString::number(file->size()),                              securityHeaders)                         .toUtf8();
    waitWrittenByteCount_ = data.size() + file->size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info,                QString("Replied with file: %1, status: %2, size: %3")                   .arg(filePath)                   .arg(httpStatusCode)                   .arg(file->size()),                this);}
void JQHttpServer::Session::replyImage(const QImage ℑ, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyImage");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyImage", Qt::QueuedConnection,                                  Q_ARG(QImage, image), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    auto buffer = std::make_shared();
    if (!buffer->open(QIODevice::ReadWrite)) {        Logger::log(LogLevel::Error, "Failed to open buffer for image", this);        safeDeleteLater();        return;
    }
    if (!image.save(buffer.get(), "PNG")) {        Logger::log(LogLevel::Error, "Failed to save image to buffer", this);        safeDeleteLater();        return;
    }
    ioDeviceForReply_ = buffer;    ioDeviceForReply_->seek(0);
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_IMAGE_FORMAT                         .arg(QString::number(httpStatusCode),                              QString::number(buffer->buffer().size()),                              securityHeaders)                         .toUtf8();
    waitWrittenByteCount_ = data.size() + buffer->buffer().size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info,                QString("Replied with image, status: %1, size: %2")                   .arg(httpStatusCode)                   .arg(buffer->buffer().size()),                this);}
void JQHttpServer::Session::replyImage(const QString &imageFilePath, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyImage");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyImage", Qt::QueuedConnection,                                  Q_ARG(QString, imageFilePath), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    auto buffer = std::make_shared(imageFilePath);
    if (!buffer->open(QIODevice::ReadOnly)) {        Logger::log(LogLevel::Error, QString("Failed to open image file: %1").arg(imageFilePath), this);        safeDeleteLater();        return;
    }
    ioDeviceForReply_ = buffer;    ioDeviceForReply_->seek(0);
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_IMAGE_FORMAT                         .arg(QString::number(httpStatusCode),                              QString::number(buffer->size()),                              securityHeaders)                         .toUtf8();
    waitWrittenByteCount_ = data.size() + buffer->size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info,                QString("Replied with image file: %1, status: %2, size: %3")                   .arg(imageFilePath)                   .arg(httpStatusCode)                   .arg(buffer->size()),                this);}
void JQHttpServer::Session::replyBytes(QByteArray bytes, int httpStatusCode) {    JQHTTPSERVER_SESSION_PROTECTION("replyBytes");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyBytes", Qt::QueuedConnection,                                  Q_ARG(QByteArray, std::move(bytes)), Q_ARG(int, httpStatusCode));        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    auto buffer = std::make_shared();    buffer->setData(std::move(bytes));
    if (!buffer->open(QIODevice::ReadWrite)) {        Logger::log(LogLevel::Error, "Failed to open buffer for bytes", this);        safeDeleteLater();        return;
    }
    ioDeviceForReply_ = buffer;    ioDeviceForReply_->seek(0);
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_BYTES_FORMAT                         .arg(QString::number(httpStatusCode),                              QString::number(buffer->buffer().size()),                              securityHeaders)                         .toUtf8();
    waitWrittenByteCount_ = data.size() + buffer->buffer().size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info,                QString("Replied with bytes, status: %1, size: %2")                   .arg(httpStatusCode)                   .arg(buffer->buffer().size()),                this);}
void JQHttpServer::Session::replyOptions() {    JQHTTPSERVER_SESSION_PROTECTION("replyOptions");
    if (alreadyReply_) {        Logger::log(LogLevel::Warning, "Already replied to this session", this);        return;
    }
    if (QThread::currentThread() != thread()) {        QMetaObject::invokeMethod(this, "replyOptions", Qt::QueuedConnection);        return;
    }
    alreadyReply_ = true;
    if (!ioDevice_) {        Logger::log(LogLevel::Error, "IO device is null", this);        safeDeleteLater();        return;
    }
    QString securityHeaders;    addSecurityHeaders(securityHeaders);
    const auto data = REPLY_OPTIONS_FORMAT                         .arg(securityHeaders)                         .toUtf8();
    waitWrittenByteCount_ = data.size();    ioDevice_->write(data);
    Logger::log(LogLevel::Info, "Replied to OPTIONS request", this);}
void JQHttpServer::Session::inspectionBufferSetup1() {    if (!headerAcceptedFinish_) {        while (true) {            static QByteArray splitFlag("\r\n");            auto splitFlagIndex = buffer_.indexOf(splitFlag);
            if (splitFlagIndex == -1) {                if (requestMethod_.isEmpty() && buffer_.size() > 4) {                    Logger::log(LogLevel::Warning, "Invalid request format", this);                    safeDeleteLater();                }
                return;
            }
            if (requestMethod_.isEmpty() && splitFlagIndex == 0) {                Logger::log(LogLevel::Warning, "Empty request method", this);                safeDeleteLater();                return;
            }
            if (requestMethod_.isEmpty()) {                auto requestLineDatas = buffer_.mid(0, splitFlagIndex).split(' ');                buffer_.remove(0, splitFlagIndex + 2);
                if (requestLineDatas.size() != 3) {                    Logger::log(LogLevel::Warning, "Invalid request line", this);                    safeDeleteLater();                    return;
                }
                requestMethod_ = requestLineDatas.at(0);                requestUrl_ = requestLineDatas.at(1);                requestCrlf_ = requestLineDatas.at(2);
                static const QStringList allowedMethods = {"GET", "OPTIONS", "POST", "PUT"};                if (!allowedMethods.contains(requestMethod_.toUpper())) {                    Logger::log(LogLevel::Warning,                                QString("Unsupported method: %1").arg(requestMethod_), this);                    safeDeleteLater();                    return;
                }
                Logger::log(LogLevel::Debug,                            QString("Request: %1 %2").arg(requestMethod_).arg(requestUrl_), this);            } else if (splitFlagIndex == 0) {                buffer_.remove(0, 2);                headerAcceptedFinish_ = true;
                bool shouldProcess = false;                if (requestMethod_.toUpper() == "GET" || requestMethod_.toUpper() == "OPTIONS") {                    shouldProcess = true;
                } else if ((requestMethod_.toUpper() == "POST" || requestMethod_.toUpper() == "PUT")) {                    shouldProcess = (contentLength_ > 0) ? (!buffer_.isEmpty()) : true;
                }
                if (shouldProcess) {                    inspectionBufferSetup2();                }
            } else {                auto index = buffer_.indexOf(':');                if (index <= 0) {                    Logger::log(LogLevel::Warning, "Invalid header format", this);                    safeDeleteLater();                    return;
                }
                auto headerData = buffer_.mid(0, splitFlagIndex);                buffer_.remove(0, splitFlagIndex + 2);
                const auto key = headerData.mid(0, index);                auto value = headerData.mid(index + 1).trimmed();
                requestHeader_[key] = value;
                if (key.toLower() == "content-length") {                    contentLength_ = value.toLongLong();                }
            }
        }
    } else {        inspectionBufferSetup2();    }}
void JQHttpServer::Session::inspectionBufferSetup2() {    requestBody_ += buffer_;    buffer_.clear();
    if (!handleAcceptedCallback_) {        Logger::log(LogLevel::Error, "No handle accepted callback set", this);        safeDeleteLater();        return;
    }
    if (contentLength_ != -1 && requestBody_.size() != contentLength_) {        return;
    }
    Logger::log(LogLevel::Debug,                QString("Request body received, size: %1").arg(requestBody_.size()), this);
    auto self = std::shared_ptr(this, [](Session*){}); // Use with caution, ensure lifecycle    handleAcceptedCallback_(self);
}
// AbstractManage implementationJQHttpServer::AbstractManage::AbstractManage(const ServerConfig &config)    : config_(config){    handleThreadPool_ = std::make_shared();    serverThreadPool_ = std::make_shared();
    handleThreadPool_->setMaxThreadCount(config_.maxThreads);    serverThreadPool_->setMaxThreadCount(1);
    ConnectionManager::instance().setMaxConnections(config_.maxConnections);    Logger::setLogLevel(config_.logLevel);}
JQHttpServer::AbstractManage::~AbstractManage() {    stop();}
bool JQHttpServer::AbstractManage::start() {    if (QThread::currentThread() != thread()) {        Logger::log(LogLevel::Error, "Start called from wrong thread");        return false;
    }
    if (isRunning()) {        Logger::log(LogLevel::Warning, "Server already running");        return false;
    }
    return startServerThread();}
void JQHttpServer::AbstractManage::stop() {    if (!isRunning()) {        Logger::log(LogLevel::Warning, "Server not running");        return;
    }
    emit readyToClose();    stopServerThread();    stopHandleThread();}
bool JQHttpServer::AbstractManage::startServerThread() {    QSemaphore semaphore;    bool success = false;
    QtConcurrent::run(serverThreadPool_.get(), [&semaphore, &success, this]() {        QEventLoop eventLoop;        connect(this, &AbstractManage::readyToClose, &eventLoop, &QEventLoop::quit);
        success = this->onStart();        semaphore.release(1);
        if (success) {            Logger::log(LogLevel::Info, "Server started successfully");            eventLoop.exec();            this->onFinish();            Logger::log(LogLevel::Info, "Server stopped");        }
    });
    semaphore.acquire(1);    return success;}
void JQHttpServer::AbstractManage::stopHandleThread() {    if (handleThreadPool_) {        handleThreadPool_->waitForDone();    }}
void JQHttpServer::AbstractManage::stopServerThread() {    if (serverThreadPool_) {        serverThreadPool_->waitForDone();    }}
void JQHttpServer::AbstractManage::newSession(const std::shared_ptr &session) {    if (!session) return;
    session->setHandleAcceptedCallback([this](const std::shared_ptr &session) {        handleAccepted(session);    });
    auto rawPtr = session.get();    connect(rawPtr, &QObject::destroyed, [this, rawPtr]() {        QMutexLocker locker(&mutex_);        availableSessions_.remove(rawPtr);    });
    QMutexLocker locker(&mutex_);    availableSessions_.insert(rawPtr);}
void JQHttpServer::AbstractManage::handleAccepted(const std::shared_ptr &session) {    if (!httpAcceptedCallback_) {        Logger::log(LogLevel::Error, "No HTTP accepted callback set");        return;
    }
    QtConcurrent::run(handleThreadPool_.get(), [this, session]() {        try {            httpAcceptedCallback_(session);        } catch (const std::exception &e) {            Logger::log(LogLevel::Error,                        QString("Exception in request handler: %1").arg(e.what()),                        session.get());        } catch (...) {            Logger::log(LogLevel::Error,                        "Unknown exception in request handler",                        session.get());        }
    });}
// TcpServerManage implementationJQHttpServer::TcpServerManage::TcpServerManage(const ServerConfig &config)    : AbstractManage(config){ }
JQHttpServer::TcpServerManage::~TcpServerManage() {    stop();}
bool JQHttpServer::TcpServerManage::listen(const QHostAddress &address, quint16 port) {    listenAddress_ = address;    listenPort_ = port;    return start();}
bool JQHttpServer::TcpServerManage::isRunning() {    QMutexLocker locker(&mutex_);    return !tcpServer_.isNull();}
bool JQHttpServer::TcpServerManage::onStart() {    QMutexLocker locker(&mutex_);
    tcpServer_ = new QTcpServer();
    connect(tcpServer_.data(), &QTcpServer::newConnection, [this]() {        auto socket = tcpServer_->nextPendingConnection();        if (socket) {            newSession(std::make_shared(                std::shared_ptr(socket, [](QIODevice*){ /* Not actively deleted, managed by Session */ }),                config_            ));        }
    });
    if (!tcpServer_->listen(listenAddress_, listenPort_)) {        Logger::log(LogLevel::Error,                    QString("Failed to start TCP server on %1:%2: %3")                       .arg(listenAddress_.toString())                       .arg(listenPort_)                       .arg(tcpServer_->errorString()));        delete tcpServer_.data();        tcpServer_.clear();        return false;
    }
    Logger::log(LogLevel::Info,                QString("TCP server listening on %1:%2")                   .arg(listenAddress_.toString())                   .arg(listenPort_));    return true;}
void JQHttpServer::TcpServerManage::onFinish() {    QMutexLocker locker(&mutex_);
    if (tcpServer_) {        tcpServer_->close();        delete tcpServer_.data();        tcpServer_.clear();    }}
#ifndef QT_NO_SSL// SslServerManage implementationJQHttpServer::SslServerManage::SslServerManage(const ServerConfig &config)    : AbstractManage(config){ }
JQHttpServer::SslServerManage::~SslServerManage() {    stop();}
bool JQHttpServer::SslServerManage::listen(        const QHostAddress &address,        quint16 port,        const QString &crtFilePath,        const QString &keyFilePath,        const QList<qpair> &caFileList){    listenAddress_ = address;    listenPort_ = port;
    // Read certificate and key files    QFile fileForCrt(crtFilePath);    if (!fileForCrt.open(QIODevice::ReadOnly)) {        Logger::log(LogLevel::Error, QString("Cannot open certificate file: %1").arg(crtFilePath));        return false;
    }
    QFile fileForKey(keyFilePath);    if (!fileForKey.open(QIODevice::ReadOnly)) {        Logger::log(LogLevel::Error, QString("Cannot open key file: %1").arg(keyFilePath));        return false;
    }
    QSslCertificate sslCertificate(fileForCrt.readAll(), QSsl::Pem);    QSslKey sslKey(fileForKey.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
    if (sslCertificate.isNull()) {        Logger::log(LogLevel::Error, "Invalid SSL certificate");        return false;
    }
    if (sslKey.isNull()) {        Logger::log(LogLevel::Error, "Invalid SSL key");        return false;
    }
    // Read CA certificates    QList caCertificates;    for (const auto &caFile : caFileList) {        QFile fileForCa(caFile.first);        if (!fileForCa.open(QIODevice::ReadOnly)) {            Logger::log(LogLevel::Error, QString("Cannot open CA file: %1").arg(caFile.first));            return false;
        }        caCertificates.push_back(QSslCertificate(fileForCa.readAll(),                               caFile.second ? QSsl::Pem : QSsl::Der));    }
    sslConfiguration_ = std::make_shared();    sslConfiguration_->setPeerVerifyMode(QSslSocket::VerifyNone);    sslConfiguration_->setLocalCertificate(sslCertificate);    sslConfiguration_->setPrivateKey(sslKey);    sslConfiguration_->setProtocol(QSsl::TlsV1_2OrLater);    sslConfiguration_->setCaCertificates(caCertificates);
    return start();}
bool JQHttpServer::SslServerManage::isRunning() {    QMutexLocker locker(&mutex_);    return !tcpServer_.isNull();}
bool JQHttpServer::SslServerManage::onStart() {    QMutexLocker locker(&mutex_);
    tcpServer_ = new SslServerHelper([this](qintptr socketDescriptor) {        auto sslSocket = new QSslSocket();        sslSocket->setSslConfiguration(*sslConfiguration_);
        connect(sslSocket, &QSslSocket::encrypted, [this, sslSocket]() {            newSession(std::make_shared(                std::shared_ptr(sslSocket, [](QIODevice*){}),                config_            ));        });
        connect(sslSocket, qOverload<const qlist &>(&QSslSocket::sslErrors),                [sslSocket](const QList &errors) {            for (const auto &error : errors) {                Logger::log(LogLevel::Warning,                            QString("SSL error: %1").arg(error.errorString()));            }
        });
        sslSocket->setSocketDescriptor(socketDescriptor);        sslSocket->startServerEncryption();    });
    if (!tcpServer_->listen(listenAddress_, listenPort_)) {        Logger::log(LogLevel::Error,                    QString("Failed to start SSL server on %1:%2: %3")                       .arg(listenAddress_.toString())                       .arg(listenPort_)                       .arg(tcpServer_->errorString()));        delete tcpServer_.data();        tcpServer_.clear();        return false;
    }
    Logger::log(LogLevel::Info,                QString("SSL server listening on %1:%2")                   .arg(listenAddress_.toString())                   .arg(listenPort_));    return true;}
void JQHttpServer::SslServerManage::onFinish() {    QMutexLocker locker(&mutex_);
    if (tcpServer_) {        tcpServer_->close();        delete tcpServer_.data();        tcpServer_.clear();    }}#endif
// LocalServerManage implementationJQHttpServer::LocalServerManage::LocalServerManage(const ServerConfig &config)    : AbstractManage(config){ }
JQHttpServer::LocalServerManage::~LocalServerManage() {    stop();}
bool JQHttpServer::LocalServerManage::listen(const QString &name) {    listenName_ = name;    return start();}
bool JQHttpServer::LocalServerManage::isRunning() {    QMutexLocker locker(&mutex_);    return !localServer_.isNull();}
bool JQHttpServer::LocalServerManage::onStart() {    QMutexLocker locker(&mutex_);
    localServer_ = new QLocalServer();
    connect(localServer_.data(), &QLocalServer::newConnection, [this]() {        auto socket = localServer_->nextPendingConnection();        if (socket) {            newSession(std::make_shared(                std::shared_ptr(socket, [](QIODevice*){}),                config_            ));        }
    });
    if (!localServer_->listen(listenName_)) {        Logger::log(LogLevel::Error,                    QString("Failed to start local server on %1: %2")                       .arg(listenName_)                       .arg(localServer_->errorString()));        delete localServer_.data();        localServer_.clear();        return false;
    }
    Logger::log(LogLevel::Info,                QString("Local server listening on %1").arg(listenName_));    return true;}
void JQHttpServer::LocalServerManage::onFinish() {    QMutexLocker locker(&mutex_);
    if (localServer_) {        localServer_->close();        delete localServer_.data();        localServer_.clear();    }}
</qpair</qpair</qpair

Leave a Comment