• Qt 访问网络的 HttpClient(封装QNetworkAccessManager,且有服务端)


    Qt 使用 QNetworkAccessManager 访问网络,这里对其进行了简单的封装,访问网络的代码可以简化为:

    1
    2
    3
    HttpClient("http://localhost:8080/device").get([](const QString &response) {
    qDebug() << response;
    });

    更多的使用方法请参考 main() 里的例子。HttpClient 的实现为 HttpClient.h 和 HttpClient.cpp 部分。

    main.cpp

    main() 函数里展示了 HttpClient 的使用示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    #include "HttpClient.h"
     
    #include <QDebug>
    #include <QFile>
    #include <QApplication>
    #include <QNetworkAccessManager>
     
    int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
     
    {
    // 在代码块里执行网络访问,是为了测试 HttpClient 对象在被析构后,网络访问的回调函数仍然能正常执行
    // [[1]] GET 请求无参数
    HttpClient("http://localhost:8080/device").get([](const QString &response) {
    qDebug() << response;
    });
     
    // [[2]] GET 请求有参数,有自定义 header
    HttpClient("http://localhost:8080/signIn")
    .addParam("id", "1")
    .addParam("name", "诸葛亮")
    .addHeader("token", "123AS#D")
    .get([](const QString &response) {
    qDebug() << response;
    });
     
    // [[3]] POST 请求有参数,有自定义 header
    HttpClient("http://localhost:8080/signIn")
    .addParam("id", "2")
    .addParam("name", "卧龙")
    .addHeader("token", "DER#2J7")
    .addHeader("content-type", "application/x-www-form-urlencoded")
    .post([](const QString &response) {
    qDebug() << response;
    });
     
    // [[4]] 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,调用 useManager()
    // 使用共享的 QNetworkAccessManager,它不会被 HttpClient 删除。
    // 如果下面的代码不传入 QNetworkAccessManager,从任务管理器里可以看到创建了几千个线程。
    QNetworkAccessManager *manager = new QNetworkAccessManager();
    for (int i = 0; i < 5000; ++i) {
    HttpClient("http://localhost:8080/device").useManager(manager).get([=](const QString &response) {
    qDebug() << response << ", " << i;
    });
    }
     
    // [[5]] 下载
    QFile *file = new QFile("dog.png");
    if (file->open(QIODevice::WriteOnly)) {
    HttpClient("http://xtuer.github.io/img/dog.png").setDebug(true).download([=](const QByteArray &data) {
    file->write(data);
    }, [=] {
    file->flush();
    file->close();
    file->deleteLater();
     
    qDebug() << "Download file finished";
    });
    }
     
    // [[6]] 上传
    HttpClient("http://localhost:8080/upload").upload("/Users/Biao/Pictures/ade.jpg");
    }
     
    return a.exec();
    }

    HttpClient.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    #ifndef HTTPCLIENT_H
    #define HTTPCLIENT_H
     
    #include <functional>
     
    class QString;
    class QByteArray;
    struct HttpClientPrivate;
    class QNetworkReply;
    class QNetworkAccessManager;
     
    class HttpClient {
    public:
    HttpClient(const QString &url);
    ~HttpClient();
     
    /**
    * @brief 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,
    * 可以使用传人的 QNetworkAccessManager,它不会被 HttpClient 删除。
    * 如果没有使用 useManager() 传入一个 QNetworkAccessManager,则 HttpClient 会自动的创建一个,并且在网络访问完成后删除它。
    * @param manager QNetworkAccessManager 对象
    * @return 返回 HttpClient 的引用,可以用于链式调用
    */
    HttpClient& useManager(QNetworkAccessManager *manager);
     
    /**
    * @brief 参数 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
    * @param debug 是否启用调试模式
    * @return 返回 HttpClient 的引用,可以用于链式调用
    */
    HttpClient& debug(bool debug);
     
    /**
    * @brief 增加参数
    * @param name 参数的名字
    * @param value 参数的值
    * @return 返回 HttpClient 的引用,可以用于链式调用
    */
    HttpClient& addParam(const QString &name, const QString &value);
     
    /**
    * @brief 增加访问头
    * @param header 访问头的名字
    * @param value 访问头的值
    * @return 返回 HttpClient 的引用,可以用于链式调用
    */
    HttpClient& addHeader(const QString &header, const QString &value);
     
    /**
    * @brief 添加 POST 表单使用的头信息,等价于 addHeader("content-type", "application/x-www-form-urlencoded")
    * @return 返回 HttpClient 的引用,可以用于链式调用
    */
    HttpClient& addFormHeader();
     
    /**
    * @brief 执行 GET 请求
    * @param successHandler 请求成功的回调 lambda 函数
    * @param errorHandler 请求失败的回调 lambda 函数
    * @param encoding 请求响应的编码
    */
    void get(std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler = NULL,
    const char *encoding = "UTF-8");
     
    /**
    * @brief 执行 POST 请求
    * @param successHandler 请求成功的回调 lambda 函数
    * @param errorHandler 请求失败的回调 lambda 函数
    * @param encoding 请求响应的编码
    */
    void post(std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler = NULL,
    const char *encoding = "UTF-8");
     
    /**
    * @brief 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
    * @param readyRead 有数据可读取时的回调 lambda 函数
    * @param finishHandler 请求处理完成后的回调 lambda 函数
    * @param errorHandler 请求失败的回调 lambda 函数
    */
    void download(std::function<void (const QByteArray &)> readyRead,
    std::function<void ()> finishHandler = NULL,
    std::function<void (const QString &)> errorHandler = NULL);
     
    /**
    * @brief 上传文件
    * @param path 要上传的文件的路径
    * @param successHandler 请求成功的回调 lambda 函数
    * @param errorHandler 请求失败的回调 lambda 函数
    * @param encoding 请求响应的编码
    */
    void upload(const QString &path, std::function<void (const QString &)> successHandler = NULL,
    std::function<void (const QString &)> errorHandler = NULL,
    const char *encoding = "UTF-8");
     
    private:
    /**
    * @brief 执行请求的辅助函数
    * @param posted 为 true 表示 POST 请求,为 false 表示 GET 请求
    * @param successHandler 请求成功的回调 lambda 函数
    * @param errorHandler 请求失败的回调 lambda 函数
    * @param encoding 请求响应的编码
    */
    void execute(bool posted,
    std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler,
    const char *encoding);
     
    /**
    * @brief 读取服务器响应的数据
    * @param reply 请求的 QNetworkReply 对象
    * @param encoding 请求响应的编码,默认使用 UTF-8
    * @return 服务器端响应的字符串
    */
    QString readResponse(QNetworkReply *reply, const char *encoding = "UTF-8");
     
    HttpClientPrivate *d;
    };
     
    #endif // HTTPCLIENT_H

    HttpClient.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    #include "HttpClient.h"
     
    #include <QDebug>
    #include <QFile>
    #include <QHash>
    #include <QUrlQuery>
    #include <QNetworkReply>
    #include <QNetworkRequest>
    #include <QNetworkAccessManager>
    #include <QHttpPart>
    #include <QHttpMultiPart>
     
    struct HttpClientPrivate {
    HttpClientPrivate(const QString &url) : url(url), networkAccessManager(NULL), useInternalNetworkAccessManager(true), debug(false) {}
     
    QString url; // 请求的 URL
    QUrlQuery params; // 请求的参数
    QHash<QString, QString> headers; // 请求的头
    QNetworkAccessManager *networkAccessManager;
    bool useInternalNetworkAccessManager; // 是否使用内部的 QNetworkAccessManager
    bool debug;
    };
     
    HttpClient::HttpClient(const QString &url) : d(new HttpClientPrivate(url)) {
    // qDebug() << "HttpClient";
    }
     
    HttpClient::~HttpClient() {
    // qDebug() << "~HttpClient";
    delete d;
    }
     
    HttpClient &HttpClient::useManager(QNetworkAccessManager *manager) {
    d->networkAccessManager = manager;
    d->useInternalNetworkAccessManager = false;
    return *this;
    }
     
    // 传入 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
    HttpClient &HttpClient::debug(bool debug) {
    d->debug = debug;
    return *this;
    }
     
    // 增加参数
    HttpClient &HttpClient::addParam(const QString &name, const QString &value) {
    d->params.addQueryItem(name, value);
    return *this;
    }
     
    // 增加访问头
    HttpClient &HttpClient::addHeader(const QString &header, const QString &value) {
    d->headers[header] = value;
    return *this;
    }
     
    HttpClient &HttpClient::addFormHeader() {
    return addHeader("content-type", "application/x-www-form-urlencoded");
    }
     
    // 执行 GET 请求
    void HttpClient::get(std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler,
    const char *encoding) {
    execute(false, successHandler, errorHandler, encoding);
    }
     
    // 执行 POST 请求
    void HttpClient::post(std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler,
    const char *encoding) {
    execute(true, successHandler, errorHandler, encoding);
    }
     
    // 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
    void HttpClient::download(std::function<void (const QByteArray &)> readyRead,
    std::function<void ()> finishHandler,
    std::function<void (const QString &)> errorHandler) {
    // 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
    if (!d->params.isEmpty()) {
    d->url += "?" + d->params.toString(QUrl::FullyEncoded);
    }
     
    if (d->debug) {
    qDebug() << QString("URL: %1?%2").arg(d->url).arg(d->params.toString());
    }
     
    QUrl urlx(d->url);
    QNetworkRequest request(urlx);
    bool internal = d->useInternalNetworkAccessManager;
    QNetworkAccessManager *manager = internal ? new QNetworkAccessManager() : d->networkAccessManager;
    QNetworkReply *reply = manager->get(request);
     
    // 有数据可读取时回调 readyRead()
    QObject::connect(reply, &QNetworkReply::readyRead, [=] {
    readyRead(reply->readAll());
    });
     
    // 请求结束
    QObject::connect(reply, &QNetworkReply::finished, [=] {
    if (reply->error() == QNetworkReply::NoError && NULL != finishHandler) {
    finishHandler();
    }
     
    // 释放资源
    reply->deleteLater();
    if (internal) {
    manager->deleteLater();
    }
    });
     
    // 请求错误处理
    QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
    if (NULL != errorHandler) {
    errorHandler(reply->errorString());
    }
    });
    }
     
    void HttpClient::upload(const QString &path,
    std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler,
    const char *encoding) {
    if (d->debug) {
    qDebug() << QString("URL: %1").arg(d->url);
    }
     
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
     
    QFile *file = new QFile(path);
    file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
     
    // 如果文件打开失败,则释放资源返回
    if(!file->open(QIODevice::ReadOnly)) {
    if (NULL != errorHandler) {
    errorHandler(QString("文件打开失败: %1").arg(file->errorString()));
    multiPart->deleteLater();
    return;
    }
    }
     
    // 表明是文件上传
    QString disposition = QString("form-data; name="file"; filename="%1"").arg(file->fileName());
    QHttpPart imagePart;
    imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
    imagePart.setBodyDevice(file);
    multiPart->append(imagePart);
     
    bool internal = d->useInternalNetworkAccessManager;
    QNetworkRequest request(QUrl(d->url));
    QNetworkAccessManager *manager = internal ? new QNetworkAccessManager() : d->networkAccessManager;
    QNetworkReply *reply = manager->post(request, multiPart);
    multiPart->setParent(reply);
     
    // 请求结束时一次性读取所有响应数据
    QObject::connect(reply, &QNetworkReply::finished, [=] {
    if (reply->error() == QNetworkReply::NoError && NULL != successHandler) {
    successHandler(readResponse(reply, encoding)); // 成功执行
    }
     
    // 释放资源
    reply->deleteLater();
    if (internal) {
    manager->deleteLater();
    }
    });
     
    // 请求错误处理
    QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
    if (NULL != errorHandler) {
    errorHandler(reply->errorString());
    }
    });
    }
     
    // 执行请求的辅助函数
    void HttpClient::execute(bool posted,
    std::function<void (const QString &)> successHandler,
    std::function<void (const QString &)> errorHandler,
    const char *encoding) {
    // 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
    if (!posted && !d->params.isEmpty()) {
    d->url += "?" + d->params.toString(QUrl::FullyEncoded);
    }
     
    if (d->debug) {
    qDebug() << QString("URL: %1?%2").arg(d->url).arg(d->params.toString());
    }
     
    QUrl urlx(d->url);
    QNetworkRequest request(urlx);
     
    // 把请求的头添加到 request 中
    QHashIterator<QString, QString> iter(d->headers);
    while (iter.hasNext()) {
    iter.next();
    request.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8());
    }
     
    // 注意: 不能在 Lambda 表达式里使用 HttpClient 对象的成员数据,因其可能在网络访问未结束时就已经被析构掉了,
    // 所以如果要使用它的相关数据,定义一个局部变量来保存其数据,然后在 Lambda 表达式里访问这个局部变量
     
    // 如果不使用外部的 manager 则创建一个新的,在访问完成后会自动删除掉
    bool internal = d->useInternalNetworkAccessManager;
    QNetworkAccessManager *manager = internal ? new QNetworkAccessManager() : d->networkAccessManager;
    QNetworkReply *reply = posted ? manager->post(request, d->params.toString(QUrl::FullyEncoded).toUtf8()) : manager->get(request);
     
    // 请求结束时一次性读取所有响应数据
    QObject::connect(reply, &QNetworkReply::finished, [=] {
    if (reply->error() == QNetworkReply::NoError && NULL != successHandler) {
    successHandler(readResponse(reply, encoding)); // 成功执行
    }
     
    // 释放资源
    reply->deleteLater();
    if (internal) {
    manager->deleteLater();
    }
    });
     
    // 请求错误处理
    QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
    if (NULL != errorHandler) {
    errorHandler(reply->errorString());
    }
    });
    }
     
    QString HttpClient::readResponse(QNetworkReply *reply, const char *encoding) {
    QTextStream in(reply);
    QString result;
    in.setCodec(encoding);
     
    while (!in.atEnd()) {
    result += in.readLine();
    }
     
    return result;
    }

    服务器端处理请求的代码

    这里的服务器端处理请求的代码使用了 SpringMVC 实现,作为参考,可以使用其他语言实现,例如 PHP,C#。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    @GetMapping("/device")
    @ResponseBody
    public String detectDevice(Device device) {
    if (device.isMobile()) {
    return "Mobile";
    } else if (device.isTablet()) {
    return "Tablet";
    } else {
    return "Desktop";
    }
    }
     
    // URL: /signIn?id=1&name=xxx
    @GetMapping("/signIn")
    @ResponseBody
    public String singInGet(@RequestParam String id,
    @RequestParam String name,
    @RequestHeader(value="token", required=false) String token) throws Exception {
    name = new String(name.getBytes("iso8859-1"), "UTF-8");
    return String.format("GET: id: %s, name: %s, token: %s", id, name, token);
    }
     
    // URL: /signIn
    @PostMapping("/signIn")
    @ResponseBody
    public String singInPost(@RequestParam String id,
    @RequestParam String name,
    @RequestHeader(value="token", required=false) String token) throws Exception {
    return String.format("POST: id: %s, name: %s, token: %s", id, name, token);
    }
     
    // 上传
    @PostMapping("/upload")
    @ResponseBody
    public Result uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
    System.out.println(file.getOriginalFilename());
    file.transferTo(new File("/Users/Biao/Desktop/" + file.getOriginalFilename()));
     
    return Result.ok("OK", file.getOriginalFilename());
    }
     
     
    http://www.qtdebug.com/qt-httpclient/
  • 相关阅读:
    Codeforces Round #545 (div 1.)
    THUSC 2017 大魔法师
    loj #6216. 雪花挂饰
    [NOI Online #2 提高组]涂色游戏
    [NOI Online #2 提高组]子序列问题
    [NOI Online #1 入门组]跑步
    备战noip week7
    [NOI Online #3 提高组]优秀子序列
    20201017校测
    springboot基于maven多模块项目搭建(直接启动webApplication)
  • 原文地址:https://www.cnblogs.com/findumars/p/5515510.html
Copyright © 2020-2023  润新知