QT之D指针(一)

参考1:https://blog.csdn.net/haoxinhaoxin/article/details/79473930

参考2:https://www.devbean.net/2016/11/qt-creator-source-study-07/

维基百科解释:https://wiki.qt.io/D-Pointer

PIMPL(Private Implementation 或 Pointer to Implementation)是通过一个私有的成员指针,将指针所指向的类的内部实现数据进行隐藏。

PIMPL基于这样一个事实:在C++类中,允许定义一个成员指针,指向一个已声明过的类型。在头文件中只是存放该类型的声明,而具体的定义是存放在CPP文件中,这样就可以隐藏类型的具体实现

最近在看一些开源项目时,发现很多XXPrivate的用法,查了资料才知道D指针,优点是不暴露自己数据结构的同时,达到高低版本的兼容性。

#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H

#include <functional>
#include <QMap>
#include <QVariant>
#include <QStringList>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>

class HttpClientPrivate;

/**
 * 对 QNetworkAccessManager 简单封装的 HTTP 访问客户端,简化 GET、POST、PUT、DELETE、上传、下载等操作。
 * 在执行请求前设置需要的参数和回调函数:
 *     1. 调用 header() 设置请求头
 *     2. 调用 param() 设置参数,使用 Form 表单的方式提交请求,GET 请求的 query parameters 也可以用它设置
 *     3. 调用 json() 设置 JSON 字符串的 request body,Content-Type 为 application/json,
 *        当然也可以不是 JSON 格式,因使用 request body 的情况多数是使用 JSON 格式传递复杂对象,故命名为 json
 *     4. 调用 success() 注册请求成功的回调函数
 *     5. 调用 fail() 注册请求失败的回调函数
 *     6. 调用 complete() 注册请求结束的回调函数
 *        success(), fail(), complete() 的回调函数是可选的,根据需要注册对应的回调函数,也可以一个都不注册
 * 然后根据请求的类型调用 get(), post(), put(), remove(), download(), upload() 执行 HTTP 请求
 *
 * 默认 HttpClient 会创建一个 QNetworkAccessManager,如果不想使用默认的,调用 manager() 传入即可。
 * 调用 debug(true) 设置为调试模式,输出调试信息如 URL、参数等。
 */
class HttpClient {
public:
    HttpClient(const QString &url);
    ~HttpClient();

    /**
     * @brief 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,
     *     可以传入 QNetworkAccessManager 给多个请求共享 (它不会被 HttpClient 删除,用户需要自己手动删除)。
     *     如果没有使用 manager() 传入一个 QNetworkAccessManager,则 HttpClient 会自动的创建一个,并且在网络访问完成后自动删除它。
     *
     * @param  manager 执行 HTTP 请求的 QNetworkAccessManager 对象
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& manager(QNetworkAccessManager *manager);

    /**
     * @brief  参数 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
     *
     * @param  debug 是否启用调试模式
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& debug(bool debug);

    /**
     * @brief 添加一个请求的参数,可以多次调用添加多个参数
     *
     * @param name  参数的名字
     * @param value 参数的值
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& param(const QString &name, const QVariant &value);

    /**
     * @brief 添加多个请求的参数
     *
     * @param ps QMap 类型的参数,key 为参数名,value 为参数值
     *           可以使用 {{"name", 1}, {"box", 2}} 的方式创建 QMap 对象
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& params(const QMap<QString, QVariant> &ps);

    /**
     * @brief 添加请求的参数 (请求体),使用 Json 格式,例如 "{\"name\": \"Alice\"}"
     *
     * @param json 请求体 (request body) 为 Json 格式的参数字符串
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& json(const QString &json);

    /**
     * @brief 添加请求头
     *
     * @param name  请求头的名字
     * @param value 请求头的值
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& header(const QString &name, const QString &value);

    /**
     * @brief 添加多个请求头
     *
     * @param nameValues 请求头的名字和值对
     *                   可以使用 {{"name", 1}, {"box", 2}} 的方式创建 QMap 对象
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& headers(const QMap<QString, QString> nameValues);

    /**
     * @brief 注册请求成功的回调函数
     *
     * @param successHandler 成功的回调函数,参数为响应的字符串
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& success(std::function<void (const QString &)> successHandler);

    /**
     * @brief 注册请求失败的回调函数
     *
     * @param failHandler 失败的回调函数,参数为失败原因和 HTTP 状态码
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& fail(std::function<void (const QString &, int)> failHandler);

    /**
     * @brief 注册请求结束的回调函数,不管成功还是失败请求结束后都会执行
     *
     * @param completeHandler 完成的回调函数,无参数
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& complete(std::function<void ()> completeHandler);

    /**
     * @brief 设置请求响应的字符集,默认使用 UTF-8
     *
     * @param cs 字符集
     * @return 返回 HttpClient 的引用,可以用于链式调用
     */
    HttpClient& charset(const QString &cs);

