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!</p>'

#上传
@app.route('/upload',methods=['POST'])
def upload_file():
    try:
        global filename_temp
        f = request.files['myfile']
        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/<fileid>',methods=['GET'])
def download_file(fileid):
    try:
        global filename_temp
        print('download file:'+filename_temp) #假装是通过id从数据库拿到的文件
        return send_from_directory(BASE_PATH,filename_temp,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) 
    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 只是测试了两个功能的基本使用,很多异常场景需要自己根据需求处理。

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

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

5.参考

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

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

上一篇:ntp服务端的安装与配置


下一篇:QT 学习笔记(1) ---- 打印调试信息