Qt实现HTTP客户端操作

0.前言

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

目录

1.简介

2.认识QNetworkAccessManager

3.服务请求QNetworkRequest

4.服务响应QNetworkReply

5.表单数据QHttpMultiPart与QHttpPart

6.访问HTTPS

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

8.参考


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(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
//eventLoop.exec();
//QByteArray reply_data=reply->readAll();

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

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

Qt实现HTTP客户端操作_第1张图片

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

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

QNetworkAccessManager *manager=new QNetworkAccessManager(this);
qDebug()<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()<

4.服务响应QNetworkReply

manager的finished信号发出后,就可以处理响应了。这里我使用的是连接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:"<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 url(redirectionTargetUrl.toString());
      manager->get(QNetworkRequest(url));
}

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(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
    eventLoop.exec();

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

6.访问HTTPS

支持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()<

Qt实现HTTP客户端操作_第2张图片

(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

Qt实现HTTP客户端操作_第3张图片

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

你可能感兴趣的:(Qt,稍纵即逝的追寻)