Qt实现HTTP客户端操作

本文深入探讨Qt中HTTP请求的实现方法,包括GET/POST请求、表单提交、HTTPS访问及错误处理。通过实例演示如何使用QNetworkAccessManager、QNetworkRequest和QNetworkReply等关键类,特别关注异步请求、重定向处理及超时机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(2019-7-27写的笔记,2021-7-21更新部分代码)

0.前言

本文是Qt中HTTP相关接口一个简单总结,主要是get/post请求以及表单提交等应用,HTTP相关知识自行百度。

下一篇,文件上传下载:https://gongjianbo1992.blog.csdn.net/article/details/119490369

1.简介

从Qt4.4开始,引入了QNetworkRequest、QNetworkReply 和 QNetworkAccessManager等类来进行HTTP、FTP的操作,替代之前的QFtp和QHttp。要使用这些类,先在pro文件中引入network模块:

QT += network

2.认识QNetworkAccessManager

网络访问API围绕一个QNetworkAccessManager对象构建,该对象包含其发送的请求的通用配置和设置。它包含代理和缓存配置,以及与此类问题相关的信号,以及可用于监控网络操作进度的回复信号。由于QNetworkAccessManager基于QObject,因此只能从它所属的线程中使用它。

创建QNetworkAccessManager对象后,应用程序就可以使用它通过网络发送请求。 该类提供了一组标准函数,它们接收请求和可选数据,每个函数都返回一个QNetworkReply对象。返回的对象用于获取响应相应请求而返回的任何数据。

//构建一个manager对象
QNetworkAccessManager *manager = new QNetworkAccessManager(this); 
//manager具有异步API,当http请求完成后,会通过finished信号进行通知
connect(manager,&QNetworkAccessManager::finished,this,&MyClass::replyFinished); 
//发送异步get请求
manager->get(QNetworkRequest(QUrl("http://qt-project.org")));

//这里也可以用一个QEventLoop来等待请求完成,但是我更爱用槽函数
//QNetworkReply *reply=manager->get(request);
//QEventLoop eventLoop;
//connect(reply, &QNetworkReply ::finished, &eventLoop, &QEventLoop::quit);
//eventLoop.exec();
//QByteArray reply_data=reply->readAll();

QNetworkAccessManager将收到的请求排队。并行执行的请求数取决于协议。 目前,对于桌面平台上的HTTP协议,一个主机/端口组合并行执行6个请求。请求完成后,用户有责任在适当的时间删除QNetworkReply对象。不要在连接到finished()的插槽内直接删除它,可以使用deleteLater()函数。

通过Operation枚举可以看支持的请求方式:

可以看到,HTTP的常用请求方式都有对应的方法,并且提供了自定义的接口。

也可以通过supportedSchemes()方法查看支持的协议:

QNetworkAccessManager *manager=new QNetworkAccessManager(this);
//查看支持的协议
qDebug()<<manager->supportedSchemes();
//安装了openssl,会有https
//("ftp", "file", "qrc", "http", "data")

3.服务请求QNetworkRequest

要发起一个get/post请求,首先要构建一个QNetworkRequest对象作为参数,它包含一个URL和一些可用于修改请求的辅助信息。

//构建请求对象
QNetworkRequest request;
request.setUrl(QUrl("http://httpbin.org/get"));
request.setRawHeader("Content-Type","application/json");

//发送请求
//manager->get(request);
//manager->post(request, QByteArray());
//manager->put(request, QByteArray());

有时需要在url中携带参数,如果手动进行字符串拼接不是很方便。Qt5.0提供了 QUrlQuery类,可以很方便的拼接和解析url中的参数。

QUrl url("http://httpbin.org/get");
    
//拼接
QUrlQuery query;
query.addQueryItem("ie","utf-8");
url.setQuery(query);
qDebug()<<url;
//QUrl("http://httpbin.org/get?ie=utf-8")
    
//解析
QUrlQuery query2(url);
if(query2.hasQueryItem("ie"))
    qDebug()<<query2.queryItemValue("ie");
//"utf-8"

4.服务响应QNetworkReply

调用manager的get、post等接口后会返回一个reply对象,在manager的finished信号种也会传递该reply对象。如果要同步处理就在get、post后用事件循环等finished信号,如果是异步处理直接关联finished信号到槽即可。

