简述
Qt NetWork 模块中提供了一些网络类,例如QNetworkRequest、QNetworkReplay和QNetworkAccessManager使用常见的协议进行网络操作
调试的过程中可以使用Fiddler,便于我们调试。Fidder是一个HTTP协议代理工具,当然也可以使用Web/Http调试工具
HTTP 消息结构
Request
请求行: Request消息中的第一行,由请求方式、请求URL、HTTP协议及版本三部分组成。
请求头:其中Content-Type制定了客户端发送的内容格式,例如Content-Type:application/json,指定客户端发送的数据格式为json数据
Content-Type: application/x-www-form-urlencoded 指定发送的为表单类型数据
请求体: 要发送的表单数据
Response
状态行:Response消息中的第一行,由HTTP协议版本号、状态码、状态消息三部分组成。状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response。HTTP/1.1中定义了5类状态码,状态码由三位数字组成,第一个数字定义了响应的类别:
1XX : 提示信息-表示请求已经被成功接收,继续处理。
2XX : 成功-表示请求已被成功接收,理解、接受。
3XX: 重定向-要完成请求必须进行更进一步的处理
4XX: 客户端错误-请求语法有错误或请求无法实现
5XX: 服务端错误-服务器未能实现合法的请求
响应头:其中 Content-Type制定了客户端发送的内容格式,例如Content-Type:application/json,指定客户端发送的数据格式为json数据
Content-Type: application/x-www-form-urlencoded 指定发送的为表单类型数据
响应体:服务器返回的内容。
支持的协议
在进行网络请求之前,首先要查看QNetworkAccessManager支持的协议:
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
qDebug() << manager->supportedSchemes();
列出支持的协议
("ftp", "file", "qrc", "http", "https", "data")
//说明:安装openssl,才会有https
请求
构建一个请求非常简单
QString baseUrl = "http://www.csdn.net/";
QNetworkRequest request;
request.setUrl(QUrl(baseUrl));
现在名为resquest的QNetworkRequest的请求就构建成功了,为了获取所想要的信息,需要把请求发送出去
QNetworkReply *pReplay = manager->get(request);
这是会获取一个名为pReplay的QNetworkReply响应对象,等待响应完成后,就可以从这个对象中获取想要的信息。
QByteArray bytes = pReplay->readAll();
qDebug() << bytes;
完成的代码:
// URL
QString baseUrl = "http://www.csdn.net/";
// 构造请求
QNetworkRequest request;
request.setUrl(QUrl(baseUrl));
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// 发送请求
QNetworkReply *pReplay = manager->get(request);
// 开启一个局部的事件循环,等待响应结束,退出
QEventLoop eventLoop;
QObject::connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
// 获取响应信息
QByteArray bytes = pReplay->readAll();
qDebug() << bytes;
注意:
因为请求的过程是异步的,所以此时使用QEventLoop开启一个局部的时间循环,等待响应结束,时间循环退出。
开启事件循环的同时,程序界面将不会响应用户的操作(界面被阻塞)
简单的API意味着所有的HTTP请求类型都是显而易见的,POST PUT的请求如下:
QNetworkReply *pPostReplay = manager->post(request, QByteArray());
QNetworkReply *pPutReplay = manager->put(request, QByteArray());
传递URL参数
经常想为URL的查询字符串(query string)传递某种数据。如果是手工构建URL,那么数据会以键/值对应的形式置于URL中,跟在一个问号的后面。例如:http://www.zhihu.com/search?type=content&q= Qt 。Qt提供了QUrlQuery类,可以很便利的提供这些参数
举例来说,如果想传递type=content 和 q= Qt 到http://www.zhihu.com/search中,可以使用以下的代码:
// URL
QString baseUrl = "http://www.zhihu.com/search";
QUrl url(baseUrl);
// key-value 对
QUrlQuery query;
query.addQueryItem("type", "content");
query.addQueryItem("q", "Qt");
url.setQuery(query);
通过打印Url,可以看到URL被正确编码
qDebug() << url.url();
// "http://www.zhihu.com/search?type=content&q=Qt"
复杂的POST请求
httpbin是一个使用Python + Flask编写的HTTP请求和响应服务,主要用于测试HTTP库
通常,你想要发送一些编码为表单形式的数据,非常想一个HTML表单,要实现这个,只需要简单地传递一个QByteArray给data参数
// URL
QString baseUrl = "http://httpbin.org/post";
QUrl url(baseUrl);
// 表单数据
QByteArray dataArray;
dataArray.append("key1=value1&");
dataArray.append("key2=value2");
// 构造请求
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setUrl(url);
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// 发送请求
manager->post(request, dataArray);
// URL
QString baseUrl = "http://httpbin.org/post";
QUrl url(baseUrl);
// Json数据
QJsonObject json;
json.insert("User", "Qter");
json.insert("Password", "123456");
QJsonDocument document;
document.setObject(json);
QByteArray dataArray = document.toJson(QJsonDocument::Compact);
// 构造请求
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(url);
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
// 连接信号槽
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *)));
// 发送请求
manager->post(request, dataArray);
为了不阻塞界面,不再使用QEventLoop, 而用QNetworkAccessManager对应的信号,当响应结束就会发射finished()信号,将其连接到对应的槽函数上
定制请求头
如果想为请求添加HHTP头部,只需简单地调用setHeader()就可以了
例如,发送请求时,使用的User-Agent是 Mozilla/5.0, 为了方便以后追踪版本信息,可以将软件的版本信息写入到User-Agent中
QNetworkRequest request;
request.setHeader(QNetworkRequest::UserAgentHeader, "my-app/0.0.1");
User-Agent:包含发出请求的用户信息
当然,除了User-Agent之外,QNetworkRequest:knowHeaders还包含其他请求头,它就是为HTTP头部而生的。根据RFC 2616, HTTP的头部大小写不敏感
如果QNetworkRequest:knowHeaders不满足需要,使用setRawHeader
响应内容
要获取响应的内容,可以调用readAll(),由于上述的POST请求返回的数据是Json格式,将响应结果转为Json,然后对齐解析
void replyFinished(QNetworkReply *reply)
{
// 获取响应信息
QByteArray bytes = reply->readAll();
QJsonParseError jsonError;
QJsonDocument doucment = QJsonDocument::fromJson(bytes, &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
qDebug() << QStringLiteral("解析Json失败");
return;
}
// 解析Json
if (doucment.isObject()) {
QJsonObject obj = doucment.object();
QJsonValue value;
if (obj.contains("data")) {
value = obj.take("data");
if (value.isString()) {
QString data = value.toString();
qDebug() << data;
}
}
}
}
响应的内容可以是HTML页面、也可以是字符串、Json、XML等
响应状态码
我们可以检查状态码
QVariant variant = pReplay->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (variant.isValid())
qDebug() << variant.toInt();
// 200
最常见的200 OK表示请求成功,请求所希望的响应头或数据体将随此响应返回
响应头
进入Response Headers:
可以看到包含很多,和上面一样,使用任意QNetworkRequest:KnownHeaders来访问这些响应头字段,例如 Content Type:
QVariant variant = pReplay->header(QNetworkRequest::ContentTypeHeader);
if (variant.isValid())
qDebug() << variant.toString();
// "text/html; charset=utf-8"
如果QNetworkRequest:KnownHeaders 不满足需要,可以使用rawHeader(),例如:响应包含了登录后的TOKEN,位于原始消息头中
if(reply->hasRawHeader("TOKEN"))
QByteArray byte = reply->rawHeader("TOKEN");
它还有一个特殊点,那就是服务器可以多次接受同一个header,每次使用不用的值。Qt会将它们合并,这样它们就可以用一个映射表示出来,参见RFC 7230
接收者可以合并多个相同名称的header栏位,把他们合并为一个"field-name: field-value"配对,将每个后续的栏位依次追加到合并的栏位值中,用逗号隔开即可,这样做不会改变信息的语义。