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 'hello!
'
#上传
@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/',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)
传文件主要是借助 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]"<open(qiodevice::readonly)){
file->deletelater();
qdebug()<<"open file error";
return;
}
qnetworkrequest request;
request.set);
//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"<attribute(qnetworkrequest::httpstatuscodeattribute).toint();
qdebug()<<"reply"<readall());
qdebug()<<"[upload file] finished";
}
文件的下载相对就更简单的,只是要注意处理下载的异常情况。
void httpmanager::download(const qstring &url, const qstring &fileid, const qstring &filepath)
{
qdebug()<<"[download file]"<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"<attribute(qnetworkrequest::httpstatuscodeattribute).toint();
//如果是无效的响应吧数据清除
if(status_code != 200)
file.remove();
qdebug()<<"reply"<readall());
qdebug()<<"[download file] finished";
}
我的 demo 只是测试了两个功能的基本使用,很多异常场景需要自己根据需求处理。对于大文件,目前测试将近 4g 可以正常上传下载,但是下载时可能会阻塞当前线程(我的 demo 没多线程所以界面会假死)。下面两个是个上传后下载的文件,分别为 1g 和 3点8g:
我的示例链接:https://github.com/gongjianbo/mytestcode/tree/master/qt/testqt_20210807_httpfile