QT5实现https的post请求(QNetworkAccessManager、QNetworkRequest和QNetworkReply)


前言

  1. QNetworkAccessManager、QNetworkRequest和QNetworkReply是QT5网络编程的API,三者共同完成HTTP或者HTTPS协议的通信。
  2. 初学者往往会程序编译没有问题,但是运行代码没有任何结果,于是不知道问题出在哪里。此时,要借助postmanwireshark等工具的帮助。

本文按照问题出现的顺序总结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部分代码如下

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部分代码如下:

#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类的构造函数来设置。例如:

QNetworkRequest m_httpRequest(QUrl("http://www.wangsansan.com/test/HttpsPostTest.php"));

有2个问题需要注意:

  1. QUrl地址中,http与https的连接过程、传输协议、端口都是不一样的。
  2. 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类。刚开始的时候,不知道使用setHeadersetRawHeader的效果。利用wireshark就可以查看了。例如,QNetworkRequest::m_httpRequest.setHeader(QNetworkRequest::LocationHeader, QByteArray("/test/HttpsPostTest.php"));的结果如下图所示:
在这里插入图片描述

部分代码如下:

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部分代码如下:

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());
}

运行结果举例:

reply data: "{\"error_code\":111,\"error_msg\":\"Access token expired\"}"

总结

至此,QT5利用QNetworkAccessManager、QNetworkRequest和QNetworkReply三个类可以实现https的post请求。但是,还有问题需要改进:

  1. 返回数据的处理
  2. post请求报文中的正文部分,其格式、内容、字节长度等还需要验证。Jason数据的嵌套还需要实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值