    /**
     * @brief 执行 GET 请求
     */
    void get();

    /**
     * @brief 执行 POST 请求
     */
    void post();

    /**
     * @brief 执行 PUT 请求
     */
    void put();

    /**
     * @brief 执行 DELETE 请求,由于 delete 是 C++ 的运算符,所以用同义词 remove
     *        注意: Qt 提供的 DELETE 请求是不支持传递参数的,
     *        请参考 QNetworkAccessManager::deleteResource(const QNetworkRequest &request)
     */
    void remove();

    /**
     * @brief 使用 GET 进行下载,下载的文件保存到 savePath
     *
     * @param savePath 下载的文件保存路径
     */
    void download(const QString &savePath);

    /**
     * @brief 上传单个文件
     *        使用 POST 上传,服务器端获取文件的参数名为 file
     *
     * @param path 要上传的文件的路径
     */
    void upload(const QString &path);

    /**
     * @brief 上传文件,文件的内容已经读取到 data 中
     *        使用 POST 上传,服务器端获取文件的参数名为 file
     *
     * @param path 要上传的文件的路径
     */
    void upload(const QByteArray &data);

    /**
     * @brief 上传多个文件
     *        使用 POST 上传,服务器端获取文件的参数名为 files
     *
     * @param paths 要上传的文件的路径
     */
    void upload(const QStringList &paths);

private:
    HttpClientPrivate *d;
};

#endif // HTTPCLIENT_H

 

#include "HttpClient.h"

#include <QDebug>
#include <QFile>
#include <QHash>
#include <QUrlQuery>
#include <QHttpPart>
#include <QHttpMultiPart>

/*-----------------------------------------------------------------------------|
 |                              HttpClientPrivate                              |
 |----------------------------------------------------------------------------*/
/**
 * @brief 请求的类型
 *
 * 注: UPLOAD 不是 HTTP Method,只是为了上传时对请求进行特殊处理而定义的
 */
enum class HttpClientRequestMethod {
    GET, POST, PUT, DELETE, UPLOAD
};

/**
 * @brief 缓存 HttpClientPrivate 的数据成员,方便在异步 lambda 中使用 = 以值的方式访问。
 */
class HttpClientPrivateCache {
public:
    std::function<void (const QString &)>      successHandler = nullptr;
    std::function<void (const QString &, int)>    failHandler = nullptr;
    std::function<void ()>                    completeHandler = nullptr;
    bool debug    = false;
    bool internal = false;
    QString charset;
    QNetworkAccessManager* manager = nullptr;
};

/**
 * @brief HttpClient 的辅助类,封装不希望暴露给客户端的数据和方法,使得 HttpClient 只暴露必要的 API 给客户端。
 */
class HttpClientPrivate {
    friend class HttpClient;

    HttpClientPrivate(const QString &url);
    ~HttpClientPrivate();

    /**
     * @brief 缓存 HttpClientPrivate 的数据成员
     *
     * @return 返回 HttpClientPrivateCache 缓存对象
     */
    HttpClientPrivateCache cache();

    /**
     * @brief 获取 Manager,如果传入了 manager 则返回此 manager,否则新创建一个 manager,默认会自动创建一个 manager,
     *        使用传入的 manager 则 interval 被设置为 false,自动创建的 manager 则设置 interval 为 true
     *
     * @return 返回 QNetworkAccessManager 对象
     */
    QNetworkAccessManager* getManager();

