Solved QNetworkAccessManager reply is always empty
-
@shokarta
https://doc.qt.io/qt-5/qnetworkreply.html#errorerror() returns a
QNetworkReply::NetworkError
therefore:
QNetworkReply::NetworkError myError = reply->error();
-
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:
- try to open the network file
- wait (even sleep if needed) for reply
- if no error save the readAll() to the variable
- 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 -
@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 QTimerQEventLoop 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 whatQIODevice::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 thatQIODevice::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? -
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? -
@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? -
@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.
-
@shokarta
here, have a working class to use