2013-02-27 9 views
7

Ich versuche, QNetworkAccessManager zu verwenden, um HTTP-Multipart auf einen dedizierten Server hochzuladen.QNetworkAccessManager: Post http Multipart von serieller QIODevice

Der Multipart besteht aus einem JSON-Teil, der die hochgeladenen Daten beschreibt.

Die Daten werden von einem seriellen QIODevice gelesen, der die Daten verschlüsselt.

Dies ist der Code, der die mehrt Anfrage erstellt:

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 

Wenn die p_encDevice eine Instanz von QFile ist, die Datei einfach gut hochgeladen wird.

Wenn die spezialisierte Verschlüsselung QIODevice verwendet wird (serielles Gerät), werden alle Daten von meinem benutzerdefinierten Gerät gelesen. QNetworkAccessManager :: post() wird jedoch nicht abgeschlossen (hängt).

ich in der Dokumentation von QHttpPart dass lesen:

wenn das Gerät sequentiell (zB Steckdosen, aber keine Dateien), QNetworkAccessManager :: post() sollte aufgerufen werden, nachdem Gerät fertig emittiert hat() .

Leider weiß ich nicht, wie das geht.

Bitte beraten.

EDIT:

QIODevice haben nicht fertig() Slot überhaupt. Außerdem passiert das Lesen von meinem benutzerdefinierten IODevice überhaupt nicht, wenn QNetworkAccessManager :: post() nicht aufgerufen wird und das Gerät daher kein solches Ereignis ausgeben kann. (Catch 22?)

EDIT 2:

Es scheint, dass QNAM nicht mit sequentiellen Geräten funktioniert. Siehe discussion on qt-project.

EDIT 3:

ich es geschafft, zu „täuschen“ QNAM, um es glauben zu machen, dass sie von nicht-sequenzielle Geräte ist das Lesen, sondern suchen und Reset-Funktionen sucht verhindern. Dies wird funktionieren, bis QNAM versuchen wird, zu suchen.

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); 
} 
+0

Ich denke, das entsprechende Signal ist 'QIODevice :: readChannelFinished()'. Grundsätzlich muss 'QIODevice :: bytesAvailable()' den korrekten Wert zurückgeben, damit es funktioniert. –

+0

Haben Sie das Problem seitdem gelöst, matejk? – lpapp

+0

Ich habe es gelöst, aber nicht auf eine saubere Art und Weise. Siehe meinen Kommentar unten. – matejk

Antwort

0

Aus einer separaten Diskussion in qt-project und durch die Überprüfung des Quellcodes scheint es, dass QNAM überhaupt nicht mit sequenziellen arbeitet. Die Dokumentation und der Code sind falsch.

+0

so, wie dieser Anwendungsfall zu lösen? Wir müssen Kompromisse mit großen Daten im Speicher eingehen, oder Sie fangen stattdessen an, es zu mappen? – lpapp

+0

@LaszloPapp Ich habe das Verschlüsselungsgerät so erstellt, dass es als nicht sequenziell deklariert wird, aber die Funktionen read() und seek() überprüfen die aktuelle Position, um sicherzustellen, dass die Daten sequenziell gelesen werden. QNAM liest die Daten sequenziell. Es ist nicht nett, aber es funktioniert. – matejk

2

