QNetworkAccessManager:从串行 QIODevice 发布 http 多部分
Posted
技术标签:
【中文标题】QNetworkAccessManager:从串行 QIODevice 发布 http 多部分【英文标题】:QNetworkAccessManager: post http multipart from serial QIODevice 【发布时间】:2013-02-27 10:24:39 【问题描述】:我正在尝试使用 QNetworkAccessManager 将 http 多部分上传到专用服务器。
多部分由描述上传数据的 JSON 部分组成。
数据从串行 QIODevice 中读取,该设备对数据进行加密。
这是创建多部分请求的代码:
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart metaPart;
metaPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
metaPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"metadata\""));
metaPart.setBody(meta.toJson());
multiPart->append(metaPart);
QHttpPart filePart;
filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(fileFormat));
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\""));
filePart.setBodyDevice(p_encDevice);
p_encDevice->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
multiPart->append(filePart);
QNetworkAccessManager netMgr;
QScopedPointer<QNetworkReply> reply( netMgr.post(request, multiPart) );
multiPart->setParent(reply.data()); // delete the multiPart with the reply
如果 p_encDevice 是 QFile 的实例,则该文件可以正常上传。
如果使用专门的加密 QIODevice(串行设备),那么所有数据都是从我的自定义设备中读取的。但是 QNetworkAccessManager::post() 没有完成(挂起)。
我在QHttpPart 的文档中读到:
如果设备是连续的(例如套接字,但不是文件), QNetworkAccessManager::post() 应该在设备完成后调用 发出完成()。
很遗憾,我不知道该怎么做。
请指教。
编辑:
QIODevice 根本没有finished() 槽。更重要的是,如果没有调用 QNetworkAccessManager::post(),则根本不会从我的自定义 IODevice 中读取数据,因此设备将无法发出这样的事件。 (第 22 条?)
编辑 2:
QNAM 似乎根本不适用于顺序设备。见discussion on qt-project。
编辑 3:
我设法“欺骗”了 QNAM,让它认为它正在从非顺序设备读取,但查找和重置功能阻止了查找。这将一直有效,直到 QNAM 真正尝试寻找。
bool AesDevice::isSequential() const
return false;
bool AesDevice::reset()
if (this->pos() != 0)
return false;
return QIODevice::reset();
bool AesDevice::seek(qint64 pos)
if (this->pos() != pos)
return false;
return QIODevice::seek(pos);
【问题讨论】:
我认为合适的信号是QIODevice::readChannelFinished()
。基本上QIODevice::bytesAvailable()
必须返回正确的值才能正常工作。
从那以后你解决了这个问题吗,matejk?
我设法解决了它,但不是以干净的方式。请参阅下面的评论。
【参考方案1】:
您需要大量重构代码,以便传递给post
的变量在您发布的函数之外可用,然后您需要使用代码定义的新槽来执行@ 987654322@里面的实现。最后,您需要使用connect(p_encDevice, SIGNAL(finished()), this, SLOT(yourSlot())
将它们粘合在一起。
您基本上已经完成了,您只需要重构它并添加一个可以绑定到QIODevice::finished()
信号的新插槽。
【讨论】:
尼古拉斯,谢谢。这是否实际上意味着来自传入 p_encDevice 的所有数据都将在调用 post 之前被读入 QNetworkAccessManager 的内部缓冲区?如果是这样,那么将数据读入 QByteArray 并将其传递给 QHttpPart::setBody 会容易得多。 它不会读入 QNAM 缓冲区,本质上您将像现在一样继续读入 filePart 但您需要 filePart 成为类成员,以便您创建的插槽可以访问它。 QIODevice 根本没有完成插槽。更重要的是,如果没有调用 QNetworkAccessManager::post,则根本不会从我的自定义 IODevice 中读取数据,因此设备将无法发出这样的事件。 @matejk:确实,没有 QIODevice::finished()。我想知道尼古拉斯打算写什么……【参考方案2】:与使用QHttpPart
和QHttpMultiPart
相比,手动创建http 帖子数据的成功率更高。我知道这可能不是你想听到的,而且有点乱,但它确实有效。在此示例中,我正在阅读QFile
,但您可以在任何QIODevice
上调用readAll()
。还有一点值得注意,QIODevice::size()
会帮你检查是否所有的数据都被读取了。
QByteArray postData;
QFile *file=new QFile("/tmp/image.jpg");
if(!(file->open(QIODevice::ReadOnly)))
qDebug() << "Could not open file for reading: "<< file->fileName();
return;
//create a header that the server can recognize
postData.insert(0,"--AaB03x\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"image.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n");
postData.append(file->readAll());
postData.append("\r\n--AaB03x--\r\n");
//here you can add additional parameters that your server may need to parse the data at the end of the url
QString check(QString(POST_URL)+"?fn="+fn+"&md="+md);
QNetworkRequest req(QUrl(check.toLocal8Bit()));
req.setHeader(QNetworkRequest::ContentTypeHeader,"multipart/form-data; boundary=AaB03x");
QVariant l=postData.length();
req.setHeader(QNetworkRequest::ContentLengthHeader,l.toString());
file->close();
//free up memory
delete(file);
//post the data
reply=manager->post(req,postData);
//connect the reply object so we can track the progress of the upload
connect(reply,SIGNAL(uploadProgress(qint64,qint64)),this,SLOT(updateProgress(qint64,qint64)));
那么服务器就可以这样访问数据了:
<?php
$filename=$_REQUEST['fn'];
$makedir=$_REQUEST['md'];
if($_FILES["attachment"]["type"]=="image/jpeg")
if(!move_uploaded_file($_FILES["attachment"]["tmp_name"], "/directory/" . $filename))
echo "File Error";
error_log("Uploaded File Error");
exit();
;
else
print("no file");
error_log("No File");
exit();
echo "Success.";
?>
我希望这些代码可以帮助到你。
【讨论】:
我目前阅读了完整的数据并将其发布,但是我负担不起,因为文件可能非常大(几 GB)。 @matejk:确切地说,readAll 实际上除了 readAll 和 QNAM 发布之外不需要任何其他东西,但是当文件很大时人们不能这样做。【参考方案3】:我认为问题是 QNetworkAccessManager 在上传(POST,PUT)数据时不支持chunked transfer encoding。这意味着 QNAM 必须提前知道要上传的数据的长度,以便发送 Content-Length 标头。这意味着:
-
数据不是来自顺序设备,而是来自随机访问设备,它们会通过
size()
正确报告其总大小;
或者数据来自顺序设备,但是设备已经全部缓冲了(这就是finished()
的注释的意思),会报告(我想是通过bytesAvailable()
);
或者数据来自没有缓冲所有数据的顺序设备,这反过来意味着
-
任一 QNAM 都会读取并缓冲来自设备的所有数据(通过读取直到 EOF)
或者用户手动设置请求的 Content-Length 标头。
(关于最后两点,请参阅 QNetworkRequest::DoNotBufferUploadDataAttribute 的文档。)
所以,QHttpMultiPart 以某种方式共享这些限制,并且它很可能在案例 3 中窒息。假设您不可能在内存中缓冲来自“编码器”QIODevice 的所有数据,您是否有可能知道提前编码数据并在 QHttpPart 上设置内容长度?
(最后一点,你不应该使用 QScopedPointer。当智能指针超出范围时,这将删除 QNR,但你不想这样做。你想在 QNR 发出时删除它完成())。
【讨论】:
感谢您尝试其他选项。我设置了 DoNotBufferUploadDataAttribute 并明确设置了内容长度,因为我提前知道了大小,但它确实没有帮助。输入流被读取到 EOF,但随后一切都停止了。文档中提到了信号finished(),而QIODevice 根本不发出。 我故意使用 ScopedPointer,因为后面的代码正在等待 QNR 完成。 @matejk:是的,我也在一个主函数中使用 QScopedPointer,因为 Qt 事件循环对此进行了辩护,所以 QNetworkReply 不能被它删除。是的,很遗憾,没有finished() 信号。【参考方案4】:从qt-project 中的单独讨论和检查源代码来看,QNAM 似乎根本不适用于顺序。文档和代码都错了。
【讨论】:
那么如何解决这个用例呢?我们需要对内存中的大数据进行妥协,还是您开始对其进行映射? @LaszloPapp 我以声明为非顺序的方式创建了加密设备,但 read() 和 seek() 函数验证当前位置以确保按顺序读取数据方式。 QNAM 正在按顺序读取数据。这不是很好,但它有效。以上是关于QNetworkAccessManager:从串行 QIODevice 发布 http 多部分的主要内容,如果未能解决你的问题,请参考以下文章
QT 从 QWebView 的 QNetworkAccessManager 读取数据
QNetworkAccessManager 可以从不同的线程获取/发布吗?
如何从 QNetworkAccessManager::networkAccessibleChanged() 获得信号?
QNetworkAccessManager 从 Internet 崩溃中下载图像预览