//同步处理,可以开启一个局部的事件循环,等待响应,不会阻塞线程
QNetworkReply *reply=manager->get(request);
QEventLoop eventLoop;
connect(reply, &QNetworkReply ::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
//处理reply信息
receiveReply(reply);

这里我使用的是连接manager的finished信号到槽函数的方式来异步处理:

//connect(manager,&QNetworkAccessManager::finished,this,&MyClass::replyFinished); 
//槽函数
void MyClass::replyFinished(QNetworkReply *reply)
{
    if(reply->error()!=QNetworkReply::NoError){
        //处理中的错误信息
        qDebug()<<"reply error:"<<reply->errorString();
    }else{
        //请求方式
        qDebug()<<"operation:"<<reply->operation();
        //状态码
        qDebug()<<"status code:"<<reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        qDebug()<<"url:"<<reply->url();
        //qDebug()<<"raw header:"<<reply->rawHeaderList();

        //获取响应信息
        const QByteArray reply_data=reply->readAll();
        qDebug()<<"read all:"<<reply_data;

        //解析json
        QJsonParseError json_error;
        QJsonDocument document=QJsonDocument::fromJson(reply_data, &json_error);
        if(json_error.error==QJsonParseError::NoError){
            if(document.isObject()){
                const QJsonObject obj=document.object();
                qDebug()<<obj;
                if(obj.contains("args")){
                    QJsonValue value=obj.value("args");
                    qDebug()<<value;
                    //QJsonValue(object, QJsonObject({"ie":"utf-8"}))
                }
            }
        }else{
            qDebug()<<"json error:"<<json_error.errorString();
        }
    }
    reply->deleteLater();
}

访问有些网页会返回301/302状态码,需要重新请求重定向的地址。

if (status_code == 301 || status_code == 302){
      // Or the target URL if it was a redirect:
      QVariant redirectionTargetUrl =reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
      //qDebug() << "redirection Url is " << redirectionTargetUrl.toString();
      QUrl url(redirectionTargetUrl.toString());
      manager->get(QNetworkRequest(url));
}

也可以用定时器定时来调用reply的abort或者close,会提前finished终止当前任务。Qt 5.15 新增了 setTransferTimeout 接口,可对 QNetworkRequest 或是 QNetworkAccessManager 设置。

//超时处理,可以使用定时器调用abort、close来终止当前的请求
QNetworkReply *reply=manager->get(request);
if(reply->isRunning()){
    QTimer *timer=new QTimer(reply);//对象树关联释放,也可以在finish进行释放
    timer->setSingleShot(true);
    //超时就结束,abort后状态码为0,错误信息为"Operation canceled"
    connect(timer,&QTimer::timeout,reply,&QNetworkReply::abort);
    //结束就关定时器
    connect(reply,&QNetworkReply::finished,timer,&QTimer::stop);
    //定时
    timer->start(5000);
}

5.表单数据QHttpMultiPart与QHttpPart

HTTP表单提交需要借助QHttpMultiPart与QHttpPart两个类,官方文档有个小示例(直接F1查看QHttpMultiPart),我这里稍加修改使之可以正常使用。

示例中图片的ContentDispositionHeader没有添加filename,导致测试时提交失败。示例中也没有设置Boundary分隔符,导致我提交到我们公司服务器时失败,加上就好了。这些应该都和服务器的设置有关。

void test(){    
    QNetworkAccessManager *manager=new QNetworkAccessManager(this);

    //构建一个multiPart用于提交表单
    //注意,multiPart请在请求完成后再删除
    QHttpMultiPart *multiPart=new QHttpMultiPart(QHttpMultiPart::FormDataType);

    //文本内容
    QHttpPart namePart;
    //Content-Type对照表详情百度http://tool.oschina.net/commons/
    namePart.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("text/plain"));
    namePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"thename\";"));
    namePart.setBody("gongjianbo");
    QHttpPart agePart;
    agePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"theage\";"));
    agePart.setBody("27");
    multiPart->append(namePart);
    multiPart->append(agePart);

    //文件内容
    QHttpPart filePart;
    filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); //貌似我们公司我用application也行
    //示例里没有filename,导致提交不成功
    filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"thefile\";filename=\"file.txt\";"));
    QFile *textFile = new QFile("F:/Src/file.txt");
    textFile->setParent(multiPart); //在删除reply时一并释放
    if(textFile->open(QIODevice::ReadOnly)){
        //要读取小块数据,请使用setBody(); 对于像图像这样的较大数据块,请使用setBodyDevice()。
        filePart.setBodyDevice(textFile);
        multiPart->append(filePart);
    }

    //图片内容
    QHttpPart imagePart;
    imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
    //示例里没有filename,导致提交不成功
    imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"theimage\";filename=\"image.png\";"));
    QFile *imageFile = new QFile("F:/Src/image.png");
    imageFile->setParent(multiPart); //在删除reply时一并释放
    if(imageFile->open(QIODevice::ReadOnly)){
        imagePart.setBodyDevice(imageFile);
        multiPart->append(imagePart);
    }

    //在我们公司里使用的时候,没有Boundary也会导致提交不成功
    multiPart->setBoundary("qtdata");
    QNetworkRequest request(QUrl("http://httpbin.org/post"));
    request.setRawHeader("Content-Type","multipart/form-data;boundary=qtdata");

    //提交表单
    QNetworkReply *reply=manager->post(request,multiPart);
    multiPart->setParent(reply); //在删除reply时一并释放

    QEventLoop eventLoop;
    connect(reply, &QNetworkReply ::finished, &eventLoop, &QEventLoop::quit);
    eventLoop.exec();

    qDebug()<<reply->readAll();
    reply->deleteLater();
}