Sie benötigen, um Ihren Code ziemlich viel Refactoring, so dass die Variablen, die Sie post passieren außerhalb dieser Funktion zur Verfügung stehen Sie gepostet haben, dann werden Sie einen neuen Slot mit dem zu tun Code definiert brauchen die post innerhalb der Implementierung. Schließlich müssen Sie connect(p_encDevice, SIGNAL(finished()), this, SLOT(yourSlot()) tun, um alles zusammen zu kleben.

Sie sind meistens dort, Sie müssen es nur umgestalten und einen neuen Steckplatz hinzufügen, den Sie an das Signal QIODevice::finished() binden können.

+0

Nicolas, danke. Bedeutet dies, dass alle Daten von eingehenden p_encDevice in den internen Puffer des QNetworkAccessManager eingelesen werden, bevor der Post aufgerufen wird? Wenn es so ist, dann ist es viel einfacher, wenn ich die Daten in QByteArray lese und sie an QHttpPart :: setBody übergebe. – matejk

+0

Es wird nicht in den QNAM-Puffer eingelesen, im Wesentlichen wirst du weiter in das filePart einlesen, wie du es jetzt tust, aber du brauchst filePart als Klassenmitglied, damit der von dir erstellte Slot darauf zugreifen kann. –

+0

QIODevice hat den Slot überhaupt nicht fertig gestellt.Darüber hinaus passiert das Lesen von meinem benutzerdefinierten IODevice überhaupt nicht, wenn QNetworkAccessManager :: post nicht aufgerufen wird und das Gerät daher kein solches Ereignis ausgeben kann. – matejk

1

Ich hatte mehr Erfolg beim Erstellen der HTTP-Post-Daten manuell als mit QHttpPart und QHttpMultiPart. Ich weiß, es ist wahrscheinlich nicht das, was du hören willst, und es ist ein bisschen chaotisch, aber es funktioniert definitiv.In diesem Beispiel lese ich von einem QFile, aber Sie können readAll() auf irgendeinem QIODevice anrufen. Es ist auch erwähnenswert, QIODevice::size() wird Ihnen helfen, zu überprüfen, ob alle Daten gelesen worden sind.

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

Dann kann der Server die Daten wie folgt zugreifen:

<?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."; 
?> 

ich einige dieser Code hoffen, dass Sie helfen können.

+0

Ich lese derzeit vollständige Daten und poste es, aber ich kann mir das nicht leisten, weil die Dateien sehr groß sein können (ein paar GB). – matejk

+0

@matejk: genau, readAll würde eigentlich nichts mehr als readAll und Post von QNAM benötigen, aber die Leute können dies nicht tun, wenn es große Dateien gibt. – lpapp

1

Ich denke, der Haken ist, dass QNetworkAccessManager chunked transfer encoding beim Hochladen von Daten (POST, PUT) nicht unterstützt. Das bedeutet, dass QNAM die Länge der Daten, die hochgeladen werden sollen, im Voraus wissen muss, um den Content-Length-Header zu senden. Dies bedeutet:

  1. entweder die Daten tut nicht von sequentiellen Geräte, sondern von Schreib-Lese-Geräte, die korrekt ihre Gesamtgröße durch size() berichten würde;
  2. oder die Daten kommen von einem sequentiellen Gerät, aber das Gerät hat bereits alles gepuffert (dies ist die Bedeutung der Notiz über finished()), und werde es melden (über bytesAvailable(), nehme ich an);
  3. oder die Daten stammen aus einer sequenziellen Vorrichtung, die alle, die Daten nicht zwischengespeichert ist, was wiederum bedeutet,
    1. entweder QNAM liest und Puffer selbst alle Daten von der Vorrichtung (durch Lesen, bis EOF)
    2. kommenden
    3. oder Der Benutzer legt den Content-Length-Header für die Anforderung manuell fest.

(über die letzten zwei Punkte, siehe die Dokumentation für die QNetworkRequest :: DoNotBufferUploadDataAttribute.)

So QHttpMultiPart irgendwie diese Einschränkungen teilt, und es ist wahrscheinlich, dass es auf Fall 3. Gesetzt erstickt dass Sie nicht alle Daten von Ihrem "Encoder" QIODevice in den Speicher puffern können, besteht die Möglichkeit, dass Sie die Größe der codierten Daten im Voraus kennen und die Inhaltslänge auf dem QHttpPart festlegen?

(Als letzter Hinweis sollten Sie QScopedPointer nicht verwenden. Das wird die QNR löschen, wenn der Smart Pointer außerhalb des Gültigkeitsbereichs fällt, aber Sie wollen das nicht tun. Sie möchten die QNR löschen, wenn es emittiert beendet()).

+0

Vielen Dank für zusätzliche Optionen. Ich habe DoNotBufferUploadDataAttribute gesetzt und die Inhaltslänge explizit festgelegt, weil ich die Größe im Voraus kenne, aber es hat wirklich nicht geholfen. Der Eingabestrom wird in den EOF gelesen, aber dann hört alles auf. Die Dokumentation erwähnt Signal beendet(), die QIODevice überhaupt nicht emittiert. – matejk

+0

Ich verwende ScopedPointer absichtlich, weil der folgende Code darauf wartet, dass der QNR beendet wird. – matejk

+0

@matejk: Ja, ich benutze QScopedPointer auch in einer Hauptfunktion, wo QNetworkReply nicht gelöscht werden kann, da die Qt-Event-Schleife dies verteidigt. Ja, leider gibt es kein fertiges() Signal. – lpapp