一、前言
Qt 使用 QNetworkAccessManager 通路網絡,這裡對其進行了簡單的封裝,通路網絡的代碼可以簡化為:
// [[1]] GET 請求無參數
HttpClient("http://localhost:8080/device").success([](const QString &response) {
qDebug() << response;
}).get();
更多的使用方法請參考 main() 裡的例子。HttpClient 的實作為 HttpClient.h 和 HttpClient.cpp 部分。
二、main.cpp
main() 函數裡展示了 HttpClient 的使用示例。
#include <QApplication>
#include <QNetworkAccessManager>
#include <QDebug>
#include <QFile>
#include "HttpClient.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// 在代碼塊裡執行網絡通路,是為了測試 HttpClient 對象在被析構後,網絡通路的回調函數仍然能正常執行
{
// [[1]] GET 請求無參數
HttpClient("http://localhost:8080/device").success([](const QString &response) {
qDebug() << response;
}).get();
// [[2]] GET 請求有參數,有自定義 header
HttpClient("http://localhost:8080/device").success([](const QString &response) {
qDebug() << response;
}).param("id", "1").param("name", "諸葛亮").header("token", "123AS#D").get();
// [[3]] POST 請求有參數,有自定義 header
HttpClient("http://localhost:8080/device").success([](const QString &response) {
qDebug() << response;
}).param("id", "2").param("name", "卧龍").header("token", "DER#2J7")
.header("content-type", "application/x-www-form-urlencoded").post();
// [[4]] 每建立一個 QNetworkAccessManager 對象都會建立一個線程,當頻繁的通路網絡時,為了節省線程資源,調用 useManager()
// 使用共享的 QNetworkAccessManager,它不會被 HttpClient 删除。
// 如果下面的代碼不傳入 QNetworkAccessManager,從任務管理器裡可以看到建立了幾千個線程。
QNetworkAccessManager *manager = new QNetworkAccessManager();
for (int i = 0; i < 5000; ++i) {
HttpClient("http://localhost:8080/device").success([=](const QString &response) {
qDebug() << response << ", " << i;
}).manager(manager).get();
}
}
return a.exec();
}
三、HttpClient.h
#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();
void stop2();
/**
* @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
四、HttpClient.cpp
#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();
void stop1();
/**
* @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;
}
void HttpClientPrivate::stop1()
{
manager->deleteLater();
}
// 緩存 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;
}
void HttpClient::stop2()
{
d->stop1();
}
// 傳入 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());
}
參考:
Qt 通路網絡的 HttpClient
Qt 通路網絡的 HttpClient(封裝QNetworkAccessManager,且有服務端)
點選領取Qt學習資料+視訊教程~Qt開發(視訊教程+文檔+代碼+項目實戰)