R1x2BF.png

QT 封装好的网络类

19次阅读
没有评论

httpclient.h

#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H

#include <functional>

class QString;
class QByteArray;
class QNetworkRequest;
class QNetworkReply;
class QNetworkAccessManager;
class RequestDispatcher;

typedef std::function<void (const QString &)> DeliverHandler;

/**
* 对 QNetworkAccessManager 进行简单封装的 HTTP 访问客户端,简化 GET、POST、PUT、DELETE、上传、下载等操作。
* 执行请求可调用 get(), post(), put(), remove(), download(), upload()。
* 在执行请求前可调用 header() 设置请求头,参数使用 Form 表单的方式传递则调用 param(),如果参数使用 request body
* 传递则调用 json() 设置参数(当然也可以不是 JSON 格式,使用 request body 的情况多数是 RESTful 时,大家都是用 JSON 格式,故命名为 json)。
* 默认 HttpClient 会创建一个 QNetworkAccessManager,如果不想使用默认的,调用 manager() 传入即可。
* 默认不输出请求的网址参数等调试信息,如果需要输出,调用 debug(true) 即可。
*/
class HttpClient {
public:
HttpClient(const QString &url);
~HttpClient();

/**
* @brief 每创建一个 QNetworkAccessManager 对象都会创建一个线程,当频繁的访问网络时,为了节省线程资源,
* 可以传入 QNetworkAccessManager 给多个请求共享(它不会被 HttpClient 删除,用户需要自己手动删除)。
* 如果没有使用 useManager() 传入一个 QNetworkAccessManager,则 HttpClient 会自动的创建一个,并且在网络访问完成后删除它。
* @param manager 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 QString &value);

/**
* @brief 添加请求的参数,使用 Json 格式,例如 "{\"name\": \"Alice\"}"
* @param json Json 格式的参数字符串
* @return
*/
HttpClient& json(const QString &json);

HttpClient& header(const QString &header, const QString &value);

void get(DeliverHandler successHandler,
DeliverHandler errorHandler = NULL,
const char *encoding = "UTF-8");

void post(DeliverHandler successHandler,
DeliverHandler errorHandler = NULL,
const char *encoding = "UTF-8");

void put(DeliverHandler successHandler,
DeliverHandler errorHandler = NULL,
const char *encoding = "UTF-8");

// 执行 DELETE 请求
void remove(DeliverHandler successHandler,
DeliverHandler errorHandler = NULL,
const char *encoding = "UTF-8");

// 下载
void download(const QString &savePath,
DeliverHandler successHandler = NULL,
DeliverHandler errorHandler = NULL);

// 当有数据可读取时回调 readyRead
void download(std::function<void (const QByteArray &)> readyRead,
DeliverHandler successHandler = NULL,
DeliverHandler errorHandler = NULL);

// 上传文件
void upload(const QString &path, DeliverHandler successHandler = NULL,
DeliverHandler errorHandler = NULL,
const char *encoding = "UTF-8");

// 上传数据
void upload(const QByteArray &data, DeliverHandler successHandler = NULL,
DeliverHandler errorHandler = NULL,
const char *encoding = "UTF-8");
private:
RequestDispatcher *_dispatcher;
};
#endif // HTTPCLIENT_H

httpclient.cpp

#include "httpclient.h"
#include <QDebug>
#include <QFile>
#include <QHash>
#include <QUrlQuery>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QHttpPart>
#include <QHttpMultiPart>

