菜鸟笔记
提升您的技术认知

qt实现http客户端操作-ag真人游戏

0.前言

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

下一篇,文件上传下载:https://www.coonote.com/note/qt-http-upload.html

从qt4.4开始,引入了qnetworkrequest、qnetworkreply 和 qnetworkaccessmanager等类来进行http、ftp的操作,替代之前的qftp和qhttp。要使用这些类,先在pro文件中引入network模块:

qt  = network

网络访问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(q));
//这里也可以用一个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()<supportedschemes();
//安装了openssl,会有https
//("ftp", "file", "qrc", "http", "data")

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

//构建请求对象
qnetworkrequest request;
request.set);
request.setrawheader("content-type","application/json");
//发送请求
//manager->get(request);
//manager->post(request, qbytearray());
//manager->put(request, qbytearray());

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

qurl ;
    
//拼接
qurlquery query;
query.addqueryitem("ie","utf-8");
url.setquery(query);
qdebug()<mvalue("ie");
//"utf-8"

调用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:"<errorstring();
    }else{
        //请求方式
        qdebug()<<"operation:"<operation();
        //状态码
        qdebug()<<"status code:"<attribute(qnetworkrequest::httpstatuscodeattribute).toint();
        qdebug()<<"url:"<;
        //qdebug()<<"raw header:"<rawheaderlist();
        //获取响应信息
        const qbytearray reply_data=reply->readall();
        qdebug()<<"read all:"<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 );
      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);
}

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(q);
    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()<readall();
    reply->deletelater();
}

支持https请求需要配置openssl环境,qt默认是不带ssl认证的,直接访问https会有错误信号。


    //默认链接的ssl库版本
    qdebug()<supportedschemes();
    //正常的reply接收
    connect(manager,&qnetworkaccessmanager::finished,
            this,&mainwindow::slotfinish);
    //ssl错误
    connect(manager,&qnetworkaccessmanager::sslerrors,
            this,[=](qnetworkreply *reply,const qlist &erros){
        qdebug()<

(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

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

我的示例链接:https://github.com/gongjianbo/mytestcode/tree/master/qt/qt5httpdemo

网站地图