    /**
     * @brief 使用用户设定的 URL、请求头、参数等创建 Request
     *
     * @param d      HttpClientPrivate 的对象
     * @param method 请求的类型
     * @return 返回可用于执行请求的 QNetworkRequest
     */
    static QNetworkRequest createRequest(HttpClientPrivate *d, HttpClientRequestMethod method);

    /**
     * @brief 执行请求的辅助函数
     *
     * @param d      HttpClientPrivate 的对象
     * @param method 请求的类型
     */
    static void executeQuery(HttpClientPrivate *d, HttpClientRequestMethod method);

    /**
     * @brief 上传文件或者数据
     *
     * @param d     HttpClientPrivate 的对象
     * @param paths 要上传的文件的路径(path 和 data 不能同时使用)
     * @param data  要上传的文件的数据
     */
    static void upload(HttpClientPrivate *d, const QStringList &paths, const QByteArray &data);

    /**
     * @brief 使用 GET 进行下载,下载的文件保存到 savePath
     *
     * @param d        HttpClientPrivate 的对象
     * @param savePath 下载的文件保存路径
     */
    static void download(HttpClientPrivate *d, const QString &savePath);

    /**
     * @brief 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
     *
     * @param readyRead 有数据可读取时的回调 lambda 函数
     */
    static void download(HttpClientPrivate *d, std::function<void (const QByteArray &)> readyRead);

    /**
     * @brief 读取服务器响应的数据
     *
     * @param reply   请求的 QNetworkReply 对象
     * @param charset 请求响应的字符集,默认使用 UTF-8
     * @return 返回服务器端响应的字符串
     */
    static QString readReply(QNetworkReply *reply, const QString &charset = "UTF-8");

    /**
     * @brief 请求结束的处理函数
     *
     * @param cache          HttpClientPrivateCache 缓存对象
     * @param reply          QNetworkReply 对象,不能为 NULL
     * @param successMessage 请求成功的消息
     * @param failMessage    请求失败的消息
     */
    static void handleFinish(HttpClientPrivateCache cache, QNetworkReply *reply, const QString &successMessage, const QString &failMessage);

    /// 成员变量 //
    QString   url;                            // 请求的 URL
    QString   json;                           // 请求的参数使用 Json 格式
    QUrlQuery params;                         // 请求的参数使用 Form 格式
    QString   charset = "UTF-8";              // 请求响应的字符集
    QHash<QString, QString> headers;          // 请求头
    QNetworkAccessManager *manager = nullptr; // 执行 HTTP 请求的 QNetworkAccessManager 对象
    bool useJson  = false;                    // 为 true 时请求使用 Json 格式传递参数,否则使用 Form 格式传递参数
    bool debug    = false;                    // 为 true 时输出请求的 URL 和参数
    bool internal = true;                     // 是否使用自动创建的 manager

    std::function<void (const QString &)>   successHandler = nullptr; // 成功的回调函数,参数为响应的字符串
    std::function<void (const QString &, int)> failHandler = nullptr; // 失败的回调函数,参数为失败原因和 HTTP status code
    std::function<void ()>                 completeHandler = nullptr; // 结束的回调函数,无参数
};

HttpClientPrivate::HttpClientPrivate(const QString &url) : url(url) { }

HttpClientPrivate::~HttpClientPrivate() {
    manager         = nullptr;
    successHandler  = nullptr;
    failHandler     = nullptr;
    completeHandler = nullptr;
}

// 缓存 HttpClientPrivate 的数据成员
HttpClientPrivateCache HttpClientPrivate::cache() {
    HttpClientPrivateCache cache;

    cache.successHandler  = successHandler;
    cache.failHandler     = failHandler;
    cache.completeHandler = completeHandler;
    cache.debug    = debug;
    cache.internal = internal;
    cache.charset  = charset;
    cache.manager  = getManager();

    return cache;
}

