Qt实现HTTP文件上传下载(Flask服务端)

接上一篇 Qt HTTP 基本操作:https://blog.csdn.net/gongjianbo1992/article/details/97568863
1.准备服务端测试代码

只需要上传和下载两个接口,实际应用时可能还需要 token 验证之类的,而且也没有对文件传输结果进行校验。

   

#using flask 2.0.1
import os,sys
from flask import Flask,request,jsonify,send_file,send_from_directory

app = Flask(__name__)
filename_temp = ''
BASE_PATH=os.path.join(os.path.dirname(os.path.abspath(__file__)),'upload')

#测试
@app.route('/',methods=['GET','POST'])
def hello():
    return '<p>Hello! this is normal test junjun</p>'

#上传
@app.route('/upload',methods=['POST'])
def upload_file():
    try:
        global filename_temp
        f = request.files['agvFile']
        filename_temp = f.filename
        print('upload file:'+f.filename)
        f.save(os.path.join(BASE_PATH,f.filename))
        return jsonify({
            'filename':f.filename,
            'fileid':0 #假装对每个文件返回一个id,然后通过id再下载
        })
    except Exception as e:
        print('error:'+str(e))
        return jsonify({'error':0}),0

#下载
@app.route('/download/<fileName>',methods=['GET'])
def download_file(fileName):
    try:
        print('download file:'+fileName)
        return send_from_directory(BASE_PATH,fileName,as_attachment=True)
    except Exception as e:
        print('error:'+str(e))
        return jsonify({'error':0}),0 

if __name__ == '__main__':
    print('server runing... ...')
    if not os.path.exists(BASE_PATH): 
        os.makedirs(BASE_PATH)
    print(BASE_PATH)           
    app.run(host='127.0.0.1',port=12345,debug=True)
     

2.文件上传

传文件主要是借助 QHttpMultiPart 类,并设置 Content-Type 为 multipart/form-data。我用 Flask 测试的时候,要给 QHttpPart 设置 multipart/form-data ,Flask 的 request.files 才能拿到 form-data 的 filename 等信息。

form-data 里的 name 设置为某个值如 "myfile" 后,Flask 里可以用 request.files['myfile'] 获取到这个 part,进而拿到设置的 filename。

注意 get/post 返回的 QNetworkReply 需要自己释放,Qt 5.14 可以设置 QNetworkAccessManager 的 setAutoDeleteReplies(true) 自动释放,Qt 5.15 又新增了 setTransferTimeout 设置超时时间。

有些情况还需要设置 QHttpMultiPart 的 boundary,不过我这个测试 demo 暂时用不到。

   

void HttpManager::upload(const QString &url, const QString &filepath)
    {
        qDebug()<<"[upload file]"<<url<<QFileInfo(filepath).fileName();
        QFile *file=new QFile(filepath);
        if(!file->open(QIODevice::ReadOnly)){
            file->deleteLater();
            qDebug()<<"open file error";
            return;
        }
     
        QNetworkRequest request;
        request.setUrl(QUrl(url));
        //request.setRawHeader("Content-Type","multipart/form-data");
     
        //QHttpMultiPart需要在请求完成后释放
        QHttpMultiPart *multi_part = new QHttpMultiPart(QHttpMultiPart::FormDataType);
        QHttpPart file_part;
        file_part.setHeader(QNetworkRequest::ContentDispositionHeader,
                            QVariant(QString("form-data; name=\"myfile\"; filename=\"%1\";")
                                     .arg(QFileInfo(filepath).fileName())));
        //part.header加上这句flask.request.files才能拿到form-data的信息
        //注意不是request的header
        file_part.setRawHeader("Content-Type", "multipart/form-data");
        file_part.setBodyDevice(file);
        file->setParent(multi_part);
        multi_part->append(file_part);
     
        QNetworkReply *reply = manager.post(request,multi_part);
        multi_part->setParent(reply); //在删除reply时一并释放
     
        //因为是测试所以同步等待
        QEventLoop eventLoop;
        //上传进度
        connect(reply, &QNetworkReply::uploadProgress,
                this, [this](qint64 bytesSent, qint64 bytesTotal){
            qDebug()<<"[upload file] bytesSend"<<bytesSent<<"bytesTotal"<<bytesTotal;
        });
        //结束退出事件循环
        connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
        eventLoop.exec();
     
        int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        qDebug()<<"reply"<<status_code<<QString(reply->readAll());
        qDebug()<<"[upload file] finished";
    }

3.文件下载

文件的下载相对就更简单的,只是要注意处理下载的异常情况。

   

 void HttpManager::download(const QString &url, const QString &fileid, const QString &filepath)
    {
        qDebug()<<"[download file]"<<url<<fileid;
     
        QNetworkRequest request;
        request.setUrl(QUrl(url+QString("/%1").arg(fileid)));
        QNetworkReply *reply = manager.get(request);
     
        //先删除已有的
        QFile file(filepath);
        file.remove();
     
        //因为是测试所以同步等待
        QEventLoop eventLoop;
        //数据可读
        connect(reply, &QNetworkReply::readyRead, this, [this,reply,&file](){
               if(!reply->isOpen()){
                   if(!reply->open(QIODevice::ReadOnly)){
                       qDebug()<<"[download file] reply open failed";
                       return;
                   }
               }
               if(!file.isOpen()){
                   if(!file.open(QIODevice::WriteOnly | QIODevice::Append)){
                       qDebug()<<"[download file] file open failed";
                       return;
                   }
               }
               file.write(reply->readAll());
        });
        //下载进度
        connect(reply, &QNetworkReply::downloadProgress,
                this, [this](qint64 bytesReceived, qint64 bytesTotal){
            qDebug()<<"[download file] bytesReceived"<<bytesReceived<<"bytesTotal"<<bytesTotal;
        });
        //结束退出事件循环
        connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
        eventLoop.exec();
        file.close(); //关闭文件
     
        int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        //如果是无效的响应吧数据清除
        if(status_code != 200)
            file.remove();
        qDebug()<<"reply"<<status_code<<QString(reply->readAll());
        qDebug()<<"[download file] finished";
    }

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

我的 demo 只是测试了两个功能的基本使用,很多异常场景需要自己根据需求处理。对于大文件,目前测试将近 4G 可以正常上传下载,但是下载时可能会阻塞当前线程(我的 demo 没多线程所以界面会假死)。下面两个是个上传后下载的文件,分别为 1G 和 3点8G:

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

5.参考

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

博客:https://blog.csdn.net/u012321968/article/details/111636380
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值