2013-02-27 20 views
7

Tôi đang cố gắng sử dụng QNetworkAccessManager để tải lên các đa số http lên một máy chủ chuyên dụng.QNetworkAccessManager: đăng http multipart từ số seri QIODevice

Multipart bao gồm phần JSON mô tả dữ liệu đang được tải lên.

Dữ liệu được đọc từ một QIODevice nối tiếp, mã hóa dữ liệu.

Đây là mã tạo yêu cầu nhiều phần dữ liệu:

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 

Nếu p_encDevice là một thể hiện của QFile, tập tin đó được tải lên tốt.

Nếu mã hóa chuyên biệt QIODevice được sử dụng (thiết bị nối tiếp) thì tất cả dữ liệu được đọc từ thiết bị tùy chỉnh của tôi. tuy nhiên QNetworkAccessManager :: post() không hoàn thành (bị treo).

Tôi đọc trong tài liệu của QHttpPart rằng:

nếu thiết bị là tuần tự (ví dụ như ổ cắm, nhưng không phải tập tin), QNetworkAccessManager :: bài() nên được gọi sau khi thiết bị đã phát ra thành() .

Rất tiếc, tôi không biết cách thực hiện điều đó.

Vui lòng thông báo.

EDIT:

QIODevice không có() khe thành ở tất cả. Hơn nữa, đọc từ IODevice tùy chỉnh của tôi không xảy ra chút nào nếu QNetworkAccessManager :: post() không được gọi và do đó thiết bị sẽ không thể phát ra một sự kiện như vậy. (Catch 22?)

EDIT 2:

Dường như QNAM không làm việc với các thiết bị liên tục ở tất cả. Xem discussion on qt-project.

EDIT 3:

tôi quản lý để "đánh lừa" QNAM để làm cho nó nghĩ rằng nó được đọc từ các thiết bị không tuần tự, nhưng tìm kiếm và thiết lập lại chức năng ngăn chặn tìm kiếm. Điều này sẽ hoạt động cho đến khi QNAM thực sự sẽ cố gắng tìm kiếm.

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

Tôi nghĩ rằng tín hiệu thích hợp là 'QIODevice :: readChannelFinished()'. Về cơ bản 'QIODevice :: bytesAvailable()' phải trả về giá trị đúng cho nó hoạt động. –

+0

Bạn đã giải quyết vấn đề kể từ đó, matejk? – lpapp

+0

Tôi đã xoay sở để giải quyết nó, nhưng không phải là một cách rõ ràng. Xem bình luận của tôi dưới đây. – matejk

Trả lời

0

Từ một cuộc thảo luận riêng lẻ trong qt-project và bằng cách kiểm tra mã nguồn, có vẻ như QNAM không hoạt động với tuần tự. Cả tài liệu và mã đều sai.

+0

để biết cách giải quyết trường hợp sử dụng này? Chúng tôi cần phải thỏa hiệp với dữ liệu lớn trong bộ nhớ, hoặc bạn bắt đầu mmap'ing nó thay thế? – lpapp

+0

@LaszloPapp Tôi đã tạo thiết bị mã hóa theo cách mà nó tuyên bố là không tuần tự, nhưng các hàm read() và seek() xác minh vị trí hiện tại để đảm bảo rằng dữ liệu được đọc theo cách tuần tự. QNAM đang đọc dữ liệu tuần tự. Nó không đẹp, nhưng nó hoạt động. – matejk

2