6.访问HTTPS

支持HTTPS请求需要配置OpenSSL环境,Qt默认是不带SSl认证的,直接访问HTTPS会有错误信号。


    //默认链接的ssl库版本
    qDebug()<<QSslSocket::sslLibraryBuildVersionString();
    //支持的协议
    qDebug()<<manager->supportedSchemes();
    //正常的reply接收
    connect(manager,&QNetworkAccessManager::finished,
            this,&MainWindow::slotFinish);
    //ssl错误
    connect(manager,&QNetworkAccessManager::sslErrors,
            this,[=](QNetworkReply *reply,const QList<QSslError> &erros){
        qDebug()<<erros;
    });

(2019-8-28修改并完善https部分的内容)

以下是我测试的一些配置和测试(测试地址https://httpbin.org/post):

Qt5.9.8+MSVC2015-32bit+openssl1.0.x,将编译出来的libeay32.dll和ssleay32.dll放到exe同级目录能正常访问https(如果是在QtCreator中运行的话,放到安装环境的bin目录下也能使用)。

Qt5.9.8+MinGW-32bit+openssl1.0.x,复制Qt Tools目录下的libeay32.dll和ssleay32.dll放到exe同级目录能正常访问https(我的在D:\Qt\Qt5.9.8\Tools\QtCreator\bin目录)。

Qt5.12.4+MSVC2015-64bit,将编译出来的libcrypto-1_1-x64.dll和libssl-1_1-x64.dll放到exe同级目录就能正常访问https了(我没放openssl的库貌似也能访问,我在家又测试了一遍,发现也是msvc 64bit的不用dll也能访问https,但是32bit的就不行,不知道是系统里有这个库还是啥原因)。

Qt5.13.0+MSVC2019-32bit,将编译出来的libcrypto-1_1.dll和libssl-1_1.dll放到exe同级目录就能正常访问https了。

我的openssl编译记录:https://blog.csdn.net/gongjianbo1992/article/details/100115710

我生成的dll百度云连接:https://pan.baidu.com/s/1ALvI7FStr9YIx942QQTv1Q

提取码:0q96

7.操作实例(代码链接)

这里借助httpbin.org网站写一个简单的例子。httpbin.org是用 Python + Flask编写的一个开源项目,这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等,且支持 GET、POST 等多种方法,对开发和测试很有帮助。

我的示例链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/Qt5HttpDemo

8.参考

参考文档:https://doc.qt.io/qt-5/qnetworkaccessmanager.html

参考博客(HTTP):https://blog.csdn.net/liang19890820/article/details/52535755

参考博客(表单):https://blog.csdn.net/liang19890820/article/details/52548717

参考博客(表单):https://blog.csdn.net/u011728480/article/details/76851172?locationNum=1&fps=1

参考博客(HTTPS):https://www.cnblogs.com/jk-Huan/p/8953541.html

OpenSSL编译:https://blog.csdn.net/gongjianbo1992/article/details/100115710

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值