Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QNetworkAccessManager reply is always empty



  • I am trying to read remote ini file to future use it into QSettings, however i keep getting empty reply.

        auto nam = new QNetworkAccessManager(this);
        QNetworkRequest request;
    //    request.setHeader(QNetworkRequest::ContentTypeHeader,"plain/text");
        request.setUrl(QUrl("http://somereachablelocation/settings.ini"));
    
        QNetworkReply* reply = nam->get(request);
        if (reply)
        {
            qDebug() << reply->readAll();
        }
        else {
            qDebug() << reply->error();
        }
    

    and i always have in debug only "".
    The ini file is reachable, because when i place the url http://somereachablelocation/settings.ini into the browser, it returns the content of the ini file.

    anything im forgetting?
    Im including:

    #include <QtNetwork/QNetworkAccessManager>
    #include <QtNetwork/QNetworkRequest>
    #include <QtNetwork/QNetworkReply>
    

    and in .pro file i of course have:

    QT += network
    

    Thank you



  • @shokarta Hello, sorry I was on my way home.
    Yes, I know sometimes we need to wait for the reply to be finished.
    It is not recommended, but possible.
    [Wow, this must be the most down-voted post I've ever posted, so you can see it is really not recommended.]
    And if you want to wait, you actually don't need to use lambda or slot.

    bool MSSQL::checkSettings()
    {
        //If you would call this function multiple times, it's better to declare a QNetworkAccessManager as a member variable
        QNetworkAccessManager nam;
    
        QNetworkRequest request(QUrl("http://somereachablle/settings.ini"));
        auto reply = nam.get(request);
    
        QEventLoop loop;
        QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
        loop.exec();
    
        reply->deleteLater();
        auto error = reply->error();
        if(error != QNetworkReply::NoError) {
            qDebug() << "network error:" << error << reply->errorString();
            return false;
        }
        QByteArray read = reply->readAll();
        if(read.isEmpty()) {
            qDebug() << "response is empty";
            return false;
        }
        QString fileName(QDir::currentPath() + "/" + settingsFile);
        QFile file(fileName);
        //[CHANGED]Remove QIODevice::Text to prevent line terminator mis-translating
        if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            qDebug() << "file open error:" << file.error() << file.errorString();
            return false;
        }
        //NOTE: Don't use QTextStream to write QByteArray to file, that's not the right way even if the result is correct!!!
        file.write(read);
        file.close();
        return true;
    }
    


  • QNetworkAccessManager is asynchronous. When you read the reply, the request is not even started.
    You should connect to the finished() signal of the reply.



  • Hello Bonnie,
    I have tried severeal methods indiates what you just said.
    Can you please show real example? Preferably on the code I have shown, in order to make sure my mistake is not involved, because im trying thos for couple of days :(


  • Moderators

    @shokarta

     auto nam = new QNetworkAccessManager(this);
         
    connect(nam, &QNetworkAccessManager::finished,this, [](QNetworkReply *reply)->void {
            qDebug() <<Q_FUNC_INFO << "QNetworkAccessManager::finished";
            qDebug = reply->readAll();
            reply->deleteLater();
        });
    
       QNetworkRequest request;
    //    request.setHeader(QNetworkRequest::ContentTypeHeader,"plain/text");
        request.setUrl(QUrl("http://somereachablelocation/settings.ini"));
    
        nam->get(request);
    


  • This post is deleted!

  • Moderators

    @shokarta you're right, my bad.

    debug my be the wrong tool here lets print a hex dumb and check for error:

    connect(nam, &QNetworkAccessManager::finished,this, [](QNetworkReply *reply)->void {
                qDebug() <<Q_FUNC_INFO << "QNetworkAccessManager::finished";
                QByteArray ba = reply->readAll();
                qDebug() << ba.toHex(' ');
    
                qDebug() << "Error ? " reply->error()
                reply->deleteLater();
            });
    

    It's probably also better to listen to pot errors of the network manager, those do emit error signals, listen to that ;)



  • @J-Hilk said in QNetworkAccessManager reply is always empty:

    reply->error()

    can I also save the error to the variable?
    QString replyError = reply->error();

    of course not like this, but how it would be the proper way?


  • Moderators

    @shokarta
    https://doc.qt.io/qt-5/qnetworkreply.html#error

    error() returns a QNetworkReply::NetworkError

    therefore:

    QNetworkReply::NetworkError  myError = reply->error();
    

  • Moderators

    I have the feeling, you're not overly familiar with lambdas, so lets split this properly into class functions.

    add the following into your header file:

    privat slots:
         void onNetworkReplyFinished(QNetworkReply *reply);
    

    in your cpp file add the following body:

    void myClassName:: onNetworkReplyFinished(QNetworkReply *reply)
    {
         qDebug() <<Q_FUNC_INFO << "QNetworkAccessManager::finished";
                     QByteArray ba = reply->readAll();
                     qDebug() << ba.toHex(' ');
         
                     qDebug() << "Error ? " reply->error()
                     reply->deleteLater();
    }
    

    change the connect to:

     auto nam = new QNetworkAccessManager(this);
         
    connect(nam, &QNetworkAccessManager::finished,this, & myClassName:: onNetworkReplyFinished);
    
       QNetworkRequest request;
    //    request.setHeader(QNetworkRequest::ContentTypeHeader,"plain/text");
        request.setUrl(QUrl("http://somereachablelocation/settings.ini"));
    
        nam->get(request);
    


  • @J-Hilk said in QNetworkAccessManager reply is always empty:

    I have the feeling, you're not overly familiar with lambdas, so lets split this properly into class functions.

    You are right, im not...
    However after evaluation, i dont see a reason why shouldnt I use it on my purposes just in a simple custom function as Im using it:

    bool MSSQL::checkSettings()
    {
        auto nam = new QNetworkAccessManager(this);
        connect(nam, &QNetworkAccessManager::finished, this, [](QNetworkReply *reply)->void {
            QByteArray iniOutput = reply->readAll();
            QNetworkReply::NetworkError iniError = reply->error();
            reply->deleteLater();
        });
        QNetworkRequest request;
        //request.setHeader(QNetworkRequest::ContentTypeHeader,"plain/text");
        request.setUrl(QUrl("http://somereachablle/settings.ini"));
        nam->get(request);
    
        if(iniError)
        {
            qDebug() << "Terminal DB Error: Remote INI file not reachable: " << iniError;
            return false;
        }
        else {
            QString fileName(QDir::currentPath() + "\\" + settingsFile);
            QFile file(fileName);
            file.open(QIODevice::ReadWrite | QIODevice::Text);
            QTextStream out(&file);
            out << iniOutput;
            file.close();
            return true;
        }
    }
    

    but as expected, unfortunately it does not recognize variables iniError and iniOutput

    I kinda understand why, however I dont know how to modify this to the logic of:

    1. try to open the network file
    2. wait (even sleep if needed) for reply
    3. if no error save the readAll() to the variable
    4. if error also store it in the variable so i can print it anytime later

    I know im asking a lot, but this should be quite easly possible, right? just dont know the correct approach...
    Kindly thank you


  • Moderators

    @shokarta here's the problem, the network call is asynchronous, so you can't (shouldn't) wait for the reply and return then.

    go with the approach of a separate function inside your class that is treated as a callback of sorts for the NetworkaccessManager

    void MSSQL::onNetworkReplyFinished(QNetworkReply *reply)
    {
         qDebug() <<Q_FUNC_INFO << "QNetworkAccessManager::finished";
                     QByteArray ba = reply->readAll();
                     qDebug() << ba.toHex(' ');
                      QNetworkReply::NetworkError iniError = reply->error();
        
                     reply->deleteLater();
    
    if(iniError)
        {
            qDebug() << "Terminal DB Error: Remote INI file not reachable: " << iniError;
            emit NetworkAccessWasSuccessful(false);
        }
        else {
            QString fileName(QDir::currentPath() + "\\" + settingsFile);
            QFile file(fileName);
            file.open(QIODevice::ReadWrite | QIODevice::Text);
            file.write(ba);
            file.close();
            emit NetworkAccessWasSuccessful(true);;
        }
    }
    


  • @shokarta Hello, sorry I was on my way home.
    Yes, I know sometimes we need to wait for the reply to be finished.
    It is not recommended, but possible.
    [Wow, this must be the most down-voted post I've ever posted, so you can see it is really not recommended.]
    And if you want to wait, you actually don't need to use lambda or slot.

    bool MSSQL::checkSettings()
    {
        //If you would call this function multiple times, it's better to declare a QNetworkAccessManager as a member variable
        QNetworkAccessManager nam;
    
        QNetworkRequest request(QUrl("http://somereachablle/settings.ini"));
        auto reply = nam.get(request);
    
        QEventLoop loop;
        QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
        loop.exec();
    
        reply->deleteLater();
        auto error = reply->error();
        if(error != QNetworkReply::NoError) {
            qDebug() << "network error:" << error << reply->errorString();
            return false;
        }
        QByteArray read = reply->readAll();
        if(read.isEmpty()) {
            qDebug() << "response is empty";
            return false;
        }
        QString fileName(QDir::currentPath() + "/" + settingsFile);
        QFile file(fileName);
        //[CHANGED]Remove QIODevice::Text to prevent line terminator mis-translating
        if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            qDebug() << "file open error:" << file.error() << file.errorString();
            return false;
        }
        //NOTE: Don't use QTextStream to write QByteArray to file, that's not the right way even if the result is correct!!!
        file.write(read);
        file.close();
        return true;
    }
    


  • @Bonnie thank you, this works very fine...
    Just for precausion, is it possible to sed timeout for the waiting loop?



  • @shokarta
    The QEventLoop itself doesn't have timeout function.
    But you could set a QTimer

    QEventLoop loop;
    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    QTimer::singleShot(30 * 1000, &loop, &QEventLoop::quit);
    loop.exec();
    
    reply->deleteLater();
    if(!reply->isFinished()) {
        qDebug() << "timeout";
        return false;
    }
    


  • @Bonnie sweet, not its realy ok :)
    One very last thing, I have noticed that the read content contains new rows as "\r\n" which after write() makes double empty rows instead of single ones.

    Im ok to fix it by:

        int start_pos = 0;
        QByteArray from("\r\n");
        QByteArray to("\n");
        while((start_pos = read.indexOf(from, start_pos)) != -1) {
            read.replace(start_pos, from.length(), to);
            start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
        }
    

    but im wondering if this can be prevented by setting header maybe?



  • Are you running the code in Windows?
    And the original text from the ini on the network should be "\n"?
    This is what QIODevice::Text supposed to do in Windows: replace "\n" to "\r\n" when writing.



  • Yes on Windows,
    however file is stored on linux network and i odnt know if there are \n or \r\n
    but when i qDebug() << read; even before QIODevice::Text i can see there are \r\n, therefore.
    Then i replace it with just \n and then qhen opening file via QIODevice::Text it writes just fine... therefore QIODevice::Text has no impact...



  • @shokarta
    I've tested on my Windows, QIODevice::Text does do what it is supposed to do.
    The thing is, "\r\n" is the right line terminator in Windows, so you will see a normal line break with a "\r\n".
    But if your original text is "\r\n", then it will be translated to "\r\r\n", that's not right.
    So I think you can just delete that QIODevice::Text flag.



  • @Bonnie said in QNetworkAccessManager reply is always empty:

    Yes, I know sometimes we need to wait for the reply to be finished.

    So connect the QNetworkReply::finished() signal then...

    QEventLoop loop;

    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);

    loop.exec();

    It doesn't look a pretty good idea to mess with the event loop. And not needed either



  • @Pablo-J-Rogina said in QNetworkAccessManager reply is always empty:

    It doesn't look a pretty good idea to mess with the event loop. And not needed either

    There are cases that we need to get response synchronously.
    In the OP's case for example, the function need to return a result of true / false.
    And this is a typical usage of QEventLoop: to wait until a signal.
    I've already said it is not recommended.
    But anyone should be free to use it if he insists a synchronous request.



  • @Bonnie so far im using your option, however in dev version of the application i will try the lamba solution
    @J-Hilk so far I have this:

    in .h file:

    public:
        explicit MSSQL(QObject *parent = nullptr);
        Q_INVOKABLE void checkSettings();
    
    private:
        QString settingsFile = "settings.ini";
        void onNetworkReplyFinished(QNetworkReply *reply);
    

    and in cpp file:

    MSSQL::MSSQL(QObject *parent) :
        QObject(parent)
    {
    }
    
    void MSSQL::checkSettings()
    {
        qDebug() << "test1";
        QNetworkAccessManager nam;
        connect(nam, &QNetworkAccessManager::finished, this, &MSSQL::onNetworkReplyFinished);
        QNetworkRequest request(QUrl("http://someinifilelocation.ini"));     // PROD
        nam.get(request);
        return;
    }
    
    void MSSQL::onNetworkReplyFinished(QNetworkReply *reply)
    {
        qDebug() << "test2";
        qDebug() <<Q_FUNC_INFO << "QNetworkAccessManager::finished";
        QByteArray read = reply->readAll();
        QNetworkReply::NetworkError error = reply->error();
        reply->deleteLater();
    
        if(error)
        {
            qDebug() << "Terminal DB Error: Remote INI file not reachable: " << error;
            return;
        }
        else {
            if(read.isEmpty()) {
                qDebug() << "Terminal DB Error: Remote INI file response is empty";
                return;
            }
            QByteArray newFile = QCryptographicHash::hash(read, QCryptographicHash::Md5);
    
            QString fileName(QDir::currentPath() + "/" + settingsFile);
            QFile file(fileName);
            if(!file.open(QIODevice::ReadWrite)) {
                qDebug() << "Terminal DB Error: Creating local INI:" << file.error() << file.errorString();
                return;
            }
            QByteArray oldFile = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
            if (oldFile != newFile)
            {
                qDebug() << "Terminal DB: Downloading new INI file from remote source";
                file.resize(0);
                file.write(read);
            }
            file.close();
            refreshParameters();
        }
    }
    

    but im having debug only on "test1"... test2 never happens :(
    what do I do wrong?


  • Lifetime Qt Champion

    Hi,

    Because "nam" is a function local variable that will be destroyed as soon as your function ends and thus everything connect to it will be disconnected.



  • @SGaist said in QNetworkAccessManager reply is always empty:

    Because "nam" is a function local variable that will be destroyed as soon as your function ends and thus everything connect to it will be disconnected.

    so &nam would be the solution? or sendig it with?
    any suggestion how to handle this?


  • Lifetime Qt Champion

    @shokarta
    You could move
    QNetworkAccessManager nam;
    to the .h inside class so its a class member.
    Then it will live as long as the class and you will no such issues.



  • @mrjj said in QNetworkAccessManager reply is always empty:

    You could move
    QNetworkAccessManager nam;

    So i did this, however it works very fine only the first time function checkSettings() is called, but second and all other times it always fails on:

    if(read.isEmpty())
    

    so how come its always empty since then?
    do I need to clear the buffer after each slot is set?


  • Lifetime Qt Champion

    @shokarta Did you move

    connect(nam, &QNetworkAccessManager::finished, this, &MSSQL::onNetworkReplyFinished);
    

    to the constructor of your class? As now you connect it each time checkSettings() is called.


  • Moderators

    @shokarta
    here, have a working class to use

    https://github.com/DeiVadder/QFileDownloader



  • well, yes, i did all those, now it works, however still all app is freezed between i ask for reply and i receive it...

    so i have this:

    .h file:

    public:
        explicit MSSQL(QObject *parent = nullptr);
        Q_INVOKABLE void checkSettings();
        Q_INVOKABLE bool checkConnection();
    
    private:
        QString settingsFile = "settings.ini";
    
        QNetworkAccessManager nam;
        void onNetworkReplyFinished(QNetworkReply *reply);
    

    and in cpp file:

    MSSQL::MSSQL(QObject *parent) :
        QObject(parent)
    {
        connect(&nam, &QNetworkAccessManager::finished, this, &MSSQL::onNetworkReplyFinished);
    }
    
    void MSSQL::checkSettings()
    {
        QNetworkRequest request(QUrl("http://some/settings.ini"));     // PROD
        nam.get(request);
        qDebug() << "test1";
        return;
    }
    
    void MSSQL::onNetworkReplyFinished(QNetworkReply *reply)
    {
        QByteArray read = reply->readAll();
        QNetworkReply::NetworkError error = reply->error();
        reply->deleteLater();
    
        if(error)
        {
            qDebug() << "Terminal DB Error: Remote INI file not reachable: " << error;
            return;
        }
        else {
            if(read.isEmpty()) {
                qDebug() << "Terminal DB Error: Remote INI file response is empty:" << read;
                return;
            }
            qDebug() << "test3 - not empty";
            QByteArray newFile = QCryptographicHash::hash(read, QCryptographicHash::Md5);
    
            QString fileName(QDir::currentPath() + "/" + settingsFile);
            QFile file(fileName);
            if(!file.open(QIODevice::ReadWrite)) {
                qDebug() << "Terminal DB Error: Creating local INI:" << file.error() << file.errorString();
                return;
            }
            QByteArray oldFile = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
            if (oldFile != newFile)
            {
                qDebug() << "Terminal DB: Downloading new INI file from remote source";
                file.resize(0);
                file.write(read);
            }
            file.close();
            refreshParameters();
        }
    }
    

    and basicaly the app debugs "test1" but then freezes all app till i see debug "test3 - not empty", so somehow it waits till the signal anynway? :(


  • Lifetime Qt Champion

    @shokarta From the code you posted I don't see why it should freeze, but I don't know what else you are doing.


Log in to reply