// 执行请求的辅助函数
void HttpClientPrivate::executeQuery(HttpClientPrivate *d, HttpClientRequestMethod method) {
    // 1. 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
    // 2. 创建请求需要的变量
    // 3. 根据 method 执行不同的请求
    // 4. 请求结束时获取响应数据,在 handleFinish 中执行回调函数

    // [1] 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
    HttpClientPrivateCache cache = d->cache();

    // [2] 创建请求需要的变量
    QNetworkRequest request = HttpClientPrivate::createRequest(d, method);
    QNetworkReply    *reply = nullptr;

    // [3] 根据 method 执行不同的请求
    switch (method) {
    case HttpClientRequestMethod::GET:
        reply = cache.manager->get(request);
        break;
    case HttpClientRequestMethod::POST:
        reply = cache.manager->post(request, d->useJson ? d->json.toUtf8() : d->params.toString(QUrl::FullyEncoded).toUtf8());
        break;
    case HttpClientRequestMethod::PUT:
        reply = cache.manager->put(request, d->useJson ? d->json.toUtf8() : d->params.toString(QUrl::FullyEncoded).toUtf8());
        break;
    case HttpClientRequestMethod::DELETE:
        reply = cache.manager->deleteResource(request);
        break;
    default:
        break;
    }

    // [4] 请求结束时获取响应数据,在 handleFinish 中执行回调函数
    // 请求结束时一次性读取所有响应数据
    QObject::connect(reply, &QNetworkReply::finished, [=] {
        QString successMessage = HttpClientPrivate::readReply(reply, cache.charset.toUtf8());
        QString failMessage    = reply->errorString();
        HttpClientPrivate::handleFinish(cache, reply, successMessage, failMessage);
    });
}

// 使用 GET 进行下载,下载的文件保存到 savePath
void HttpClientPrivate::download(HttpClientPrivate *d, const QString &savePath) {
    // 1. 打开下载文件,如果打开文件出错,不进行下载
    // 2. 给请求结束的回调函数注入关闭释放文件的行为
    // 3. 调用下载的重载函数开始下载
    QFile *file = new QFile(savePath);

    // [1] 打开下载文件,如果打开文件出错,不进行下载
    if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        file->close();
        file->deleteLater();

        if (d->debug) {
            qDebug().noquote() << QString("[错误] 打开文件出错: %1").arg(savePath);
        }

        if (nullptr != d->failHandler) {
            d->failHandler(QString("[错误] 打开文件出错: %1").arg(savePath), -1);
        }

        return;
    }

    // [2] 给请求结束的回调函数注入关闭释放文件的行为
    std::function<void ()> userCompleteHandler     = d->completeHandler;
    std::function<void ()> injectedCompleteHandler = [=]() {
        // 请求结束后释放文件对象
        file->flush();
        file->close();
        file->deleteLater();

        // 执行用户指定的结束回调函数
        if (nullptr != userCompleteHandler) {
            userCompleteHandler();
        }
    };
    d->completeHandler = injectedCompleteHandler;

    // [3] 调用下载的重载函数开始下载
    HttpClientPrivate::download(d, [=](const QByteArray &data) {
        file->write(data);
    });
}

// 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
void HttpClientPrivate::download(HttpClientPrivate *d, std::function<void (const QByteArray &)> readyRead) {
    // 1. 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
    // 2. 创建请求需要的变量,执行请求
    // 3. 有数据可读取时回调 readyRead()
    // 4. 请求结束时获取响应数据,在 handleFinish 中执行回调函数

    // [1] 缓存需要的变量,在 lambda 中使用 = 捕捉使用 (不能使用引用 &,因为 d 已经被析构)
    HttpClientPrivateCache cache = d->cache();

    // [2] 创建请求需要的变量,执行请求
    QNetworkRequest request = HttpClientPrivate::createRequest(d, HttpClientRequestMethod::GET);
    QNetworkReply    *reply = cache.manager->get(request);

    // [3] 有数据可读取时回调 readyRead()
    QObject::connect(reply, &QNetworkReply::readyRead, [=] {
        readyRead(reply->readAll());
    });

    // [4] 请求结束时获取响应数据,在 handleFinish 中执行回调函数
    QObject::connect(reply, &QNetworkReply::finished, [=] {
        QString successMessage = "下载完成"; // 请求结束时一次性读取所有响应数据
        QString failMessage    = reply->errorString();
        HttpClientPrivate::handleFinish(cache, reply, successMessage, failMessage);
    });
}