class RequestDispatcher {
public:
RequestDispatcher(const QString &url);

QString url; // 请求的 URL
QUrlQuery params; // 请求的参数使用 Form 格式
QString json; // 请求的参数使用 Json 格式

QHash<QString, QString> headers; // 请求头
QNetworkAccessManager *manager;

bool useJson; // 为 true 时请求使用 Json 格式传递参数,否则使用 Form 格式传递参数
bool debug; // 为 true 时输出请求的 URL 和参数

enum HttpMethod {
GET, POST, PUT, DELETE, UPLOAD /* UPLOAD 不是 HTTP Method,只是为了上传时特殊处理而定义的 */
};

// 获取 Manager
static QNetworkAccessManager* getManager(RequestDispatcher *dispatcher, bool *internal);

// 使用用户设定的 URL、请求头等创建 Request
static QNetworkRequest createRequest(RequestDispatcher *dispatcher, HttpMethod method);

// 执行请求的辅助函数
static void executeQuery(RequestDispatcher *dispatcher, HttpMethod method,
DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding);

// 上传文件或者数据
static void upload(RequestDispatcher *dispatcher,
const QString &path, const QByteArray &data,
DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding);

// 读取服务器响应的数据
static QString readReply(QNetworkReply *reply, const char *encoding = "UTF-8");

// 请求结束的处理函数
static void handleFinish(bool debug,
const QString &successMessage,
const QString &errorMessage,
DeliverHandler successHandler,
DeliverHandler errorHandler,
QNetworkReply *reply, QNetworkAccessManager *manager);
};

RequestDispatcher::RequestDispatcher(const QString &url) :
url(url),
manager(NULL),
useJson(false),
debug(false) {

}

// 注意: 不要在回调函数中使用 d,因为回调函数被调用时 HttpClient 对象很可能已经被释放掉了。
HttpClient::HttpClient(const QString &url) :
_dispatcher(new RequestDispatcher(url)) {

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

HttpClient &HttpClient::manager(QNetworkAccessManager *manager) {
_dispatcher->manager = manager;
return *this;
}

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

// 添加 Form 格式参数
HttpClient &HttpClient::param(const QString &name, const QString &value) {
_dispatcher->params.addQueryItem(name, value);
return *this;
}

// 添加 Json 格式参数
HttpClient &HttpClient::json(const QString &json) {
_dispatcher->useJson = true;
_dispatcher->json = json;
return *this;
}

// 添加访问头
HttpClient &HttpClient::header(const QString &header, const QString &value) {
_dispatcher->headers[header] = value;
return *this;
}

// 执行 GET 请求
void HttpClient::get(DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
RequestDispatcher::executeQuery(_dispatcher, RequestDispatcher::GET, successHandler, errorHandler, encoding);
}

// 执行 POST 请求
void HttpClient::post(DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
RequestDispatcher::executeQuery(_dispatcher, RequestDispatcher::POST, successHandler, errorHandler, encoding);
}

// 执行 PUT 请求
void HttpClient::put(DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
RequestDispatcher::executeQuery(_dispatcher, RequestDispatcher::PUT, successHandler, errorHandler, encoding);
}

// 执行 DELETE 请求
void HttpClient::remove(DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
RequestDispatcher::executeQuery(_dispatcher, RequestDispatcher::DELETE, successHandler, errorHandler, encoding);
}

void HttpClient::download(const QString &destinationPath,
DeliverHandler successHandler,
DeliverHandler errorHandler) {
bool debug = _dispatcher->debug;
QFile *file = new QFile(destinationPath);
if (file->open(QIODevice::WriteOnly)) {
download([=](const QByteArray &data) {
file->write(data);
}, [=](const QString &) {
// 请求结束后释放文件对象
file->flush();
file->close();
file->deleteLater();
if (debug) {
qDebug().noquote() << QStringLiteral("下载完成,保存到: %1").arg(destinationPath);
}
if (NULL != successHandler) {
successHandler(QStringLiteral("下载完成,保存到: %1").arg(destinationPath));
}
}, errorHandler);
} else {
// 打开文件出错
if (debug) {
qDebug().noquote() << QStringLiteral("打开文件出错: %1").arg(destinationPath);
}
if (NULL != errorHandler) {
errorHandler(QStringLiteral("打开文件出错: %1").arg(destinationPath));
}
}
}

// 使用 GET 进行下载,当有数据可读取时回调 readyRead(), 大多数情况下应该在 readyRead() 里把数据保存到文件
void HttpClient::download(std::function<void (const QByteArray &)> readyRead,
DeliverHandler successHandler,
DeliverHandler errorHandler) {
bool debug = _dispatcher->debug;
bool internal;

QNetworkAccessManager *manager = RequestDispatcher::getManager(_dispatcher, &internal);
QNetworkRequest request = RequestDispatcher::createRequest(_dispatcher, RequestDispatcher::GET);
QNetworkReply *reply = manager->get(request);

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

// 请求结束
QObject::connect(reply, &QNetworkReply::finished, [=] {
QString successMessage = "下载完成"; // 请求结束时一次性读取所有响应数据
QString errorMessage = reply->errorString();
RequestDispatcher::handleFinish(debug, successMessage, errorMessage, successHandler, errorHandler,
reply, internal ? manager : NULL);
});
}

// 上传文件
void HttpClient::upload(const QString &path,
DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
RequestDispatcher::upload(_dispatcher, path, QByteArray(), successHandler, errorHandler, encoding);
}

// 上传数据
void HttpClient::upload(const QByteArray &data,
DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
RequestDispatcher::upload(_dispatcher, QString(), data, successHandler, errorHandler, encoding);
}

// 上传文件或者数据的实现
void RequestDispatcher::upload(RequestDispatcher *dispatcher,
const QString &path, const QByteArray &data,
DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
bool debug = dispatcher->debug;
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

// 创建 Form 表单的参数 Text Part
QList<QPair<QString, QString> > paramItems = dispatcher->params.queryItems();
for (int i = 0; i < paramItems.size(); ++i) {
QHttpPart textPart;
QString name = paramItems.at(i).first;
QString value = paramItems.at(i).second;
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(name));
textPart.setBody(value.toUtf8());
multiPart->append(textPart);
}

if (!path.isEmpty()) {
// path 不为空时,上传文件
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)) {
QString errorMessage = QStringLiteral("打开文件失败[%2]: %1").arg(path).arg(file->errorString());
if (debug) {
qDebug().noquote() << errorMessage;
}
if (NULL != errorHandler) {
errorHandler(errorMessage);
}
multiPart->deleteLater();
return;
}
// 文件上传的参数名为 file,值为文件名
QString disposition = QString("form-data; name=\"file\"; filename=\"%1\"").arg(file->fileName());
QHttpPart filePart;
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
filePart.setBodyDevice(file);
multiPart->append(filePart);
} else {
// 上传数据
QString disposition = QString("form-data; name=\"file\"; filename=\"no-name\"");
QHttpPart dataPart;
dataPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition));
dataPart.setBody(data);
multiPart->append(dataPart);
}

