Fetch text from a site as QString
-
wrote on 1 Oct 2024, 17:55 last edited by
How can I fetch text from a site as QString in a function of a C++ class?
Can I do it using Qt classes asQNetworkAccessManager
etc.? -
wrote on 9 Oct 2024, 17:23 last edited by JonB 10 Sept 2024, 17:24
@realroot
It looks reasonable. Although yourQNetworkAccessManager* m_manager = new QNetworkAccessManager(this);
will work personally I would do the
new
in theMyClass
constructor, to the line above where you have moved theconnect()
like @SGaist said. But maybe that's just me. In any case I believe your code is now acceptable. -
wrote on 1 Oct 2024, 19:12 last edited by
Hello!@realroot
I have made a library that reads a text string from a text file on a web server.
My library compares the loaded character string (which contains the latest version number) to the current version.You might be able to get some help if you check the part of the library that reads the text string.
https://gitlab.com/posktomten/libcheckforupdates -
wrote on 1 Oct 2024, 19:37 last edited by
-
How can I fetch text from a site as QString in a function of a C++ class?
Can I do it using Qt classes asQNetworkAccessManager
etc.?wrote on 1 Oct 2024, 22:52 last edited by@realroot said in Fetch text from a site as QString:
Can I do it using Qt classes as QNetworkAccessManager etc.?
Yes. Most of the logic is in this example.
-
wrote on 4 Oct 2024, 11:54 last edited by realroot 10 Apr 2024, 12:03
QNetworkAccessManager qnam; QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> reply; reply.reset(qnam.get(QNetworkRequest(QUrl( "https://...file")))); QByteArray bytes = reply->readAll(); QString s = QString::fromUtf8(bytes));
It's empty.
I just want to fetch the webpage as text no need to open files etc. -
QNetworkAccessManager qnam; QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> reply; reply.reset(qnam.get(QNetworkRequest(QUrl( "https://...file")))); QByteArray bytes = reply->readAll(); QString s = QString::fromUtf8(bytes));
It's empty.
I just want to fetch the webpage as text no need to open files etc.wrote on 4 Oct 2024, 12:12 last edited by JonB 10 Apr 2024, 12:48@realroot said in Fetch text from a site as QString:
It's empty.
Yes, it would (likely) be. You are calling
readAll()
too early. QNetworkAccessManager::get() only starts the process of getting the response contentPosts a request to obtain the contents of the target request and returns a new QNetworkReply object opened for reading which emits the readyRead() signal whenever new data arrives.
It is asynchronous. See https://doc.qt.io/qt-6/qnetworkaccessmanager.html#details for an example of what you should be doing. You need to act on the
QNetworkReply
'sreadyRead()
orfinished()
signals, there you will be able to read the data returned. -
Hi,
Beside what @JonB, you code sample also has an object lifetime issue. You need to ensure it lasts longer than the query call.
-
wrote on 4 Oct 2024, 19:08 last edited by
If I need to act on signals() does that mean that I have to connect my C++ class to do that?
connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished);
To fix the lifetime can I do this?
QString s = QString::fromUtf8(reply->readAll());
-
If I need to act on signals() does that mean that I have to connect my C++ class to do that?
connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished);
To fix the lifetime can I do this?
QString s = QString::fromUtf8(reply->readAll());
wrote on 4 Oct 2024, 19:19 last edited by JonB 10 Apr 2024, 19:20@realroot
Yes, after yourconnect()
(or you could have connected thereply
object) you should be able toreply->readAll()
in slot.No to second, that's not the issue. The reply needs to outlive where you do the
get()
, till (at least) thefinished()
. You won't want to use aQScopedPointer
, that will destroy it. And of course the QNAM must also be kept in existence. -
wrote on 4 Oct 2024, 19:49 last edited by
QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished); manager->get(QNetworkRequest(QUrl( "https://...file"))); // Class public slot function: void replyFinished(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); QFile file(<file>); if (file.open(QIODevice::WriteOnly)) { QTextStream out(&file); out << data; file.close(); doSomething(<file>); } } reply->deleteLater(); }
Thanks, this is working. Is it safe now?
-
When are you creating your manager object ? Based only on your code, it seems you will be creating it many times though you only need one instance during the lifetime of your application.
-
wrote on 5 Oct 2024, 07:28 last edited by
In the function there is no more code:
void MyClass::downloadFile() { QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished); manager->get(QNetworkRequest(QUrl( "https://...file"))); }
Should I make a manager instance as private member of MyClass?
-
In the function there is no more code:
void MyClass::downloadFile() { QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished); manager->get(QNetworkRequest(QUrl( "https://...file"))); }
Should I make a manager instance as private member of MyClass?
wrote on 5 Oct 2024, 08:08 last edited by JonB 10 May 2024, 08:14@realroot said in Fetch text from a site as QString:
In the function there is no more code:
It is not a question of whether this function has more code. It is a question of whether
MyClass::replyFinished()
completes all processing of the reply/downloading the file. Which I imagine it does.Should I make a manager instance as private member of MyClass?
Yes. And do not allocate it more than once! You could alternatively allocate
manager
within the class instead of withnew
, i.e.QNetworkAccessManager manager;
as a member variable would work. And don't forget you need to callreply->deleteLater()
inMyClass::replyFinished(QNetworkReply *reply)
. As per https://doc.qt.io/qt-6/qnetworkaccessmanager.html#detailsNote: After the request has finished, it is the responsibility of the user to delete the QNetworkReply object at an appropriate time. Do not directly delete it inside the slot connected to finished(). You can use the deleteLater() function.
-
In addition to what @JonB wrote, the fact that you pass a parent to your manager object only ensures that it will get destroyed when the parent gets destroyed.
What you currently have is a variant of memory leak since you create new instances of QNetworkAccessManager every time you call that function and they will only get destroyed when your MyClass instance will as well.
-
wrote on 5 Oct 2024, 15:15 last edited by
I see thanks.
If I declare it asQNetworkAccessManager manager;
I have errors so I made it like this:private: QNetworkAccessManager* m_manager = new QNetworkAccessManager(this); void MyClass::downloadFile() { connect(m_manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished); manager->get(QNetworkRequest(QUrl( "https://...file"))); } void MyClass::replyFinished(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); QFile file(<file>); if (file.open(QIODevice::WriteOnly)) { QTextStream out(&file); out << data; file.close(); doSomething(<file>); } } reply->deleteLater(); }
-
I see thanks.
If I declare it asQNetworkAccessManager manager;
I have errors so I made it like this:private: QNetworkAccessManager* m_manager = new QNetworkAccessManager(this); void MyClass::downloadFile() { connect(m_manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished); manager->get(QNetworkRequest(QUrl( "https://...file"))); } void MyClass::replyFinished(QNetworkReply *reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); QFile file(<file>); if (file.open(QIODevice::WriteOnly)) { QTextStream out(&file); out << data; file.close(); doSomething(<file>); } } reply->deleteLater(); }
@realroot said in Fetch text from a site as QString:
I see thanks.
If I declare it asQNetworkAccessManager manager;
I have errors so I made it like this:private: QNetworkAccessManager* m_manager = new QNetworkAccessManager(this); void MyClass::downloadFile() { connect(m_manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished);
Move that connect to the constructor of your class. Otherwise each time you call
downloadFile
you will create a new connection which means that the slot will be called an additional time. -
wrote on 7 Oct 2024, 15:25 last edited by
Then it should be so I believe:
class MyClass : public QAbstractListModel { Q_OBJECT public: MyClass(QObject *parent = nullptr) : QAbstractListModel(parent) { connect(m_manager, &QNetworkAccessManager::finished, this, &MyClass::downloadFinished); } private: QNetworkAccessManager* m_manager = new QNetworkAccessManager(this); void MyClass::downloadFile() { m_manager->get(QNetworkRequest(QUrl( "https://...file"))); }
It's working at least.
-
wrote on 9 Oct 2024, 15:55 last edited by
If something is wrong let me know, thanks.
-
wrote on 9 Oct 2024, 17:23 last edited by JonB 10 Sept 2024, 17:24
@realroot
It looks reasonable. Although yourQNetworkAccessManager* m_manager = new QNetworkAccessManager(this);
will work personally I would do the
new
in theMyClass
constructor, to the line above where you have moved theconnect()
like @SGaist said. But maybe that's just me. In any case I believe your code is now acceptable. -
-
wrote on 12 Oct 2024, 18:05 last edited by
@JonB Like this?
public: MyClass(QObject *parent = nullptr) : QAbstractListModel(parent) { m_manager = new QNetworkAccessManager(this); connect(m_manager, &QNetworkAccessManager::finished, this, &MyClass::downloadFinished); }