// 上传文件或者数据的实现
void HttpClientPrivate::upload(HttpClientPrivate *d, const QStringList &paths, const QByteArray &data) {
    // 1. 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
    // 2. 创建 Form 表单的参数 Text Part
    // 3. 创建上传的 File Part
    //    3.1 使用文件创建 File Part
    //    3.2 使用数据创建 File Part
    // 4. 创建请求需要的变量,执行请求
    // 5. 请求结束时释放 multiPart 和打开的文件,获取响应数据,在 handleFinish 中执行回调函数

    // [1] 缓存需要的变量,在 lambda 中使用 = 捕捉使用 (不能使用引用 &,因为 d 已经被析构)
    HttpClientPrivateCache cache = d->cache();

    // [2] 创建 Form 表单的参数 Text Part
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    QList<QPair<QString, QString> > paramItems = d->params.queryItems();
    for (int i = 0; i < paramItems.size(); ++i) {
        QString name  = paramItems.at(i).first;
        QString value = paramItems.at(i).second;

        QHttpPart textPart;
        textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(name));
        textPart.setBody(value.toUtf8());
        multiPart->append(textPart);
    }

    if (paths.size() > 0) {
        // [3.1] 使用文件创建 File Part
        QString inputName = paths.size() == 1 ? "file" : "files"; // 一个文件时为 file,多个文件时为 files

        for (const QString &path : paths) {
            // path 为空时,不上传文件
            if (path.isEmpty()) {
                continue;
            }

            // We cannot delete the file now, so delete it with the multiPart
            QFile *file = new QFile(path, multiPart);

            // 如果文件打开失败,则释放资源返回,终止上传
            if(!file->open(QIODevice::ReadOnly)) {
                QString failMessage = QString("打开文件失败[%2]: %1").arg(path).arg(file->errorString());

                if (cache.debug) {
                    qDebug().noquote() << failMessage;
                }

                if (nullptr != cache.failHandler) {
                    cache.failHandler(failMessage, -1);
                }

                multiPart->deleteLater();
                return;
            }

            // 单个文件时,name 为服务器端获取文件的参数名,为 file
            // 多个文件时,name 为服务器端获取文件的参数名,为 files
            // 注意: 服务器是 Java 的则用 form-data
            // 注意: 服务器是 PHP  的则用 multipart/form-data
            QString disposition = QString("form-data; name=\"%1\"; filename=\"%2\"").arg(inputName).arg(file->fileName());
            QHttpPart filePart;
            filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
            filePart.setBodyDevice(file);
            multiPart->append(filePart);
        }
    } else {
        // [3.2] 使用数据创建 File Part
        QString   disposition = QString("form-data; name=\"file\"; filename=\"no-name\"");
        QHttpPart dataPart;
        dataPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
        dataPart.setBody(data);
        multiPart->append(dataPart);
    }

    // [4] 创建请求需要的变量,执行请求
    QNetworkRequest request = HttpClientPrivate::createRequest(d, HttpClientRequestMethod::UPLOAD);
    QNetworkReply    *reply = cache.manager->post(request, multiPart);

    // [5] 请求结束时释放 multiPart 和文件,获取响应数据,在 handleFinish 中执行回调函数
    QObject::connect(reply, &QNetworkReply::finished, [=] {
        multiPart->deleteLater(); // 释放资源: multiPart + file

        QString successMessage = HttpClientPrivate::readReply(reply, cache.charset); // 请求结束时一次性读取所有响应数据
        QString failMessage    = reply->errorString();
        HttpClientPrivate::handleFinish(cache, reply, successMessage, failMessage);
    });
}

// 获取 Manager,如果传入了 manager 则返回此 manager,否则新创建一个 manager,默认会自动创建一个 manager
QNetworkAccessManager* HttpClientPrivate::getManager() {
    return internal ? new QNetworkAccessManager() : manager;
}

