[SOLVED] QNetworkAccessManager - Adding a multipart/form-data to a POST request



  • I am trying to communicate with a web service to upload a file.
    Here is the web service : http://strava.github.io/api/v3/uploads/#post-file

    It seems I am unable to attach the file in the POST request. I have been looking for a way to do that with QNetworkAccessManager so I can do the POST request directly in Qt.

    Here is my current code, The server is answering with this error :
    "Error downloading https://www.strava.com/api/v3/uploads - server replied: Bad Request"

    QNetworkReply* ExtRequest::stravaUploadFile(QString access_token, QString activityName, QString activityDescription, QString pathToFile) {
    
        QNetworkAccessManager *managerWS = qApp->property("NetworkManagerWS").value<QNetworkAccessManager*>();
    
        const QString url =  "https://www.strava.com/api/v3/uploads";
    
        QUrlQuery postData;
        postData.addQueryItem("access_token", access_token);
    
        //        postData.addQueryItem("activity_type", "workout");
        //        postData.addQueryItem("name", activityName);
        //        postData.addQueryItem("description", activityDescription);
        //        postData.addQueryItem("trainer", QString::number(1));
    
        //Temporary test file
        QFile file("C:/test3.fit");
        QByteArray blob;
        if (file.open(QIODevice::ReadOnly))  {
            blob = file.readAll();
        }
        qDebug() << "blob size is" << blob.size();
    
        postData.addQueryItem("file", blob);
        postData.addQueryItem("data_type", "fit");
    
        QNetworkRequest request;
        request.setUrl(url);
        request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
    
        QNetworkReply *replyPutUser = managerWS->post(request, postData.toString(QUrl::FullyEncoded).toUtf8() );
        return replyPutUser;
    }
    

    Thanks for your help!



  • I think for the 'file', you have to give it the data in the file not just the file name. In their example that uses the curl command, that's the purpose of the @ before the filename (see the curl manpage).



  • @mchinand Thanks for the heads up, really newbie with curl!
    I'm now trying to put the data directly instead of the file name. (updated code up here)
    Not sure what kind of data use curl, doesn't seem to work with the file to QByteArray

    I think I need to use QHttpMultiPart, still not familiar with it tought.



  • I'm not sure either what you have to do with the data before using it with addQueryItem(). The curl manpage seems to indicate that it only strips out carriage returns and newlines when used like in Strava's example.



  • I've been trying this alternative, inspired from Qt doc.

    Not luck so far, don't see how to attach the "access_token" in the textPart

    QNetworkReply* ExtRequest::stravaUploadFile2(QString access_token, QString activityName, QString activityDescription, QString pathToFile) {
    
    QNetworkAccessManager *managerWS = qApp->property("NetworkManagerWS").value<QNetworkAccessManager*>();
    
    
    QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    
    QHttpPart textPart;
    textPart.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
    textPart.setBody("my text");
    //    textPart.setBody(QByteArray); //How to set parameters like with QUrlQuery
    postData.addQueryItem("access_token", access_token);
    
    
    QHttpPart imagePart;
    imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("file/fit"));
    imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data;"));
    QFile *file = new QFile("C:/test3.fit");
    file->open(QIODevice::ReadOnly);
    imagePart.setBodyDevice(file);
    file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
    
    multiPart->append(textPart);
    multiPart->append(imagePart);
    
    QUrl url("https://www.strava.com/api/v3/uploads");
    QNetworkRequest request(url);
    
    QNetworkReply *reply = managerWS->post(request, multiPart);
    multiPart->setParent(reply); // delete the multiPart with the reply
    
    return reply;
    

    }



  • Looks like your authkey should be in the header like in the curl example:

    "Authorization: Bearer 83ebeabdec09f6670863766f792ead24d61fe3f9"
    

    Of course, you use your own authkey.



  • Yes the authkey can be passed in the header or in the parameters.
    I still need to pass other parameters, like "data_type", so I'm passing both in parameters.
    Just haven't figured how to create multiple parameters with QHttpPart.
    Wish I could use curl directly!

    Thanks



  • Looks like each form parameter gets its own part. Here's what the curl command is sending (by adding the --trace-ascii tracefile.txt option)

    => Send header, 287 bytes (0x11f)
    0000: POST /api/v3/uploads HTTP/1.1
    001f: Host: www.strava.com
    0035: User-Agent: curl/7.42.1
    004e: Accept: */
    005b: Authorization: Bearer 83ebeabdec09f6670863766f792ead24d61fe3f9
    009b: Content-Length: 1182
    00b1: Expect: 100-continue
    00c7: Content-Type: multipart/form-data; boundary=--------------------
    0107: ----5812b831d34f5844
    011d:
    <= Recv header, 23 bytes (0x17)
    0000: HTTP/1.1 100 Continue
    => Send data, 262 bytes (0x106)
    0000: --------------------------5812b831d34f5844
    002c: Content-Disposition: form-data; name="activity_type"
    0062:
    0064: ride
    006a: --------------------------5812b831d34f5844
    0096: Content-Disposition: form-data; name="file"; filename="Activity.
    00d6: fit"
    00dc: Content-Type: application/octet-stream
    0104:
    => Send data, 771 bytes (0x303)
    0000: ..d......FIT@.....................^?...)........@...1.......@...1
    0040: .......A....................A.....................).......B.....
    0080: ...................).....a..........3...).....a..........3...)..
    00c0: ...a..........3...).....a9.........3...).....a@...y.....3...)...
    0100: ..aF...r...#.3...).....aJ...l...).3...).....aw.......r.3...)....
    0140: .a..........3.\.).....a....<.....3...).....a........_.3...).....
    0180: a....y.....3...).....a_.........3.3.).....a....W...=.3.p.)......
    01c0: .C..............................................................
    0200: ....)...).....a.......a....W..5...5....=.........p........A.....
    0240: ................)..........D....................................
    0280: .................................)...).....a.......5...5....=...
    02c0: ......p.............E...".......................).....5.)..c....
    0300: ...
    => Send data, 149 bytes (0x95)
    0000:
    0002: --------------------------5812b831d34f5844
    002e: Content-Disposition: form-data; name="data_type"
    0060:
    0062: fit
    0067: --------------------------5812b831d34f5844--
    


  • Interesting!
    Any way that you know to emulate that same curl command with QNetworkAccessManager?
    I guess I would have to send an header with the Content-Type "application/octet-stream" in a "QHttpMultiPart" , now to see if I can convert the file to this format somehow? My QByteArray get refused by the API.
    Will try some other stuff, in last resort, I will just redirect my request to my webserver (my app is requires Internet connection) instead of using Qt and create a normal curl request there.



  • Yes finally got it to work! Thanks for your message with the curl log, that helped me so much.
    I just had to add the "filename" in the fileDataPart and it worked.

        QHttpPart fileDataPart;
        fileDataPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"test3.fit\""));
        fileDataPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
    
        QFile *file = new QFile("C:/test3.fit");
        file->open(QIODevice::ReadOnly);
        qDebug() << "test file size:" << file->size();
        fileDataPart.setBodyDevice(file);
        file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.