Bạn sẽ cần phải cấu trúc lại mã của bạn khá nhiều do đó các biến bạn vượt qua để post có sẵn bên ngoài mà chức năng bạn đã đăng, sau đó bạn sẽ cần một khe mới được xác định với mã để làm post bên trong quá trình triển khai. Cuối cùng, bạn cần phải làm connect(p_encDevice, SIGNAL(finished()), this, SLOT(yourSlot()) để dán tất cả lại với nhau.

Bạn hầu như ở đó, bạn chỉ cần cấu trúc lại và thêm một vị trí mới mà bạn có thể liên kết với tín hiệu QIODevice::finished().

+0

Nicolas, cảm ơn. Điều này có nghĩa là tất cả dữ liệu từ p_encDevice đến sẽ được đọc vào bộ đệm trong của QNetworkAccessManager trước khi bài viết được gọi? Nếu nó là như vậy sau đó nó là dễ dàng hơn nhiều nếu tôi đọc dữ liệu vào QByteArray và vượt qua nó để QHttpPart :: setBody. – matejk

+0

Nó sẽ không đọc vào bộ đệm QNAM, về cơ bản bạn sẽ tiếp tục đọc vào tệp filePart như bạn làm bây giờ nhưng bạn sẽ cần tệpPart để trở thành thành viên của lớp để vùng bạn tạo có thể truy cập nó. –

+0

QIODevice không có khe cắm hoàn tất.Hơn nữa, đọc từ IODevice tùy chỉnh của tôi không xảy ra chút nào nếu QNetworkAccessManager :: post không được gọi và do đó thiết bị sẽ không thể phát ra một sự kiện như vậy. – matejk

1

Tôi đã tạo thành công hơn việc tạo dữ liệu bài đăng http theo cách thủ công so với sử dụng QHttpPartQHttpMultiPart. Tôi biết nó có thể không phải là những gì bạn muốn nghe, và đó là một chút lộn xộn, nhưng nó chắc chắn hoạt động.Trong ví dụ này tôi đang đọc từ số QFile, nhưng bạn có thể gọi readAll() trên bất kỳ QIODevice nào. Nó cũng đáng chú ý, QIODevice::size() sẽ giúp bạn kiểm tra xem tất cả dữ liệu đã được đọc chưa.

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

Sau đó, máy chủ có thể truy cập dữ liệu như thế này:

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

Tôi hy vọng một số mã này có thể giúp bạn.

+0

Tôi hiện đang đọc dữ liệu đầy đủ và đăng nó, tuy nhiên tôi không thể đủ khả năng này vì các tệp có thể rất lớn (một vài GB). – matejk

+0

@matejk: chính xác, readAll sẽ không thực sự cần bất cứ điều gì nhiều hơn readAll và đăng từ QNAM, nhưng mọi người không thể làm điều này khi có các tệp lớn. – lpapp

1

Tôi nghĩ rằng bắt là QNetworkAccessManager không hỗ trợ chunked transfer encoding khi tải lên (POST, PUT) dữ liệu. Điều này có nghĩa là QNAM phải biết trước độ dài của dữ liệu mà nó sẽ tải lên, để gửi tiêu đề Nội dung có độ dài. Điều này ngụ ý:

  1. hoặc dữ liệu không không đến từ các thiết bị tuần tự, nhưng từ các thiết bị truy cập ngẫu nhiên, trong đó sẽ báo cáo một cách chính xác tổng kích thước của họ thông qua size();
  2. hoặc dữ liệu đến từ thiết bị tuần tự, nhưng thiết bị đã lưu tất cả dữ liệu (đây là ý nghĩa của ghi chú về finished()) và sẽ báo cáo (thông qua bytesAvailable(), tôi giả sử);
  3. hoặc dữ liệu đến từ một thiết bị tuần tự mà chưa đệm tất cả các dữ liệu, do đó có nghĩa
    1. hoặc QNAM đọc và bộ đệm riêng của mình tất cả các dữ liệu đến từ các thiết bị (bằng cách đọc cho đến khi EOF)
    2. hoặc người dùng đặt tiêu đề Content-Length theo cách thủ công cho yêu cầu.

(Về hai điểm cuối cùng, xem tài liệu cho các QNetworkRequest :: DoNotBufferUploadDataAttribute.)

Vì vậy, QHttpMultiPart bằng cách nào đó chia sẻ những hạn chế này, và nó có khả năng rằng nó nghẹt thở về trường hợp 3. Giả mà bạn không thể đệm trong bộ nhớ tất cả dữ liệu từ bộ mã hóa "QIODevice" của bạn, có cơ hội nào bạn có thể biết trước kích thước của dữ liệu được mã hóa và đặt độ dài nội dung trên QHttpPart không?

(Lưu ý cuối cùng, bạn không nên sử dụng QScopedPointer. Điều này sẽ xóa QNR khi con trỏ thông minh nằm ngoài phạm vi, nhưng bạn không muốn làm điều đó. Bạn muốn xóa QNR khi nó phát ra thành()).

+0

Cảm ơn bạn đã chọn các tùy chọn bổ sung. Tôi đặt DoNotBufferUploadDataAttribute và đặt độ dài nội dung một cách rõ ràng, bởi vì tôi biết kích thước trước, nhưng nó thực sự không giúp được gì. Luồng đầu vào được đọc đến EOF, nhưng sau đó mọi thứ dừng lại. Tài liệu được đề cập đến tín hiệu hoàn thành(), mà QIODevice không phát ra ở tất cả. – matejk

+0

Tôi sử dụng ScopedPointer một cách có chủ ý, bởi vì mã sau đây đang chờ QNR kết thúc. – matejk

+0

@matejk: vâng, tôi đang sử dụng QScopedPointer cũng như trong một chức năng chính mà QNetworkReply không thể bị xóa bởi nó vì vòng lặp sự kiện Qt bảo vệ điều đó. Vâng, không có tín hiệu hoàn thành(). – lpapp

Các vấn đề liên quan