Solved 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 :( -
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! -
@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?
-
@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.