上一篇介绍到接口的使用,本篇主要讲述的是该网络库的HttpService与HttpRequest原理与实现。对QNetworkAccessManager封装和管理Http请求。采用builder设计模式,这样在多参数情况下可以灵活运用。
由于网络请求是异步发生,所以需要信号与槽的配合。HttpResponse主要为转发QNetworkReply事件。HttpResponse由于支持槽函数的类型自动推导(根据参数类型的不同自动绑定对应数据属性的信号)。
HttpRequest类主要是基于QNetworkRequest类,封装了请求相关的参数。
1. builder设计模式的应用,先看下列例子
HttpService().get("http://mobilecdn.kugou.com/api/v3/search/song")
.queryParam("format", "json")
.queryParam("keyword", musicName)
.queryParam("page", page)
.queryParam("pagesize", pageSize)
.queryParam("showtype", 1)
.onResponse(responeReceiver, responeSlot)
.onError(errorReceiver, errorSlot)
.exec();
Http请求
(1) 可以看出这是一种链式编程风格,使用起来非常清晰明了。在多参数输入的情况下很适合该做法适合。builder一个重要特征是返回自己,而下一个调用者也同样返回自己,直到执行结束。在Qt库常用QString的arg函数就是通过不断调用arg并返回自己达到追加内容的功能;
(2) 如何不使用builder模式,则设置参数需要一条一条设置,这样会使得内容很臃肿;
(3) 以下为get请求返回HttpRequest, 而HttpRequest类设置参数都会返回自己对象。
HttpRequest HttpService::get(const QString &url)
{
return HttpRequest(QNetworkAccessManager::GetOperation, this).url(url);
}
get函数
HttpRequest &url(const QString &url);
HttpRequest &header(const QString &key, const QVariant &value);
HttpRequest &headers(const QMap<QString, QVariant> &headers);
HttpRequest &queryParam(const QString &key, const QVariant &value);
HttpRequest &queryParams(const QMap<QString, QVariant> ¶ms);
HttpRequest &userAttribute(const QVariant &value);
HttpRequest &jsonBody(const QVariant &jsonBody);
HttpRequest &onResponse(const QObject *receiver, const char *slot, HttpResponse::SupportMethod type = HttpResponse::AutoInfer);
HttpRequest &onError(const QObject *receiver, const char *slot);
HttpResponse *exec();
HttpRequest函数
(4) 最后的exec代表执行函数,调用则生效发送网络请求,另外exec返回HttpResponse对象,由于HttpResponse继承QNetworkReply类,还可以对HttpResponse进行二次处理。
2. HttpRequest主要是封装QNetworkRequest类,但在封装上还是有些技巧,比如:
(1) jsonBody函数接收的参数为QVariant, 在Qt库里QVariantMap与QJsonObject都可以转换为json格式数据,通过判别转换即可得到QJsonObject数据。另外,支持发送json数据的Http接口只有post与put, 而使用get则会被忽略。
HttpRequest &HttpRequest::jsonBody(const QVariant &jsonBody)
{
if (jsonBody.type() == QVariant::Map) {
m_jsonBody = QJsonObject::fromVariantMap(jsonBody.toMap());
}
else if (jsonBody.typeName() == QMetaType::typeName(QMetaType::QJsonObject)) {
m_jsonBody = jsonBody.toJsonObject();
}
return *this;
}
jsonBody函数
(2) onResponse与onError作用是通过传入响应槽函数,当请求完成或返回失败状态码则会调用对应的槽,这里面涉及到一套信号与槽的相关知识。在调用onResponse或onError时候,对应的接收者"receiver"(对象)与接收者槽函数"slot"会加进m_slotsMap变量里面。
QMultiMap<QString, QMap<QString, const QObject *>> m_slotsMap;
m_slotsMap变量
<2.1>值得一提的是m_slotsMap不仅仅是存放接收者与接收槽函数,还具有保存了HttpResponse::SupportMethod变量的值,这一个值非常重要,就是前面所说到的通过它可以让系统识别到需要什么返回。
HttpRequest &HttpRequest::onResponse(const QObject *receiver,
const char *slot,
HttpResponse::SupportMethod type)
{
m_slotsMap.insert(NUMBER_TO_STRING(type), {{slot, receiver}});
return *this;
}
HttpRequest &HttpRequest::onError(const QObject *receiver, const char *slot)
{
return onResponse(receiver, slot, HttpResponse::AutoInfer);
}
onResponse与onError函数
<2.2>比如接收者槽函数为void finish(QByteArray result), 则系统会自动QNetworkReplay的finish信号,当finish触发则返回QByteArray类型的结果给result变量。如果是接收者槽函数为void error(QString errorStr), 则系统后台自动绑定QNetworkReply的error信号,当返回错误状态码,系统会自动返回QString类型的结果给errorStr。
<2.3>目前支持自动识别的槽函数有:
void function(QNetworkReply* reply); Is_AutoInfer: true */
void function(QByteArray data); Is_AutoInfer: true */
void function(QVariantMap map); Is_AutoInfer: true */
void function(qint64 bytesReceived, qint64 bytesTotal); Is_AutoInfer: true */
void function(QNetworkReply::NetworkError error); Is_AutoInfer: true */
void function(QString errorString); Is_AutoInfer: true */
void function(QNetworkReply::NetworkError error, QNetworkReply* reply); Is_AutoInfer: true */
void function(QString errorString, QNetworkReply* reply); Is_AutoInfer: true */
3. exec魔法
(1)exec做的工作是发送Http请求,装载HttpResponse类;
(2)exec所使用的Http接口是createRequest,使用它是可以根据m_op属性选取对应的Http请求(get, post, put)。
HttpResponse *HttpRequest::exec()
{
QNetworkReply* reply = NULL;
QBuffer* sendBuffer = new QBuffer();
QJsonObject sendJson = m_jsonBody;
if (!sendJson.isEmpty()) {
QByteArray sendByteArray = QJsonDocument(sendJson).toJson();
sendBuffer->setData(sendByteArray);
}
reply = m_httpService->createRequest(m_op, m_networkRequest, sendBuffer);
if (reply == NULL) {
sendBuffer->deleteLater();
return NULL;
}
else {
sendBuffer->setParent(reply);
}
return new HttpResponse(reply, m_slotsMap);
}
exec函数