// 使用用户设定的 URL、请求头、参数等创建 Request
QNetworkRequest HttpClientPrivate::createRequest(HttpClientPrivate *d, HttpClientRequestMethod method) {
    // 1. 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
    // 2. 调试时输出网址和参数
    // 3. 设置 Content-Type
    // 4. 添加请求头到 request 中

    bool get      = method == HttpClientRequestMethod::GET;
    bool upload   = method == HttpClientRequestMethod::UPLOAD;
    bool withForm = !get && !upload && !d->useJson; // PUT、POST 或者 DELETE 请求,且 useJson 为 false
    bool withJson = !get && !upload &&  d->useJson; // PUT、POST 或者 DELETE 请求,且 useJson 为 true

    // [1] 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
    if (get && !d->params.isEmpty()) {
        d->url += "?" + d->params.toString(QUrl::FullyEncoded);
    }

    // [2] 调试时输出网址和参数
    if (d->debug) {
        qDebug().noquote() << "[网址]" << d->url;

        if (withJson) {
            qDebug().noquote() << "[参数]" << d->json;
        } else if (withForm || upload) {
            QList<QPair<QString, QString> > paramItems = d->params.queryItems();
            QString buffer; // 避免多次调用 qDebug() 输入调试信息,每次 qDebug() 都有可能输出行号等

            // 按键值对的方式输出参数
            for (int i = 0; i < paramItems.size(); ++i) {
                QString name  = paramItems.at(i).first;
                QString value = paramItems.at(i).second;

                if (0 == i) {
                    buffer += QString("[参数] %1=%2\n").arg(name).arg(value);
                } else {
                    buffer += QString("       %1=%2\n").arg(name).arg(value);
                }
            }

            if (!buffer.isEmpty()) {
                qDebug().noquote() << buffer;
            }
        }
    }

    // [3] 设置 Content-Type
    // 如果是 POST 请求,useJson 为 true 时添加 Json 的请求头,useJson 为 false 时添加 Form 的请求头
    if (withForm) {
        d->headers["Content-Type"] = "application/x-www-form-urlencoded";
    } else if (withJson) {
        d->headers["Content-Type"] = "application/json; charset=utf-8";
    }

    // [4] 添加请求头到 request 中
    QNetworkRequest request(QUrl(d->url));
    for (auto i = d->headers.cbegin(); i != d->headers.cend(); ++i) {
        request.setRawHeader(i.key().toUtf8(), i.value().toUtf8());
    }

    return request;
}

// 读取服务器响应的数据
QString HttpClientPrivate::readReply(QNetworkReply *reply, const QString &charset) {
    QTextStream in(reply);
    QString result;
    in.setCodec(charset.toUtf8());

    while (!in.atEnd()) {
        result += in.readLine();
    }

    return result;
}

// 请求结束的处理函数
void HttpClientPrivate::handleFinish(HttpClientPrivateCache cache, QNetworkReply *reply, const QString &successMessage, const QString &failMessage) {
    // 1. 执行请求成功的回调函数
    // 2. 执行请求失败的回调函数
    // 3. 执行请求结束的回调函数
    // 4. 释放 reply 和 manager 对象

    if (reply->error() == QNetworkReply::NoError) {
        if (cache.debug) {
            qDebug().noquote() << QString("[结束] 成功: %1").arg(successMessage);
        }

        // [1] 执行请求成功的回调函数
        if (nullptr != cache.successHandler) {
            cache.successHandler(successMessage);
        }
    } else {
        if (cache.debug) {
            qDebug().noquote() << QString("[结束] 失败: %1").arg(failMessage);
        }

        // [2] 执行请求失败的回调函数
        if (nullptr != cache.failHandler) {
            cache.failHandler(failMessage, reply->error());
        }
    }

    // [3] 执行请求结束的回调函数
    if (nullptr != cache.completeHandler) {
        cache.completeHandler();
    }

    // [4] 释放 reply 和 manager 对象
    if (nullptr != reply) {
        reply->deleteLater();
    }

    if (cache.internal && nullptr != cache.manager) {
        cache.manager->deleteLater();
    }
}

/*-----------------------------------------------------------------------------|
 |                                 HttpClient                                  |
 |----------------------------------------------------------------------------*/

