一、前言

Qt 使用 QNetworkAccessManager 访问网络,这里对其进行了简单的封装,访问网络的代码可以简化为:
  1. // [[1]] GET 请求无参数
  2. HttpClient("http://localhost:8080/device").success([](const QString &response) {
  3. qDebug() << response;
  4. }).get();

二、main.cpp

main() 函数里展示了 HttpClient 的使用示例。
  1. #include <QApplication>
  2. #include <QNetworkAccessManager>
  3. #include <QDebug>
  4. #include <QFile>
  5. #include "HttpClient.h"
  6. int main(int argc, char *argv[]) {
  7. QApplication a(argc, argv);
  8. // 在代码块里执行网络访问,是为了测试 HttpClient 对象在被析构后,网络访问的回调函数仍然能正常执行
  9. {
  10. // [[1]] GET 请求无参数
  11. HttpClient("http://localhost:8080/device").success([](const QString &response) {
  12. qDebug() << response;
  13. }).get();
  14. // [[2]] GET 请求有参数,有自定义 header
  15. HttpClient("http://localhost:8080/device").success([](const QString &response) {
  16. qDebug() << response;
  17. }).param("id", "1").param("name", "诸葛亮").header("token", "123AS#D").get();
  18. // [[3]] POST 请求有参数,有自定义 header
  19. HttpClient("http://localhost:8080/device").success([](const QString &response) {
  20. qDebug() << response;
  21. }).param("id", "2").param("name", "卧龙").header("token", "DER#2J7")
  22. .header("content-type", "application/x-www-form-urlencoded").post();
  23. // [[4]] 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,调用 useManager()
  24. // 使用共享的 QNetworkAccessManager,它不会被 HttpClient 删除。
  25. // 如果下面的代码不传入 QNetworkAccessManager,从任务管理器里可以看到创建了几千个线程。
  26. QNetworkAccessManager *manager = new QNetworkAccessManager();
  27. for (int i = 0; i < 5000; ++i) {
  28. HttpClient("http://localhost:8080/device").success([=](const QString &response) {
  29. qDebug() << response << ", " << i;
  30. }).manager(manager).get();
  31. }
  32. }
  33. return a.exec();
  34. }