bool internal;
QNetworkAccessManager *manager = RequestDispatcher::getManager(dispatcher, &internal);
QNetworkRequest request = RequestDispatcher::createRequest(dispatcher, RequestDispatcher::UPLOAD);
QNetworkReply *reply = manager->post(request, multiPart);

QObject::connect(reply, &QNetworkReply::finished, [=] {
multiPart->deleteLater(); // 释放资源: multiPart + file
QString successMessage = RequestDispatcher::readReply(reply, encoding); // 请求结束时一次性读取所有响应数据
QString errorMessage = reply->errorString();
RequestDispatcher::handleFinish(debug, successMessage, errorMessage, successHandler, errorHandler,
reply, internal ? manager : NULL);
});
}

// 执行请求的辅助函数
void RequestDispatcher::executeQuery(RequestDispatcher *dispatcher, HttpMethod method,
DeliverHandler successHandler,
DeliverHandler errorHandler,
const char *encoding) {
// 如果不使用外部的 manager 则创建一个新的,在访问完成后会自动删除掉
bool debug = dispatcher->debug;
bool internal;

QNetworkAccessManager *manager = RequestDispatcher::getManager(dispatcher, &internal);
QNetworkRequest request = RequestDispatcher::createRequest(dispatcher, method);
QNetworkReply *reply = NULL;

switch (method) {
case RequestDispatcher::GET:
reply = manager->get(request);
break;
case RequestDispatcher::POST:
reply = manager->post(request, dispatcher->useJson ? dispatcher->json.toUtf8() : dispatcher->params.toString(QUrl::FullyEncoded).toUtf8());
break;
case RequestDispatcher::PUT:
reply = manager->put(request, dispatcher->useJson ? dispatcher->json.toUtf8() : dispatcher->params.toString(QUrl::FullyEncoded).toUtf8());
break;
case RequestDispatcher::DELETE:
reply = manager->deleteResource(request);
break;
default:
break;
}

QObject::connect(reply, &QNetworkReply::finished, [=] {
QString successMessage = RequestDispatcher::readReply(reply, encoding); // 请求结束时一次性读取所有响应数据
QString errorMessage = reply->errorString();
RequestDispatcher::handleFinish(debug, successMessage, errorMessage, successHandler, errorHandler,
reply, internal ? manager : NULL);
});
}

