本文是 《基于 Qt 的 REST 网络框架》的其中一节,建议全章阅读。
在 Qt 中,使用 QNetworkAccessManager 访问网络。基本的操作主要有:
- 配置 QNetworkAccessManager
- 封装请求
- 提交请求(附带可选的 Body)
- 处理应答
配置 QNetworkAccessManager
需要配置的有:代理、重定向策略。其中配置代理要麻烦一点,实现如下:
void QRestClient::setProxyUrl(const QByteArray &url)
{
QUrl proxyUrl(url);
QNetworkProxy proxy;
if (proxyUrl.scheme().compare("http", Qt::CaseInsensitive) == 0)
proxy.setType(QNetworkProxy::HttpProxy);
else if (proxyUrl.scheme().compare("sock5", Qt::CaseInsensitive) == 0)
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(proxyUrl.host());
proxy.setPort(static_cast<quint16>(proxyUrl.port()));
http_->setProxy(proxy);
}
这里的 http_ 就是 QNetworkAccessManager,其 setProxy 方法可以配置代理。另外还有 setProxyFactory 方法,可以更灵活的根据条件(比如目标域名)切换代理,但是很少用到。
封装请求
HTTP 请求中包括请求方法(Method)、路径(URI,包括 Query)、头域(Headers)以及数据部分(Body,格式由数据类型指定)。
下面的代码设置了 QNetworkRequest 的 路径(URI,包括 Query)、头域(Headers)。
void QRestRequest::toRequest(QRestClient & client, QNetworkRequest &req)
{
QUrl url;
if (url_.scheme() == nullptr)
url.setUrl(client.baseUrl() + url_.toString());
else
url = url_;
url.setQuery(query_);
req.setUrl(url);
for (auto i = headers_.keyValueBegin(); i != headers_.keyValueEnd(); ++i)
req.setRawHeader((*i).first, (*i).second);
}
然而 QNetworkReqeust 并不包含 Method 和 Body,我们利用其扩展属性(attribute)来存储这些信息。
QNetworkRequest::Attribute AttributeMethod =
static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User);
QNetworkRequest::Attribute AttributeBody =
static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User + 1);
request.setAttribute(AttributeMethod, static_cast<int>(req.method()));
request.setAttribute(AttributeBody, req.body());
提交请求
提交请求时,需要根据不同的 Method 分别使用 QNetworkAccessManager 不同方法。其中 POST、PUT 方法可以附加请求数据(Body)。而数据 Body 也有多种类型:多部分数据(QHttpMultiPart)、设备流(QIODevice)、普通二进制数据(QByteArray)。所以处理代码如下:
switch (method) {
case QRestRequest::Head:
reply = http_->head(request);
break;
case QRestRequest::Get:
reply = http_->get(request);
break;
case QRestRequest::Put:
if (auto part = body.value<QHttpMultiPart*>())
reply = http_->put(request, part);
else if (auto io = body.value<QIODevice*>())
reply = http_->put(request, io);
else
reply = http_->put(request, body.toByteArray());
break;
case QRestRequest::Post:
if (auto part = body.value<QHttpMultiPart*>())
reply = http_->post(request, part);
else if (auto io = body.value<QIODevice*>())
reply = http_->post(request, io);
else
reply = http_->post(request, body.toByteArray());
break;
case QRestRequest::Delete:
reply = http_->deleteResource(request);
break;
}
处理应答
接下来,就需要对应答(QNetworkReply)进行处理,首先要等待其 finished 信号。
因为涉及到异步等待,使用 Promise 模式会更加方便,所以将 finished 信号转换为 QPromise,实现如下:
QPromise<QNetworkReply *> result([this, reply](
const QPromiseResolve<QNetworkReply *>& resolve) {
auto callback = [=]() {
resolve(reply);
};
if (reply->isFinished()) {
callback();
return;
}
QObject::connect(reply, &QNetworkReply::finished, this, callback, Qt::QueuedConnection);
});
有可能很快就完成,此时就不会再有 finished 信号了,为了防止一直等待,这里提前做了 isFinished 判断。
接下来就是完成后的处理,如果有网络错误或者 HTTP 错误码,将其作为异常抛出,由 Promise 后续的回调处理。如果一切正常,就读取所有数据(finished 信号暗含网络数据已经全部传输完成,所以 readAll 不会阻塞)。
result.then([](QNetworkReply * reply) {
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError) {
return reply->readAll();
} else {
throw QRestException(reply->error(), reply->errorString());
}
});
本节就到此结束。