// 注意: 在异步请求中 HttpClient 的 HttpClientPrivate 成员变量 d 已经被析构,所以需要先缓存相关变量为栈对象,使用 = 以值的方式访问
HttpClient::HttpClient(const QString &url) : d(new HttpClientPrivate(url)) { }

HttpClient::~HttpClient() {
    delete d;
}

// 传入 QNetworkAccessManager 给多个请求共享
HttpClient& HttpClient::manager(QNetworkAccessManager *manager) {
    d->manager  = manager;
    d->internal = (nullptr == manager);

    return *this;
}

// 传入 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
HttpClient& HttpClient::debug(bool debug) {
    d->debug = debug;

    return *this;
}

// 添加一个请求的参数,可以多次调用添加多个参数
HttpClient& HttpClient::param(const QString &name, const QVariant &value) {
    d->params.addQueryItem(name, value.toString());

    return *this;
}

// 添加多个请求的参数
HttpClient& HttpClient::params(const QMap<QString, QVariant> &ps) {
    for (auto iter = ps.cbegin(); iter != ps.cend(); ++iter) {
        d->params.addQueryItem(iter.key(), iter.value().toString());
    }

    return *this;
}

// 添加请求的参数 (请求体),使用 Json 格式,例如 "{\"name\": \"Alice\"}"
HttpClient& HttpClient::json(const QString &json) {
    d->json    = json;
    d->useJson = true;

    return *this;
}

// 添加请求头
HttpClient& HttpClient::header(const QString &name, const QString &value) {
    d->headers[name] = value;

    return *this;
}

// 添加多个请求头
HttpClient& HttpClient::headers(const QMap<QString, QString> nameValues) {
    for (auto i = nameValues.cbegin(); i != nameValues.cend(); ++i) {
        d->headers[i.key()] = i.value();
    }

    return *this;
}

// 注册请求成功的回调函数
HttpClient& HttpClient::success(std::function<void (const QString &)> successHandler) {
    d->successHandler = successHandler;

    return *this;
}

// 注册请求失败的回调函数
HttpClient& HttpClient::fail(std::function<void (const QString &, int)> failHandler) {
    d->failHandler = failHandler;

    return *this;
}

// 注册请求结束的回调函数,不管成功还是失败都会执行
HttpClient& HttpClient::complete(std::function<void ()> completeHandler) {
    d->completeHandler = completeHandler;

    return *this;
}

// 设置请求响应的编码
HttpClient& HttpClient::charset(const QString &cs) {
    d->charset = cs;

    return *this;
}

// 执行 GET 请求
void HttpClient::get() {
    HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::GET);
}

// 执行 POST 请求
void HttpClient::post() {
    HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::POST);
}

// 执行 PUT 请求
void HttpClient::put() {
    HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::PUT);
}

// 执行 DELETE 请求
void HttpClient::remove() {
    HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::DELETE);
}

// 使用 GET 进行下载,下载的文件保存到 savePath
void HttpClient::download(const QString &savePath) {
    HttpClientPrivate::download(d, savePath);
}

// 上传文件
void HttpClient::upload(const QString &path) {
    QStringList paths = { path };
    HttpClientPrivate::upload(d, paths, QByteArray());
}

// 上传文件,文件的内容以及读取到 data 中
void HttpClient::upload(const QByteArray &data) {
    HttpClientPrivate::upload(d, QStringList(), data);
}

// 上传多个文件
void HttpClient::upload(const QStringList &paths) {
    HttpClientPrivate::upload(d, paths, QByteArray());
}
#include "HttpClient.h"

