PDA

View Full Version : Upload file to CloudApp problem



kandalf
18th November 2014, 15:30
Hi guys,

I'm having a problem when uploading a file to getcloudapp.com (CloudApp).
I'm following the instructions from the API documentation:

First a request for a new upload as shown here https://github.com/cloudapp/api/blob/master/upload-file.md#request-to-upload

Using a QNetworkAccessManager instance:



QUrl url("http://my.cl.ly/items/new");
QNetworkRequest request(url);

request.setRawHeader("Accept", "application/json");
networkManager->get(request);


The networkManager instance's finished(QNetworkReply*) signal is connected to a slot that processes the response shown in the API Documentation (https://github.com/cloudapp/api/blob/master/upload-file.md#response-with-free-plan) to make a new post for the actual upload:



void QCloudFileUploader::uploadFile(QNetworkReply *reply)
{
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(data, &error);
QJsonObject response = document.object();

QJsonObject params = response.take("params").toObject();
QHttpPart AWSKey;
QHttpPart key;
QHttpPart policy;
QHttpPart signature;
QHttpPart successRedirect;
QHttpPart acl;
QHttpPart filePart;
QMimeDatabase mime;

QUrl url(response.take("url").toString());
QHttpMultiPart *fileData = new QHttpMultiPart(this);
QCloudFileUploadThread *thread = uploaders.take(uploaders.keys().at(0));

AWSKey.setHeader(QNetworkRequest::ContentDispositi onHeader, QVariant("form-data; name=\"AWSAccessKeyId\""));
AWSKey.setBody(params["AWSAccessKeyId"].toString().toUtf8());
fileData->append(AWSKey);

key.setHeader(QNetworkRequest::ContentDispositionH eader, QVariant("form-data; name=\"key\""));
key.setBody(params["key"].toString().toUtf8());
fileData->append(key);

policy.setHeader(QNetworkRequest::ContentDispositi onHeader, QVariant("form-data; name=\"policy\""));
policy.setBody(params["policy"].toString().toUtf8());
fileData->append(policy);

signature.setHeader(QNetworkRequest::ContentDispos itionHeader, QVariant("form-data; name=\"signature\""));
signature.setBody(params["signature"].toString().toUtf8());
fileData->append(signature);

successRedirect.setHeader(QNetworkRequest::Content DispositionHeader, QVariant("form-data; name=\"success_action_redirect\""));
successRedirect.setBody(params["success_action_redirect"].toString().toUtf8());
fileData->append(successRedirect);

acl.setHeader(QNetworkRequest::ContentDispositionH eader, QVariant("form-data; name=\"acl\""));
acl.setBody(params["acl"].toString().toUtf8());
fileData->append(acl);

QNetworkRequest request(url);

QFile *file = new QFile(thread->fileName());
file->open(QIODevice::ReadOnly);

if (file->error() != QFile::NoError)
{
qDebug(file->errorString().toLatin1());
return;
}

QFileInfo fi(file->fileName());

QString contentDisposition = "form-data; name=\"file\"; filename=\"" + fi.completeBaseName() + "." + fi.completeSuffix() + "\"";
qDebug(contentDisposition.toLatin1());

filePart.setHeader(QNetworkRequest::ContentTypeHea der, QVariant(mime.mimeTypeForData(file).name()));
filePart.setHeader(QNetworkRequest::ContentDisposi tionHeader, QVariant(contentDisposition));
filePart.setBodyDevice(file);
file->setParent(fileData);
fileData->append(filePart);

QNetworkReply *reply = networkManager->post(request, fileData);

connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(notifyUpload(qint64,qint64)));
connect(reply, SIGNAL(finished()), this, SLOT(notifyUploadFinish()));
}


Don't pay too much attention to the code design, my intention here is to spot any error I might be missing.
The uploadProgress slot is reporting the file is uploaded completely. I'm skipping it here for clarity's sake.
The notifyUploadFinish() slot is getting the response of the POST request once it has finished and all looks OK:



void QCloudFileUploader::notifyUploadFinish()
{
finishedUploaders++;
qDebug("Finishing");

QNetworkReply *reply = (QNetworkReply*)sender();

if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute ) == 303)
{
qDebug(QString(reply->rawHeader("Location")).toLatin1());
QUrl url(QString(reply->rawHeader("Location")));
QNetworkRequest request(url);
reply = networkManager->get(request);
connect(reply, SIGNAL(finished()), this, SLOT(redirectFinished()));
return;
}

qDebug(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute ).toString().toLatin1());
qDebug("HTTP Redirection Target");
qDebug(reply->attribute(QNetworkRequest::RedirectionTargetAttrib ute).toString().toLatin1());
qDebug("HTTP Reason Phrase");
qDebug(reply->attribute(QNetworkRequest::HttpReasonPhraseAttribu te).toString().toLatin1());
}


The API documentation for upload to S3 (https://github.com/cloudapp/api/blob/master/upload-file.md#upload-file-to-s3) says the response should be a "303 See Other" (which is what I'm receiving), so then you need to ping CloudApp to get the details of the new file.

According to Qt documentation, Qt doesn't follow redirections so you need to do it manually. That's what I do if the response is 303, the location header looks like:

http://my.cl.ly/items/s3?bucket=f.cl.ly&key=items0.0000002V1n29090S1u2f3j0t230.000000arch-tux.png&etag=%22b3e0682a5bdaa51eb2a342a4ea77aa0f%22

For that GET request I'm getting a 302 response with the body:


<html><body>You are being <a href="http://my.cl.ly/">redirected</a>.</body></html>

So, no details as expected and the file is not shown under my account.

What caught my attention is that the get request does not get authenticated again, I'm assuming that's because the QNetworkAccessManager instance keeps the session information, but that's just a guess.

I've already tried posting the file to a local web application for debugging purposes and I could verify the params are sent correctly, I could even open the temp file generated by the web application and check it has the proper contents.

Also, I tried changing the parameters, and I saw different errors, so I could assume the parameters are correct like this.

Any clue would be very much appreciated.

Thanks a lot in advance.

kandalf
19th November 2014, 02:40
I solved it. The problem was the Accept header in the GET request to the uploaded file.

So, basically adding the header like below:


//Look at the 3rd code block in the post

if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute ) == 303)
{
qDebug(QString(reply->rawHeader("Location")).toLatin1());
QUrl url(QString(reply->rawHeader("Location")));
QNetworkRequest request(url);
request.setRawHeader("Accept", "application/json");
reply = networkManager->get(request);
connect(reply, SIGNAL(finished()), this, SLOT(redirectFinished()));
return;
}


Will turn the 302 into a 200 with the metadata of the uploaded file in the body in a JSON format.

Hope it helps.