QT5实现https的post请求
前言
- QNetworkAccessManager、QNetworkRequest和QNetworkReply是QT5网络编程的API,三者共同完成HTTP或者HTTPS协议的通信。
- 初学者往往会程序编译没有问题,但是运行代码没有任何结果,于是不知道问题出在哪里。此时,要借助
postman
、wireshark
等工具的帮助。
本文按照问题出现的顺序总结QT5的网络编程方法。
一、一定要有sslErrors处理
1、问题经过
我们知道,客户端发送HTTPS的post请求,需要手动构建请求报文的请求行、请求头部和请求体。一开始的时候,着急利用QNetworkRequest
来实现构建请求报文(request),但是程序运行始终没有输出数据,以为是构建报文的格式不正确。于是简化,构建get报文,程序运行依然没有输出数据。后来,参考官网Example、Gitee、CSDN的代码,加入了sslErrors
槽函数,程序终于有了反应。在利用QNetworkAccessManager::connectToHostEncrypted
时提示错误:qt.network.ssl: QSslSocket: cannot resolve EVP_PKEY_base_id
。
这是HTTPS请求错误,原因在于本地OpenSSL版本与Qt支持的不匹配。通过检查、下载、编译和配置OpenSSL源代码,解决了这个问题。具体方法可以参考博文: 《(Linux)解决运行Qt程序时报错》,亲测有效。
如果不加sslErrors
槽函数,那永远不知道是OpenSSL的问题,还傻傻地以为是报文格式、post方法不对。
参考资料:QNetworkReply Class | Qt Network 5.15.17
2、代码示例
httpspost.h部分代码如下
c
class HttpsPost : public QObject
{
Q_OBJECT
QNetworkAccessManager m_networkAccessManager;
public:
HttpsPost();
void doPost();
~HttpsPost();
private:
//发送data数据
QByteArray m_sendJsonData;
QVariant data;
public slots:
void sslErrors(const QList<QSslError> &errors);
void postReadyRead(QNetworkReply *reply);
}
httpspost.cpp部分代码如下:
c
#include <QtCore>
#include <QtNetwork>
#include <QSslConfiguration>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QDebug>
#include <QVector>
#include "httpspost.h"
HttpsPost::HttpsPost()
{
connect(&m_networkAccessManager, &QNetworkAccessManager::finished,
this, &HttpsPost::postReadyRead);
#if QT_CONFIG(ssl)
connect(reply, &QNetworkReply::sslErrors,
this, &HttpsPost::sslErrors);
#endif
}
void HttpsPost::sslErrors(const QList<QSslError> &sslErrors)
{
#ifndef QT_NO_SSL
foreach(const QSslError &error, sslErrors)
qDebug() << "SSL error: " << error.errorString();
#else
Q_UNUSED(sslErrors);
#endif
}
QNetworkReply
类有信号对象(signals)sslErrors
。这个信号可以用来发射、显示错误消息。从而利用自己编写的槽函数HttpsPost::sslErrors
来显示、错误处理。
二、要利用抓包工具
1、问题经过
编译、安装了OpenSSL之后,程序运行仍然没有数据输出。此时,考虑到底有没有连接Web服务器,是否发送了post请求,所以想到利用网络抓包工具。我用的是wireshark抓包工具,很好用,360软件管家就有,安全有保障。
2、wireshark的使用
安装好wireshark工具之后,首先用postman发送get、post请求,看看正确的通信过程是怎样的。
①处:添加过滤器,只看本机和Web服务器IP地址的包。
②处:可以看到发送了post请求,点击可以查看请求报文内容。
③处:查看请求报文的格式和内容。根据这个可以比对自己构建的请求报文是否正确。
④处:可以看到协议版本是TLSV1.2。
3、利用wireshark查看服务器地址
服务器的地址可以通过QNetworkRequest
类的构造函数来设置。例如:
c
QNetworkRequest m_httpRequest(QUrl("http://www.wangsansan.com/test/HttpsPostTest.php"));
有2个问题需要注意:
- QUrl地址中,http与https的连接过程、传输协议、端口都是不一样的。
- https服务器地址不能用IP地址(QUrl("https://115.28.242.169 /test/HttpsPostTest.php")),否则会报错:
SSL error: "The host name did not match any of the valid hosts for this certificate"
。
http服务器则可以用计算机名,也可以用IP地址。
参考资料:QNetworkRequest Class | Qt Network 5.15.17
4、利用wireshark查看自己构建的请求报文
构建请求报文要用到QNetworkRequest
类。刚开始的时候,不知道使用setHeader
和setRawHeader
的效果。利用wireshark就可以查看了。例如,QNetworkRequest::m_httpRequest.setHeader(QNetworkRequest::LocationHeader, QByteArray("/test/HttpsPostTest.php"));
的结果如下图所示:
部分代码如下:
c
void HttpsPost::doPost()
{
QJsonDocument doc;
QJsonObject jsonObjData;
jsonObjData.insert("A", "111"); // 设置内容字段
jsonObjData.insert("B", "222");
doc.setObject(jsonObjData);
QString str = QString(doc.toJson());
QByteArray content = str.toUtf8();
int contentLength = content.length();
//QSslConfiguration是QNetworkRequest的访问设置类,用于设置协议类型支持https
QSslConfiguration config;
config.setPeerVerifyMode(QSslSocket::VerifyNone);
config.setProtocol(QSsl::TlsV1_2);
//构建QNetworkRequest对象,设置url
QNetworkRequest m_httpRequest(QUrl("http://www.wangsansan.com/test/HttpsPostTest.php"));
//构建post请求报文
// m_httpRequest.setHeader(QNetworkRequest::LocationHeader, QByteArray("/test/HttpsPostTest.php"));
m_httpRequest.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("application/json; charset=utf-8"));
m_httpRequest.setRawHeader("Connection", QByteArray("keep-alive"));
m_httpRequest.setHeader(QNetworkRequest::ContentLengthHeader, doc.toJson().size());//m_sendJsonData.length()
reply = m_networkAccessManager.post(m_httpRequest, doc.toJson());
//返回数据
QNetworkReply *reply = m_networkAccessManager.get(m_httpRequest);
三、返回数据只能读一次
1、问题描述
到这里,向服务器发送post请求已经没有问题了。但是,运行程序仍然无法获取数据。问题应该出在获取返回数据上。刚开始时,复制了官网上的代码,问题依然存在。Gitee、CSDN上的代码,写法也各不相同。后来,看了官网的参考文档才知道,返回数据只能在返回数据结束之后,读一次。
1.The QNetworkReply class contains the data and meta data related to a request posted with QNetworkAccessManager.
2.QNetworkReply is a sequential-access QIODevice, which means that once data is read from the object, it is no longer kept by the device.
QNetworkAccessManager::finished()
和QNetworkReply::finished()
,都可以发送返回数据结束的信号(signal)。然后用槽函数来获取数据。您还可以使用QNetworkReply::isFinished()
来检查QNetworkReply是否已完成。
参考文档:
QNetworkAccessManager Class | Qt Network 5.15.17
QNetworkReply Class | Qt Network 5.15.17
2、部分代码
httpspost.h如上文,httpspost.cpp部分代码如下:
c
HttpsPost::HttpsPost()
{
connect(&m_networkAccessManager, &QNetworkAccessManager::finished,
this, &HttpsPost::postReadyRead);
#if QT_CONFIG(ssl)
connect(reply, &QNetworkReply::sslErrors,
this, &HttpsPost::sslErrors);
#endif
}
void HttpsPost::postReadyRead(QNetworkReply *reply)
{
qDebug() <<"reply data:"<< QString::fromUtf8(reply->readAll());
}
运行结果举例:
c
reply data: "{\"error_code\":111,\"error_msg\":\"Access token expired\"}"
总结
至此,QT5利用QNetworkAccessManager、QNetworkRequest和QNetworkReply三个类可以实现https的post请求。但是,还有问题需要改进:
- 返回数据的处理
- post请求报文中的正文部分,其格式、内容、字节长度等还需要验证。Jason数据的嵌套还需要实现。