2013-02-27 10 views
7

Sto tentando di utilizzare QNetworkAccessManager per caricare http multiparte su un server dedicato.QNetworkAccessManager: post http multipart da seriale QIODevice

Il multipart è costituito da una parte JSON che descrive i dati caricati.

I dati vengono letti da un dispositivo QIODial seriale, che crittografa i dati.

Questo è il codice che crea la domanda più parti:

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 

Se il p_encDevice è un'istanza di qfile, che file viene caricato bene.

Se viene utilizzato il dispositivo QIODisk di crittografia specializzato (dispositivo seriale), tutti i dati vengono letti dal mio dispositivo personalizzato. tuttavia QNetworkAccessManager :: post() non viene completato (si blocca).

ho letto nella documentazione di QHttpPart che:

se il dispositivo è sequenziale (ad esempio, prese, ma non i file), QNetworkAccessManager :: post() deve essere chiamato dopo che il dispositivo ha emesso finito() .

Purtroppo non so come farlo.

Si prega di avvisare.

EDIT:

QIODevice non ha fessura finito() affatto. Inoltre, la lettura dal mio IODevice personalizzato non si verifica affatto se QNetworkAccessManager :: post() non viene chiamato e quindi il dispositivo non sarebbe in grado di emettere tale evento. (Catch 22?)

EDIT 2:

Sembra che QNAM non funziona con i dispositivi sequenziali a tutti. Vedi discussion on qt-project.

EDIT 3:

sono riuscito a "ingannare" QNAM per farlo pensare che sta leggendo da dispositivi non sequenziali, ma cercare e ripristinare le funzioni impediscono alla ricerca. Questo funzionerà fino a quando QNAM cercherà effettivamente di cercare.

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

Penso che il segnale appropriato sia 'QIODevice :: readChannelFinished()'. Fondamentalmente 'QIODevice :: bytesAvailable()' deve restituire il valore corretto affinché funzioni. –

+0

Hai risolto il problema da allora, matejk? – lpapp

+0

Sono riuscito a risolverlo, ma non in modo pulito. Vedi il mio commento qui sotto. – matejk

risposta

0

Da una discussione separata in qt-project e ispezionando il codice sorgente sembra che QNAM non funzioni affatto in modo sequenziale. Sia la documentazione che il codice sono sbagliati.

+0

quindi come risolvere questo caso d'uso? Abbiamo bisogno di scendere a compromessi con grandi quantità di dati in memoria, o si avvia invece mmap'ing? – lpapp

+0

@LaszloPapp Ho creato il dispositivo di crittografia in modo che dichiari non sequenziale, ma le funzioni read() e seek() verificano la posizione corrente per assicurarsi che i dati vengano letti in modo sequenziale. QNAM sta leggendo i dati in sequenza. Non è bello, ma funziona. – matejk

2

Avrai bisogno di refactoring del codice di un bel po 'in modo che le variabili si passa a post sono disponibili al di fuori che la funzione che hai postato, allora avrete bisogno di un nuovo slot definito con il codice per fare il post all'interno dell'implementazione. Infine, devi fare connect(p_encDevice, SIGNAL(finished()), this, SLOT(yourSlot()) per incollarlo tutto insieme.

Per la maggior parte ci sei, devi solo rifattarlo e aggiungere un nuovo slot che puoi legare al segnale QIODevice::finished().

+0

Nicolas, grazie. Questo significa effettivamente che tutti i dati provenienti da p_encDevice in arrivo verranno letti nel buffer interno di QNetworkAccessManager prima che venga chiamato il post? Se è così, allora è molto più facile se leggo i dati in QByteArray e lo passiamo a QHttpPart :: setBody. – matejk

+0

Non leggerà nel buffer QNAM, essenzialmente continuerai a leggere nel filePart come fai ora ma avrai bisogno che filePart sia un membro della classe in modo che lo slot creato possa accedervi. –

+0

QIODevice non ha affatto finito lo slot.Inoltre, la lettura dal mio IODevice personalizzato non si verifica affatto se QNetworkAccessManager :: post non viene chiamato e quindi il dispositivo non sarebbe in grado di emettere un evento del genere. – matejk

1

Ho avuto più successo nel creare manualmente i dati dei post http rispetto all'uso di QHttpPart e QHttpMultiPart. So che probabilmente non è quello che vuoi sentire, ed è un po 'disordinato, ma sicuramente funziona.In questo esempio sto leggendo da un QFile, ma è possibile chiamare readAll() su qualsiasi QIODevice. Vale anche la pena notare, QIODevice::size() ti aiuterà a controllare se tutti i dati sono stati letti.

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

Quindi il server può accedere ai dati in questo modo:

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

spero che alcuni di questo codice può aiutare.

+0

Attualmente leggo i dati completi e li post, tuttavia non posso permettermelo perché i file possono essere molto grandi (alcuni GB). – matejk

+0

@matejk: esattamente, readAll non ha realmente bisogno di qualcosa di più di readAll e post da QNAM, ma le persone non possono farlo quando ci sono file di grandi dimensioni. – lpapp

1

Penso che il problema è che QNetworkAccessManager non supporta chunked transfer encoding durante il caricamento dei dati (POST, PUT). Ciò significa che QNAM deve conoscere in anticipo la lunghezza dei dati che sta per caricare, al fine di inviare l'intestazione Content-Length. Ciò implica:

  1. sia i dati non non provengono da dispositivi sequenziali, ma da dispositivi ad accesso casuale, che sarebbe correttamente registrare la loro dimensione totale attraverso size();
  2. oi dati provengono da un dispositivo sequenziale, ma il dispositivo ha già memorizzato tutto il buffer (questo è il significato della nota su finished()) e lo segnalerà (tramite bytesAvailable(), suppongo);
  3. oi dati proviene da un dispositivo sequenziale che non ha tamponato tutti i dati, che a sua volta significa
    1. sia QNAM legge e buffer si tutti i dati provenienti dal dispositivo (leggendo fino EOF)
    2. o l'utente imposta manualmente l'intestazione Content-Length per la richiesta.

(circa gli ultimi due punti, vedere la documentazione per l'QNetworkRequest :: DoNotBufferUploadDataAttribute.)

Quindi, QHttpMultiPart condivide in qualche modo queste limitazioni, ed è probabile che si tratta di soffocamento sul caso 3. Supponendo che non è possibile memorizzare in memoria tutti i dati dal dispositivo QIODrive "codificatore", è possibile conoscere in anticipo la dimensione dei dati codificati e impostare la lunghezza del contenuto su QHttpPart?

(Come ultima nota, non si dovrebbe utilizzare QScopedPointer. Ciò eliminerà il QNR quando il puntatore intelligente cade dall'ambito, ma non si vuole farlo. Si desidera eliminare il QNR quando esso emette finito()).

+0

Grazie per le opzioni aggiuntive da provare. Ho impostato DoNotBufferUploadDataAttribute e impostato la lunghezza del contenuto in modo esplicito, perché conosco la dimensione in anticipo, ma in realtà non ha aiutato. Il flusso di input viene letto sull'EOF, ma poi tutto si ferma. La documentazione menziona il segnale finished(), che QIODevice non emette affatto. – matejk

+0

Uso ScopedPointer intenzionalmente, perché il codice che segue attende il completamento del QNR. – matejk

+0

@matejk: sì, sto usando QScopedPointer anche in una funzione principale in cui QNetworkReply non può essere cancellato da esso poiché il loop di eventi Qt lo difende. Sì, non c'è il segnale finito(), sfortunatamente. – lpapp