QNetworkAccessManager: post http multipart из последовательного QIODevice

Я пытаюсь использовать QNetworkAccessManager для загрузки http multiparts на выделенный сервер.

составная часть состоит из части 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() должен быть вызван после того, как устройство emitted finished().

к сожалению, я не знаю, как это сделать.

пожалуйста советовать.

EDIT:

qiodevice вообще не закончил слот (). Более того, чтение из моего пользовательского IODevice не происходит вообще, если QNetworkAccessManager::post() не вызывается, и поэтому устройство не сможет выдать такое событие. (Поймать 22?)

EDIT 2:

похоже, что QNAM вообще не работает с последовательными устройствами. См.обсуждение qt-project.

EDIT 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);
}

4 ответов


вам потребуется рефакторинг довольно много кода, так что переменные передаются post доступны вне этой функции, которую вы опубликовали, тогда вам понадобится новый слот, определенный с кодом для выполнения post внутри реализации. Наконец, вам нужно сделать connect(p_encDevice, SIGNAL(finished()), this, SLOT(yourSlot()) чтобы склеить все это вместе.

ты в основном там, нужно просто переделать его и добавить новый слот, который вы можете связать к QIODevice::finished() сигнал.


у меня было больше успеха в создании данных HTTP post вручную, чем с помощью QHttpPart и QHttpMultiPart. Я знаю, это, вероятно, не то, что ты хочешь услышать, и это немного грязно, но это определенно работает. В этом примере я читаю из QFile, но вы можете называть readAll() любой QIODevice. Также стоит отметить, 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.";
?>

Я надеюсь, что некоторые из этот код может вам помочь.


Я думаю, что уловка в том, что QNetworkAccessManager не поддерживает поблочного кодирования при загрузке (POST, PUT) данных. Это означает, что QNAM должен заранее знать длину данных, которые он собирается загрузить, чтобы отправить заголовок Content-Length. Это означает:

  1. либо данных не приходят от последовательных устройств, но от устройств с произвольным доступом, которые правильно сообщают об их общем размере через size();
  2. или данные поступают с последовательного устройства, но устройство уже буферизовало все это (это значение примечания о finished()), и сообщит об этом (через bytesAvailable(), Я полагаю);
  3. или данные поступают с последовательного устройства, которое не буферизовало все данные, что в свою очередь означает
    1. либо QNAM читает и буферизует все данные, поступающие с устройства (читая до EOF)
    2. или пользователь вручную Контент-длина заголовка запроса.

(о последних двух точках см. В документах для Qnetworkrequest::DoNotBufferUploadDataAttribute.)

Итак, QHttpMultiPart каким-то образом разделяет эти ограничения, и, вероятно, он задыхается в случае 3. Предположим, что вы не можете буферизировать в памяти все данные из вашего" кодировщика " QIODevice, есть ли вероятность, что вы заранее знаете размер закодированных данных и установите длину содержимого на QHttpPart?

(в качестве последнего Примечания вы не должны использовать QScopedPointer. Это удалит QNR, когда умный указатель выпадет из области видимости, но вы не хотите этого делать. Вы хотите удалить QNR, когда он испускает finished ()).


из отдельного обсуждения в qt-project и, проверяя исходный код, кажется,что QNAM вообще не работает с последовательным. И документация, и код неверны.