三、HttpClient.h

  1. #ifndef HTTPCLIENT_H
  2. #define HTTPCLIENT_H
  3. #include <functional>
  4. #include <QMap>
  5. #include <QVariant>
  6. #include <QStringList>
  7. #include <QNetworkReply>
  8. #include <QNetworkRequest>
  9. #include <QNetworkAccessManager>
  10. class HttpClientPrivate;
  11. /**
  12. * 对 QNetworkAccessManager 简单封装的 HTTP 访问客户端,简化 GET、POST、PUT、DELETE、上传、下载等操作。
  13. * 在执行请求前设置需要的参数和回调函数:
  14. * 1. 调用 header() 设置请求头
  15. * 2. 调用 param() 设置参数,使用 Form 表单的方式提交请求,GET 请求的 query parameters 也可以用它设置
  16. * 3. 调用 json() 设置 JSON 字符串的 request body,Content-Type 为 application/json,
  17. * 当然也可以不是 JSON 格式,因使用 request body 的情况多数是使用 JSON 格式传递复杂对象,故命名为 json
  18. * 4. 调用 success() 注册请求成功的回调函数
  19. * 5. 调用 fail() 注册请求失败的回调函数
  20. * 6. 调用 complete() 注册请求结束的回调函数
  21. * success(), fail(), complete() 的回调函数是可选的,根据需要注册对应的回调函数,也可以一个都不注册
  22. * 然后根据请求的类型调用 get(), post(), put(), remove(), download(), upload() 执行 HTTP 请求
  23. *
  24. * 默认 HttpClient 会创建一个 QNetworkAccessManager,如果不想使用默认的,调用 manager() 传入即可。
  25. * 调用 debug(true) 设置为调试模式,输出调试信息如 URL、参数等。
  26. */
  27. class HttpClient {
  28. public:
  29. HttpClient(const QString &url);
  30. ~HttpClient();
  31. void stop2();
  32. /**
  33. * @brief 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,
  34. * 可以传入 QNetworkAccessManager 给多个请求共享 (它不会被 HttpClient 删除,用户需要自己手动删除)。
  35. * 如果没有使用 manager() 传入一个 QNetworkAccessManager,则 HttpClient 会自动的创建一个,并且在网络访问完成后自动删除它。
  36. *
  37. * @param manager 执行 HTTP 请求的 QNetworkAccessManager 对象
  38. * @return 返回 HttpClient 的引用,可以用于链式调用
  39. */
  40. HttpClient& manager(QNetworkAccessManager *manager);
  41. /**
  42. * @brief 参数 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
  43. *
  44. * @param debug 是否启用调试模式
  45. * @return 返回 HttpClient 的引用,可以用于链式调用
  46. */
  47. HttpClient& debug(bool debug);
  48. /**
  49. * @brief 添加一个请求的参数,可以多次调用添加多个参数
  50. *
  51. * @param name 参数的名字
  52. * @param value 参数的值
  53. * @return 返回 HttpClient 的引用,可以用于链式调用
  54. */
  55. HttpClient& param(const QString &name, const QVariant &value);
  56. /**
  57. * @brief 添加多个请求的参数
  58. *
  59. * @param ps QMap 类型的参数,key 为参数名,value 为参数值
  60. * 可以使用 {{"name", 1}, {"box", 2}} 的方式创建 QMap 对象
  61. * @return 返回 HttpClient 的引用,可以用于链式调用
  62. */
  63. HttpClient& params(const QMap<QString, QVariant> &ps);
  64. /**
  65. * @brief 添加请求的参数 (请求体),使用 Json 格式,例如 "{\"name\": \"Alice\"}"
  66. *
  67. * @param json 请求体 (request body) 为 Json 格式的参数字符串
  68. * @return 返回 HttpClient 的引用,可以用于链式调用
  69. */
  70. HttpClient& json(const QString &json);
  71. /**
  72. * @brief 添加请求头
  73. *
  74. * @param name 请求头的名字
  75. * @param value 请求头的值
  76. * @return 返回 HttpClient 的引用,可以用于链式调用
  77. */
  78. HttpClient& header(const QString &name, const QString &value);
  79. /**
  80. * @brief 添加多个请求头
  81. *
  82. * @param nameValues 请求头的名字和值对
  83. * 可以使用 {{"name", 1}, {"box", 2}} 的方式创建 QMap 对象
  84. * @return 返回 HttpClient 的引用,可以用于链式调用
  85. */
  86. HttpClient& headers(const QMap<QString, QString> nameValues);
  87. /**
  88. * @brief 注册请求成功的回调函数
  89. *
  90. * @param successHandler 成功的回调函数,参数为响应的字符串
  91. * @return 返回 HttpClient 的引用,可以用于链式调用
  92. */
  93. HttpClient& success(std::function<void(const QString &)> successHandler);
  94. /**
  95. * @brief 注册请求失败的回调函数
  96. *
  97. * @param failHandler 失败的回调函数,参数为失败原因和 HTTP 状态码
  98. * @return 返回 HttpClient 的引用,可以用于链式调用
  99. */
  100. HttpClient& fail(std::function<void(const QString &, int)> failHandler);
  101. /**
  102. * @brief 注册请求结束的回调函数,不管成功还是失败请求结束后都会执行
  103. *
  104. * @param completeHandler 完成的回调函数,无参数
  105. * @return 返回 HttpClient 的引用,可以用于链式调用
  106. */
  107. HttpClient& complete(std::function<void()> completeHandler);
  108. /**
  109. * @brief 设置请求响应的字符集,默认使用 UTF-8
  110. *
  111. * @param cs 字符集
  112. * @return 返回 HttpClient 的引用,可以用于链式调用
  113. */
  114. HttpClient& charset(const QString &cs);
  115. /**
  116. * @brief 执行 GET 请求
  117. */
  118. void get();
  119. /**
  120. * @brief 执行 POST 请求
  121. */
  122. void post();
  123. /**
  124. * @brief 执行 PUT 请求
  125. */
  126. void put();
  127. /**
  128. * @brief 执行 DELETE 请求,由于 delete 是 C++ 的运算符,所以用同义词 remove
  129. * 注意: Qt 提供的 DELETE 请求是不支持传递参数的,
  130. * 请参考 QNetworkAccessManager::deleteResource(const QNetworkRequest &request)
  131. */
  132. void remove();
  133. /**
  134. * @brief 使用 GET 进行下载,下载的文件保存到 savePath
  135. *
  136. * @param savePath 下载的文件保存路径
  137. */
  138. void download(const QString &savePath);
  139. /**
  140. * @brief 上传单个文件
  141. * 使用 POST 上传,服务器端获取文件的参数名为 file
  142. *
  143. * @param path 要上传的文件的路径
  144. */
  145. void upload(const QString &path);
  146. /**
  147. * @brief 上传文件,文件的内容已经读取到 data 中
  148. * 使用 POST 上传,服务器端获取文件的参数名为 file
  149. *
  150. * @param path 要上传的文件的路径
  151. */
  152. void upload(const QByteArray &data);
  153. /**
  154. * @brief 上传多个文件
  155. * 使用 POST 上传,服务器端获取文件的参数名为 files
  156. *
  157. * @param paths 要上传的文件的路径
  158. */
  159. void upload(const QStringList &paths);
  160. private:
  161. HttpClientPrivate *d;
  162. };
  163. #endif // HTTPCLIENT_H

