接上一篇 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