Qt 之高级网络操作

简述
Qt NetWork 模块中提供了一些网络类,例如QNetworkRequest、QNetworkReplay和QNetworkAccessManager使用常见的协议进行网络操作
调试的过程中可以使用Fiddler,便于我们调试。Fidder是一个HTTP协议代理工具,当然也可以使用Web/Http调试工具
HTTP 消息结构
Qt 之高级网络操作_第1张图片
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);

Qt 之高级网络操作_第2张图片
还可以使用jason参数传递数据,然后它会正确编码

// 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);

Qt 之高级网络操作_第3张图片
为了不阻塞界面,不再使用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:包含发出请求的用户信息
Qt 之高级网络操作_第4张图片
当然,除了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:
Qt 之高级网络操作_第5张图片
可以看到包含很多,和上面一样,使用任意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"配对,将每个后续的栏位依次追加到合并的栏位值中,用逗号隔开即可,这样做不会改变信息的语义。

你可能感兴趣的:(Qt,http)