Search code examples
c++qtpostgetqnetworkaccessmanager

Best way to design multple and nested GET/POST in QT with QNetworkManager


the doubt i have is about the correct design of a software which implements multiple and nested GET/POST request.

Say you have to run a login() function which requires a GET and a POST and then retrieveXYZ() which requires two GETs (and so on, scalable).

The way i was thinkig to do it was like

mainwindow.cpp
    //code
    login();
    retrieveXYZ();
    //code

Mainwindow::login(){ 
    //code
    connect(nam, SIGNAL(finished()), this, SLOT(onGetLoginFinished()));
    nam->get(...);
}

Mainwindow::onGetLoginFinished(){
    //do stuff
    connect(nam, SIGNAL(finished()), this, SLOT(onPostLoginFinished()));
    nam->post(...);
}    

Mainwindow::onPostLoginFinished(){
    //do stuff
}

Mainwindow::retrieveXYZ(){
    //code
    connect(nam, SIGNAL(finished()), this, SLOT(onGet1RetrieveFinished()));
    nam->get();
    //code
}

Mainwindow::onGet1RetrieveXYZFinished(){
    //do stuff
    connect(nam, SIGNAL(finished()), this, SLOT(onGet2RetrieveFinished()));
    nam->get();
}    

or should i use something like QSignalMapper ? Which are the most correct/efficient way to do so? i've seen people using sender() cast but i didn't understand the point.

Basically i would like to retrieve the particular reply finished() signal rather than the general one (or of the qnam)

This method may work but it is not nice and clean to me

Is this the best we can get?

http://www.johanpaul.com/blog/2011/07/why-qnetworkaccessmanager-should-not-have-the-finishedqnetworkreply-signal/

Moving the connect approach to the reply?


Solution

  • I got something like:

    struct RequestResult {
        int httpCode;
        QByteArray content;
    };
    
    RequestResult
    ExecuteRequest(const std::function<QNetworkReply*(QNetworkAccessManager&)>& action,
                   const std::chrono::milliseconds& timeOut)
    {
        QEventLoop eLoop;
        QTimer timeOutTimer;
        QNetworkAccessManager nam;
    
        QObject::connect(&timeOutTimer, &QTimer::timeout, &eLoop, &QEventLoop::quit);
        QObject::connect(&nam, &QNetworkAccessManager::finished, &eLoop, &QEventLoop::quit);
    
        timeOutTimer.setSingleShot(true);
        timeOutTimer.setInterval(timeOut.count());
        timeOutTimer.start();
    
        auto resetTimeOut = [&timeOutTimer]() { timeOutTimer.start(); };
        QNetworkReply* reply = action(nam);
    
        QObject::connect(reply, &QNetworkReply::uploadProgress, resetTimeOut);
        QObject::connect(reply, &QNetworkReply::downloadProgress, resetTimeOut);
    
        eLoop.exec();
    
        if (!timeOutTimer.isActive())
        {
            throw std::runtime_error("Time out"); // Probably custom exception
        }
        const int httpStatus
            = reply->attribute(QNetworkRequest::Attribute::HttpStatusCodeAttribute).toInt();
        auto content = TakeContent(*reply); // reply->readAll and decompression
    
        return RequestResult{httpStatus, content};
    }
    

    And then functions for get/delete/post/.. which are similar to

    auto RequestGet(const QNetworkRequest& request) {
        return ExecuteRequest([&](QNetworkAccessManager& nam) { return nam.get(request); },
                              timeOut);
    }
    
    auto RequestDelete(const QNetworkRequest& request) {
        return ExecuteRequest([&](QNetworkAccessManager& nam) {
                                  return nam.deleteResource(request);
                              },
                              timeOut);
    }
    
    auto RequestPost(const QNetworkRequest& request, QHttpMultiPart& multiPart)
    {
        return ExecuteRequest([&](QNetworkAccessManager& nam) {
                                  return nam.post(request, &multiPart);
                              },
                              timeOut);
    }
    

    Then, for your code, I would do something like

    Mainwindow::login()
    {
        const auto getRes = RequestGet(..);
        // ...
        const auto postRes = RequestPost(..);
        // ...
    }
    

    And you may use thread and future if you want not blocking calls.