#include <QDebug>
#include <QApplication>
#include <QNetworkAccessManager>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    // 在代码块里执行网络访问,是为了测试 HttpClient 对象在被析构后,网络访问的回调函数仍然能正常执行
    {
        QString url("http://localhost:8080/api/rest");

        // [1] GET 请求无参数
        HttpClient(url).success([](const QString &response) {
            qDebug().noquote() << response;
        }).get();

        // [2] GET 请求有参数,有自定义 header,有失败的回调函数
        // 提示: 多个参数也可以传入 map: HttpClient(url).params({{"name", "诸葛亮"}, {"attackDamage", "99"}}).get(...);
        HttpClient(url).debug(true).param("name", "诸葛亮").param("value", 99).header("token", "md5sum").success([](const QString &response) {
            qDebug().noquote() << response;
        }).fail([](const QString &error, int errorCode) {
            qDebug().noquote() << error << errorCode;
        }).get();

        // [3] POST 请求,使用 param 添加参数,请求的参数使用 Form 格式
        HttpClient(url).debug(true).param("name", "卧龙").param("value", 99).success([](const QString &response) {
            qDebug().noquote() << response;
        }).post();

        // [4] PUT 请求,使用 json 添加参数,请求的参数使用 Json 格式
        HttpClient(url).debug(true).json("{\"name\": \"孔明\"}").success([](const QString &response) {
            qDebug().noquote() << response;
        }).put();

        // [5] DELETE 请求
        HttpClient(url).debug(true).success([](const QString &response) {
            qDebug().noquote() << response;
        }).remove();
    }

    {
        // [6] 下载: 保存到文件
        HttpClient("http://qtdebug.com/img/dog.png").debug(true).success([](const QString &response) {
            qDebug().noquote() << response;
        }).download("/Users/Biao/Desktop/dog-1.png");
    }

    {
        // 上传的同时能够传递参数
        // [7] 上传一个文件
        HttpClient("http://localhost:8080/api/upload").debug(true).upload(QString("/Users/Biao/Pictures/ade.jpg"));

        // [8] 上传多个文件
        HttpClient("http://localhost:8080/api/uploads").debug(true).param("name", "Biao").success([](const QString &response) {
            qDebug().noquote() << response;
        }).upload({ "/Users/Biao/Pictures/ade.jpg", "/Users/Biao/Pictures/avatar.jpg" });
    }

    {
        // [9] 共享 QNetworkAccessManager
        // 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,调用 manager()
        // 使用共享的 QNetworkAccessManager,它不会被 HttpClient 删除,需要我们自己不用的时候删除它。
        // 如果下面的代码不传入 QNetworkAccessManager,从任务管理器里可以看到创建了几千个线程。
        QNetworkAccessManager *manager = new QNetworkAccessManager();
        for (int i = 0; i < 5000; ++i) {
            HttpClient("http://localhost:8080/api/rest").manager(manager).success([=](const QString &response) {
                qDebug().noquote() << response << ", " << i;
            }).get();
        }
    }

    return a.exec();
}

 

使用过程中遇到的问题见下文

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt使用d指针和q指针来实现封装和隐藏内部实现细节,以及提供更好的代码可读性和可维护性。同时,Qt还使用命名空间来组织和管理类和函数。 下面是一个示例代码,演示了Qt中如何使用d指针、q指针和命名空间: ```cpp // counter.h #ifndef COUNTER_H #define COUNTER_H #include <QObject> namespace MyNamespace { class CounterPrivate; // 前向声明 class Counter : public QObject { Q_OBJECT public: explicit Counter(QObject *parent = nullptr); ~Counter(); void increment(); void decrement(); int value() const; private: CounterPrivate *d_ptr; // d指针 }; } #endif // COUNTER_H // counter.cpp #include "counter.h" namespace MyNamespace { class CounterPrivate { public: int count; }; Counter::Counter(QObject *parent) : QObject(parent), d_ptr(new CounterPrivate) { d_ptr->count = 0; } Counter::~Counter() { delete d_ptr; } void Counter::increment() { ++d_ptr->count; } void Counter::decrement() { --d_ptr->count; } int Counter::value() const { return d_ptr->count; } } // main.cpp #include <QGuiApplication> #include <QQmlApplicationEngine> #include "counter.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); MyNamespace::Counter counter; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("Counter", &counter); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); } ``` 在这个示例中,我们定义了一个名为Counter的类,它位于MyNamespace命名空间中。Counter类使用了d指针来隐藏内部实现细节,并提供了increment、decrement和value等公共接口来操作计数器的值。在main.cpp中,我们创建了Counter的实例,并将其作为上下文属性传递给QML引擎。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值