QNetworkAccessManager* RequestDispatcher::getManager(RequestDispatcher *dispatcher, bool *internal) {
*internal = dispatcher->manager == NULL;
return *internal ? new QNetworkAccessManager() : dispatcher->manager;
}

QNetworkRequest RequestDispatcher::createRequest(RequestDispatcher *dispatcher, HttpMethod method) {
bool get = method == HttpMethod::GET;
bool upload = method == RequestDispatcher::UPLOAD;
bool postForm = !get && !upload && !dispatcher->useJson;
bool postJson = !get && !upload && dispatcher->useJson;

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

// 调试时输出网址和参数
if (dispatcher->debug) {
qDebug().noquote() << QStringLiteral("网址:") << dispatcher->url;
if (postJson) {
qDebug().noquote() << QStringLiteral("参数:") << dispatcher->json;
} else if (postForm || upload) {
QList<QPair<QString, QString> > paramItems = dispatcher->params.queryItems();
// 按键值对的方式输出参数
for (int i = 0; i < paramItems.size(); ++i) {
QString name = paramItems.at(i).first;
QString value = paramItems.at(i).second;
if (0 == i) {
qDebug().noquote() << QStringLiteral("参数: %1=%2").arg(name).arg(value);
} else {
qDebug().noquote() << QString(" %1=%2").arg(name).arg(value);
}
}
}
}

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

// 把请求的头添加到 request 中
QNetworkRequest request(QUrl(dispatcher->url));
QHashIterator<QString, QString> iter(dispatcher->headers);
while (iter.hasNext()) {
iter.next();
request.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8());
}
return request;
}

QString RequestDispatcher::readReply(QNetworkReply *reply, const char *encoding) {
QTextStream in(reply);
QString result;
in.setCodec(encoding);
while (!in.atEnd()) {
result += in.readLine();
}
return result;
}

void RequestDispatcher::handleFinish(bool debug,
const QString &successMessage,
const QString &errorMessage,
DeliverHandler successHandler,
DeliverHandler errorHandler,
QNetworkReply *reply, QNetworkAccessManager *manager) {
if (reply->error() == QNetworkReply::NoError) {
// 请求成功
if (debug) {
qDebug().noquote() << QStringLiteral("[成功]请求结束: %1").arg(successMessage);
}
if (NULL != successHandler) {
successHandler(successMessage);
}
} else {
// 请求失败
if (debug) {
qDebug().noquote() << QStringLiteral("[失败]请求结束: %1").arg(errorMessage);
}
if (NULL != errorHandler) {
errorHandler(errorMessage);
}
}
// 释放资源
reply->deleteLater();
if (NULL != manager) {
manager->deleteLater();
}
}
yiywain
版权声明:本文于2021-08-13转载自 evilwk/httpclient.cpp,共计13470字。
转载提示:此文章非本站原创文章,若需转载请联系原作者获得转载授权。
评论(没有评论)