四、HttpClient.cpp

  1. #include "HttpClient.h"
  2. #include <QDebug>
  3. #include <QFile>
  4. #include <QHash>
  5. #include <QUrlQuery>
  6. #include <QHttpPart>
  7. #include <QHttpMultiPart>
  8. /*-----------------------------------------------------------------------------|
  9. | HttpClientPrivate |
  10. |----------------------------------------------------------------------------*/
  11. /**
  12. * @brief 请求的类型
  13. *
  14. * 注: UPLOAD 不是 HTTP Method,只是为了上传时对请求进行特殊处理而定义的
  15. */
  16. enum class HttpClientRequestMethod {
  17. GET, POST, PUT, DELETE, UPLOAD
  18. };
  19. /**
  20. * @brief 缓存 HttpClientPrivate 的数据成员,方便在异步 lambda 中使用 = 以值的方式访问。
  21. */
  22. class HttpClientPrivateCache {
  23. public:
  24. std::function<void(const QString &)> successHandler = nullptr;
  25. std::function<void(const QString &, int)> failHandler = nullptr;
  26. std::function<void()> completeHandler = nullptr;
  27. bool debug = false;
  28. bool internal = false;
  29. QString charset;
  30. QNetworkAccessManager* manager = nullptr;
  31. };
  32. /**
  33. * @brief HttpClient 的辅助类,封装不希望暴露给客户端的数据和方法,使得 HttpClient 只暴露必要的 API 给客户端。
  34. */
  35. class HttpClientPrivate {
  36. friend class HttpClient;
  37. HttpClientPrivate(const QString &url);
  38. ~HttpClientPrivate();
  39. void stop1();
  40. /**
  41. * @brief 缓存 HttpClientPrivate 的数据成员
  42. *
  43. * @return 返回 HttpClientPrivateCache 缓存对象
  44. */
  45. HttpClientPrivateCache cache();
  46. /**
  47. * @brief 获取 Manager,如果传入了 manager 则返回此 manager,否则新创建一个 manager,默认会自动创建一个 manager,
  48. * 使用传入的 manager 则 interval 被设置为 false,自动创建的 manager 则设置 interval 为 true
  49. *
  50. * @return 返回 QNetworkAccessManager 对象
  51. */
  52. QNetworkAccessManager* getManager();
  53. /**
  54. * @brief 使用用户设定的 URL、请求头、参数等创建 Request
  55. *
  56. * @param d HttpClientPrivate 的对象
  57. * @param method 请求的类型
  58. * @return 返回可用于执行请求的 QNetworkRequest
  59. */
  60. static QNetworkRequest createRequest(HttpClientPrivate *d, HttpClientRequestMethod method);
  61. /**
  62. * @brief 执行请求的辅助函数
  63. *
  64. * @param d HttpClientPrivate 的对象
  65. * @param method 请求的类型
  66. */
  67. static void executeQuery(HttpClientPrivate *d, HttpClientRequestMethod method);
  68. /**
  69. * @brief 上传文件或者数据
  70. *
  71. * @param d HttpClientPrivate 的对象
  72. * @param paths 要上传的文件的路径(path 和 data 不能同时使用)
  73. * @param data 要上传的文件的数据
  74. */
  75. static void upload(HttpClientPrivate *d, const QStringList &paths, const QByteArray &data);
  76. /**
  77. * @brief 使用 GET 进行下载,下载的文件保存到 savePath
  78. *
  79. * @param d HttpClientPrivate 的对象
  80. * @param savePath 下载的文件保存路径
  81. */
  82. static void download(HttpClientPrivate *d, const QString &savePath);
  83. /**
  84. * @brief 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
  85. *
  86. * @param readyRead 有数据可读取时的回调 lambda 函数
  87. */
  88. static void download(HttpClientPrivate *d, std::function<void(const QByteArray &)> readyRead);
  89. /**
  90. * @brief 读取服务器响应的数据
  91. *
  92. * @param reply 请求的 QNetworkReply 对象
  93. * @param charset 请求响应的字符集,默认使用 UTF-8
  94. * @return 返回服务器端响应的字符串
  95. */
  96. static QString readReply(QNetworkReply *reply, const QString &charset = "UTF-8");
  97. /**
  98. * @brief 请求结束的处理函数
  99. *
  100. * @param cache HttpClientPrivateCache 缓存对象
  101. * @param reply QNetworkReply 对象,不能为 NULL
  102. * @param successMessage 请求成功的消息
  103. * @param failMessage 请求失败的消息
  104. */
  105. static void handleFinish(HttpClientPrivateCache cache, QNetworkReply *reply, const QString &successMessage, const QString &failMessage);
  106. /////////////////////////////////////////////////// 成员变量 //////////////////////////////////////////////
  107. QString url; // 请求的 URL
  108. QString json; // 请求的参数使用 Json 格式
  109. QUrlQuery params; // 请求的参数使用 Form 格式
  110. QString charset = "UTF-8"; // 请求响应的字符集
  111. QHash<QString, QString> headers; // 请求头
  112. QNetworkAccessManager *manager = nullptr; // 执行 HTTP 请求的 QNetworkAccessManager 对象
  113. bool useJson = false; // 为 true 时请求使用 Json 格式传递参数,否则使用 Form 格式传递参数
  114. bool debug = false; // 为 true 时输出请求的 URL 和参数
  115. bool internal = true; // 是否使用自动创建的 manager
  116. std::function<void(const QString &)> successHandler = nullptr; // 成功的回调函数,参数为响应的字符串
  117. std::function<void(const QString &, int)> failHandler = nullptr; // 失败的回调函数,参数为失败原因和 HTTP status code
  118. std::function<void()> completeHandler = nullptr; // 结束的回调函数,无参数
  119. };
  120. HttpClientPrivate::HttpClientPrivate(const QString &url) : url(url) { }
  121. HttpClientPrivate::~HttpClientPrivate() {
  122. manager = nullptr;
  123. successHandler = nullptr;
  124. failHandler = nullptr;
  125. completeHandler = nullptr;
  126. }
  127. void HttpClientPrivate::stop1()
  128. {
  129. manager->deleteLater();
  130. }
  131. // 缓存 HttpClientPrivate 的数据成员
  132. HttpClientPrivateCache HttpClientPrivate::cache() {
  133. HttpClientPrivateCache cache;
  134. cache.successHandler = successHandler;
  135. cache.failHandler = failHandler;
  136. cache.completeHandler = completeHandler;
  137. cache.debug = debug;
  138. cache.internal = internal;
  139. cache.charset = charset;
  140. cache.manager = getManager();
  141. return cache;
  142. }
  143. // 执行请求的辅助函数
  144. void HttpClientPrivate::executeQuery(HttpClientPrivate *d, HttpClientRequestMethod method) {
  145. // 1. 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
  146. // 2. 创建请求需要的变量
  147. // 3. 根据 method 执行不同的请求
  148. // 4. 请求结束时获取响应数据,在 handleFinish 中执行回调函数
  149. // [1] 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
  150. HttpClientPrivateCache cache = d->cache();
  151. // [2] 创建请求需要的变量
  152. QNetworkRequest request = HttpClientPrivate::createRequest(d, method);
  153. QNetworkReply *reply = nullptr;
  154. // [3] 根据 method 执行不同的请求
  155. switch (method) {
  156. case HttpClientRequestMethod::GET:
  157. reply = cache.manager->get(request);
  158. break;
  159. case HttpClientRequestMethod::POST:
  160. reply = cache.manager->post(request, d->useJson ? d->json.toUtf8() : d->params.toString(QUrl::FullyEncoded).toUtf8());
  161. break;
  162. case HttpClientRequestMethod::PUT:
  163. reply = cache.manager->put(request, d->useJson ? d->json.toUtf8() : d->params.toString(QUrl::FullyEncoded).toUtf8());
  164. break;
  165. case HttpClientRequestMethod::DELETE:
  166. reply = cache.manager->deleteResource(request);
  167. break;
  168. default:
  169. break;
  170. }
  171. // [4] 请求结束时获取响应数据,在 handleFinish 中执行回调函数
  172. // 请求结束时一次性读取所有响应数据
  173. QObject::connect(reply, &QNetworkReply::finished, [=] {
  174. QString successMessage = HttpClientPrivate::readReply(reply, cache.charset.toUtf8());
  175. QString failMessage = reply->errorString();
  176. HttpClientPrivate::handleFinish(cache, reply, successMessage, failMessage);
  177. });
  178. }
  179. // 使用 GET 进行下载,下载的文件保存到 savePath
  180. void HttpClientPrivate::download(HttpClientPrivate *d, const QString &savePath) {
  181. // 1. 打开下载文件,如果打开文件出错,不进行下载
  182. // 2. 给请求结束的回调函数注入关闭释放文件的行为
  183. // 3. 调用下载的重载函数开始下载
  184. QFile *file = new QFile(savePath);
  185. // [1] 打开下载文件,如果打开文件出错,不进行下载
  186. if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
  187. file->close();
  188. file->deleteLater();
  189. if (d->debug) {
  190. qDebug().noquote() << QString("[错误] 打开文件出错: %1").arg(savePath);
  191. }
  192. if (nullptr != d->failHandler) {
  193. d->failHandler(QString("[错误] 打开文件出错: %1").arg(savePath), -1);
  194. }
  195. return;
  196. }
  197. // [2] 给请求结束的回调函数注入关闭释放文件的行为
  198. std::function<void()> userCompleteHandler = d->completeHandler;
  199. std::function<void()> injectedCompleteHandler = [=]() {
  200. // 请求结束后释放文件对象
  201. file->flush();
  202. file->close();
  203. file->deleteLater();
  204. // 执行用户指定的结束回调函数
  205. if (nullptr != userCompleteHandler) {
  206. userCompleteHandler();
  207. }
  208. };
  209. d->completeHandler = injectedCompleteHandler;
  210. // [3] 调用下载的重载函数开始下载
  211. HttpClientPrivate::download(d, [=](const QByteArray &data) {
  212. file->write(data);
  213. });
  214. }
  215. // 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
  216. void HttpClientPrivate::download(HttpClientPrivate *d, std::function<void(const QByteArray &)> readyRead) {
  217. // 1. 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
  218. // 2. 创建请求需要的变量,执行请求
  219. // 3. 有数据可读取时回调 readyRead()
  220. // 4. 请求结束时获取响应数据,在 handleFinish 中执行回调函数
  221. // [1] 缓存需要的变量,在 lambda 中使用 = 捕捉使用 (不能使用引用 &,因为 d 已经被析构)
  222. HttpClientPrivateCache cache = d->cache();
  223. // [2] 创建请求需要的变量,执行请求
  224. QNetworkRequest request = HttpClientPrivate::createRequest(d, HttpClientRequestMethod::GET);
  225. QNetworkReply *reply = cache.manager->get(request);
  226. // [3] 有数据可读取时回调 readyRead()
  227. QObject::connect(reply, &QNetworkReply::readyRead, [=] {
  228. readyRead(reply->readAll());
  229. });
  230. // [4] 请求结束时获取响应数据,在 handleFinish 中执行回调函数
  231. QObject::connect(reply, &QNetworkReply::finished, [=] {
  232. QString successMessage = "下载完成"; // 请求结束时一次性读取所有响应数据
  233. QString failMessage = reply->errorString();
  234. HttpClientPrivate::handleFinish(cache, reply, successMessage, failMessage);
  235. });
  236. }
  237. // 上传文件或者数据的实现
  238. void HttpClientPrivate::upload(HttpClientPrivate *d, const QStringList &paths, const QByteArray &data) {
  239. // 1. 缓存需要的变量,在 lambda 中使用 = 捕获进行值传递 (不能使用引用 &,因为 d 已经被析构)
  240. // 2. 创建 Form 表单的参数 Text Part
  241. // 3. 创建上传的 File Part
  242. // 3.1 使用文件创建 File Part
  243. // 3.2 使用数据创建 File Part
  244. // 4. 创建请求需要的变量,执行请求
  245. // 5. 请求结束时释放 multiPart 和打开的文件,获取响应数据,在 handleFinish 中执行回调函数
  246. // [1] 缓存需要的变量,在 lambda 中使用 = 捕捉使用 (不能使用引用 &,因为 d 已经被析构)
  247. HttpClientPrivateCache cache = d->cache();
  248. // [2] 创建 Form 表单的参数 Text Part
  249. QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
  250. QList<QPair<QString, QString> > paramItems = d->params.queryItems();
  251. for (int i = 0; i < paramItems.size(); ++i) {
  252. QString name = paramItems.at(i).first;
  253. QString value = paramItems.at(i).second;
  254. QHttpPart textPart;
  255. textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(name));
  256. textPart.setBody(value.toUtf8());
  257. multiPart->append(textPart);
  258. }
  259. if (paths.size() > 0) {
  260. // [3.1] 使用文件创建 File Part
  261. QString inputName = paths.size() == 1 ? "file" : "files"; // 一个文件时为 file,多个文件时为 files
  262. for (const QString &path : paths) {
  263. // path 为空时,不上传文件
  264. if (path.isEmpty()) {
  265. continue;
  266. }
  267. // We cannot delete the file now, so delete it with the multiPart
  268. QFile *file = new QFile(path, multiPart);
  269. // 如果文件打开失败,则释放资源返回,终止上传
  270. if (!file->open(QIODevice::ReadOnly)) {
  271. QString failMessage = QString("打开文件失败[%2]: %1").arg(path).arg(file->errorString());
  272. if (cache.debug) {
  273. qDebug().noquote() << failMessage;
  274. }
  275. if (nullptr != cache.failHandler) {
  276. cache.failHandler(failMessage, -1);
  277. }
  278. multiPart->deleteLater();
  279. return;
  280. }
  281. // 单个文件时,name 为服务器端获取文件的参数名,为 file
  282. // 多个文件时,name 为服务器端获取文件的参数名,为 files
  283. // 注意: 服务器是 Java 的则用 form-data
  284. // 注意: 服务器是 PHP 的则用 multipart/form-data
  285. QString disposition = QString("form-data; name=\"%1\"; filename=\"%2\"").arg(inputName).arg(file->fileName());
  286. QHttpPart filePart;
  287. filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
  288. filePart.setBodyDevice(file);
  289. multiPart->append(filePart);
  290. }
  291. }
  292. else {
  293. // [3.2] 使用数据创建 File Part
  294. QString disposition = QString("form-data; name=\"file\"; filename=\"no-name\"");
  295. QHttpPart dataPart;
  296. dataPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
  297. dataPart.setBody(data);
  298. multiPart->append(dataPart);
  299. }
  300. // [4] 创建请求需要的变量,执行请求
  301. QNetworkRequest request = HttpClientPrivate::createRequest(d, HttpClientRequestMethod::UPLOAD);
  302. QNetworkReply *reply = cache.manager->post(request, multiPart);
  303. // [5] 请求结束时释放 multiPart 和文件,获取响应数据,在 handleFinish 中执行回调函数
  304. QObject::connect(reply, &QNetworkReply::finished, [=] {
  305. multiPart->deleteLater(); // 释放资源: multiPart + file
  306. QString successMessage = HttpClientPrivate::readReply(reply, cache.charset); // 请求结束时一次性读取所有响应数据
  307. QString failMessage = reply->errorString();
  308. HttpClientPrivate::handleFinish(cache, reply, successMessage, failMessage);
  309. });
  310. }
  311. // 获取 Manager,如果传入了 manager 则返回此 manager,否则新创建一个 manager,默认会自动创建一个 manager
  312. QNetworkAccessManager* HttpClientPrivate::getManager() {
  313. return internal ? new QNetworkAccessManager() : manager;
  314. }
  315. // 使用用户设定的 URL、请求头、参数等创建 Request
  316. QNetworkRequest HttpClientPrivate::createRequest(HttpClientPrivate *d, HttpClientRequestMethod method) {
  317. // 1. 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
  318. // 2. 调试时输出网址和参数
  319. // 3. 设置 Content-Type
  320. // 4. 添加请求头到 request 中
  321. bool get = method == HttpClientRequestMethod::GET;
  322. bool upload = method == HttpClientRequestMethod::UPLOAD;
  323. bool withForm = !get && !upload && !d->useJson; // PUT、POST 或者 DELETE 请求,且 useJson 为 false
  324. bool withJson = !get && !upload && d->useJson; // PUT、POST 或者 DELETE 请求,且 useJson 为 true
  325. // [1] 如果是 GET 请求,并且参数不为空,则编码请求的参数,放到 URL 后面
  326. if (get && !d->params.isEmpty()) {
  327. d->url += "?" + d->params.toString(QUrl::FullyEncoded);
  328. }
  329. // [2] 调试时输出网址和参数
  330. if (d->debug) {
  331. qDebug().noquote() << "[网址]" << d->url;
  332. if (withJson) {
  333. qDebug().noquote() << "[参数]" << d->json;
  334. }
  335. else if (withForm || upload) {
  336. QList<QPair<QString, QString> > paramItems = d->params.queryItems();
  337. QString buffer; // 避免多次调用 qDebug() 输入调试信息,每次 qDebug() 都有可能输出行号等
  338. // 按键值对的方式输出参数
  339. for (int i = 0; i < paramItems.size(); ++i) {
  340. QString name = paramItems.at(i).first;
  341. QString value = paramItems.at(i).second;
  342. if (0 == i) {
  343. buffer += QString("[参数] %1=%2\n").arg(name).arg(value);
  344. }
  345. else {
  346. buffer += QString(" %1=%2\n").arg(name).arg(value);
  347. }
  348. }
  349. if (!buffer.isEmpty()) {
  350. qDebug().noquote() << buffer;
  351. }
  352. }
  353. }
  354. // [3] 设置 Content-Type
  355. // 如果是 POST 请求,useJson 为 true 时添加 Json 的请求头,useJson 为 false 时添加 Form 的请求头
  356. if (withForm) {
  357. d->headers["Content-Type"] = "application/x-www-form-urlencoded";
  358. }
  359. else if (withJson) {
  360. d->headers["Content-Type"] = "application/json; charset=utf-8";
  361. }
  362. // [4] 添加请求头到 request 中
  363. QNetworkRequest request(QUrl(d->url));
  364. for (auto i = d->headers.cbegin(); i != d->headers.cend(); ++i) {
  365. request.setRawHeader(i.key().toUtf8(), i.value().toUtf8());
  366. }
  367. return request;
  368. }
  369. // 读取服务器响应的数据
  370. QString HttpClientPrivate::readReply(QNetworkReply *reply, const QString &charset) {
  371. QTextStream in(reply);
  372. QString result;
  373. in.setCodec(charset.toUtf8());
  374. while (!in.atEnd()) {
  375. result += in.readLine();
  376. }
  377. return result;
  378. }
  379. // 请求结束的处理函数
  380. void HttpClientPrivate::handleFinish(HttpClientPrivateCache cache, QNetworkReply *reply, const QString &successMessage, const QString &failMessage) {
  381. // 1. 执行请求成功的回调函数
  382. // 2. 执行请求失败的回调函数
  383. // 3. 执行请求结束的回调函数
  384. // 4. 释放 reply 和 manager 对象
  385. if (reply->error() == QNetworkReply::NoError) {
  386. if (cache.debug) {
  387. qDebug().noquote() << QString("[结束] 成功: %1").arg(successMessage);
  388. }
  389. // [1] 执行请求成功的回调函数
  390. if (nullptr != cache.successHandler) {
  391. cache.successHandler(successMessage);
  392. }
  393. }
  394. else {
  395. if (cache.debug) {
  396. qDebug().noquote() << QString("[结束] 失败: %1").arg(failMessage);
  397. }
  398. // [2] 执行请求失败的回调函数
  399. if (nullptr != cache.failHandler) {
  400. cache.failHandler(failMessage, reply->error());
  401. }
  402. }
  403. // [3] 执行请求结束的回调函数
  404. if (nullptr != cache.completeHandler) {
  405. cache.completeHandler();
  406. }
  407. // [4] 释放 reply 和 manager 对象
  408. if (nullptr != reply) {
  409. reply->deleteLater();
  410. }
  411. if (cache.internal && nullptr != cache.manager) {
  412. cache.manager->deleteLater();
  413. }
  414. }
  415. /*-----------------------------------------------------------------------------|
  416. | HttpClient |
  417. |----------------------------------------------------------------------------*/
  418. // 注意: 在异步请求中 HttpClient 的 HttpClientPrivate 成员变量 d 已经被析构,所以需要先缓存相关变量为栈对象,使用 = 以值的方式访问
  419. HttpClient::HttpClient(const QString &url) : d(new HttpClientPrivate(url)) { }
  420. HttpClient::~HttpClient() {
  421. delete d;
  422. }
  423. void HttpClient::stop2()
  424. {
  425. d->stop1();
  426. }
  427. // 传入 QNetworkAccessManager 给多个请求共享
  428. HttpClient& HttpClient::manager(QNetworkAccessManager *manager) {
  429. d->manager = manager;
  430. d->internal = (nullptr == manager);
  431. return *this;
  432. }
  433. // 传入 debug 为 true 则使用 debug 模式,请求执行时输出请求的 URL 和参数等
  434. HttpClient& HttpClient::debug(bool debug) {
  435. d->debug = debug;
  436. return *this;
  437. }
  438. // 添加一个请求的参数,可以多次调用添加多个参数
  439. HttpClient& HttpClient::param(const QString &name, const QVariant &value) {
  440. d->params.addQueryItem(name, value.toString());
  441. return *this;
  442. }
  443. // 添加多个请求的参数
  444. HttpClient& HttpClient::params(const QMap<QString, QVariant> &ps) {
  445. for (auto iter = ps.cbegin(); iter != ps.cend(); ++iter) {
  446. d->params.addQueryItem(iter.key(), iter.value().toString());
  447. }
  448. return *this;
  449. }
  450. // 添加请求的参数 (请求体),使用 Json 格式,例如 "{\"name\": \"Alice\"}"
  451. HttpClient& HttpClient::json(const QString &json) {
  452. d->json = json;
  453. d->useJson = true;
  454. return *this;
  455. }
  456. // 添加请求头
  457. HttpClient& HttpClient::header(const QString &name, const QString &value) {
  458. d->headers[name] = value;
  459. return *this;
  460. }
  461. // 添加多个请求头
  462. HttpClient& HttpClient::headers(const QMap<QString, QString> nameValues) {
  463. for (auto i = nameValues.cbegin(); i != nameValues.cend(); ++i) {
  464. d->headers[i.key()] = i.value();
  465. }
  466. return *this;
  467. }
  468. // 注册请求成功的回调函数
  469. HttpClient& HttpClient::success(std::function<void(const QString &)> successHandler) {
  470. d->successHandler = successHandler;
  471. return *this;
  472. }
  473. // 注册请求失败的回调函数
  474. HttpClient& HttpClient::fail(std::function<void(const QString &, int)> failHandler) {
  475. d->failHandler = failHandler;
  476. return *this;
  477. }
  478. // 注册请求结束的回调函数,不管成功还是失败都会执行
  479. HttpClient& HttpClient::complete(std::function<void()> completeHandler) {
  480. d->completeHandler = completeHandler;
  481. return *this;
  482. }
  483. // 设置请求响应的编码
  484. HttpClient& HttpClient::charset(const QString &cs) {
  485. d->charset = cs;
  486. return *this;
  487. }
  488. // 执行 GET 请求
  489. void HttpClient::get() {
  490. HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::GET);
  491. }
  492. // 执行 POST 请求
  493. void HttpClient::post() {
  494. HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::POST);
  495. }
  496. // 执行 PUT 请求
  497. void HttpClient::put() {
  498. HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::PUT);
  499. }
  500. // 执行 DELETE 请求
  501. void HttpClient::remove() {
  502. HttpClientPrivate::executeQuery(d, HttpClientRequestMethod::DELETE);
  503. }
  504. // 使用 GET 进行下载,下载的文件保存到 savePath
  505. void HttpClient::download(const QString &savePath) {
  506. HttpClientPrivate::download(d, savePath);
  507. }
  508. // 上传文件
  509. void HttpClient::upload(const QString &path) {
  510. QStringList paths = { path };
  511. HttpClientPrivate::upload(d, paths, QByteArray());
  512. }
  513. // 上传文件,文件的内容以及读取到 data 中
  514. void HttpClient::upload(const QByteArray &data) {
  515. HttpClientPrivate::upload(d, QStringList(), data);
  516. }
  517. // 上传多个文件
  518. void HttpClient::upload(const QStringList &paths) {
  519. HttpClientPrivate::upload(d, paths, QByteArray());
  520. }