QT: How to download from url while pressing a button - c++

I have this code:
QNetworkAccessManager man;
QNetworkRequest req(QUrl("URL"));
QString ua("HttpRequestDemo/0.1 (Win64) Qt/5.14.0");
req.setHeader(QNetworkRequest::UserAgentHeader, QVariant(ua));
QNetworkReply* reply = man.get(req);
QObject::connect(reply){
QByteArray read = reply->readLine();
QFile out("file.txt");
out.open(QIODevice::WriteOnly|QIODevice::Text);
out.write(read);
out.close();
})
This works on the main.cpp file, using the QCoreApplication, but I want to use the QApplication and download a specific data while pressing a button.
I put the same code on the on_pushButton_clicked() in the mainwindow.cpp file and it didn't even generate the file from the url.

The problem is that man and req go out of scope and are destroyed as soon as your on_pushButton_clicked() function returns, at which point the request probably hasn't even been sent yet.
You need to make sure that these objects outlive the current scope, either by making them members of the window class, or by allocating them on the heap and setting some QObject (maybe also the window class) as the parent.

The problem is that if you put the same code in a method like X you make QNetworkAccessManager a local variable that will be removed instantly that the connection is asynchronous. The solution is to make QNetworkAccessManager an attribute of the class.
*.h
private:
QNetworkAccessManager man;
*.cpp
void Klass::on_pushButton_clicked(){
QNetworkRequest req(QUrl("URL"));
QString ua("HttpRequestDemo/0.1 (Win64) Qt/5.14.2");
req.setHeader(QNetworkRequest::UserAgentHeader, QVariant(ua));
QNetworkReply* reply = man.get(req);
connect(reply, &QNetworkReply::finished, [&]() {
QByteArray read = reply->readAll();
QFile out("file.txt");
out.open(QIODevice::WriteOnly|QIODevice::Text);
out.write(read);
out.close();
reply->close();
reply->deleteLater();
})
}

If you are planning on potentially queuing very many downloads, I strongly recommend using libcurl in your Qt app. I was using QNetworkAccessManager to down 100+ financial quote files, and it would fail downloading ~ 1/3 of the time, and take a while to download. I switched to libcurl, and after figuring out how to get my crypto root certificates setup for https, it runs much faster, and almost never fails. I run it as a dll.
And yes, you will need to make sure the network manager, whether QNetworkManager or curl, doesn't go out of scope upon exiting the button handler. A more conventional pattern, although not necessarily better, is to either have a pointer to e.g. QNetworkManager in your parent class, and new it, or use a std::unique_ptr and std::make_unique (purportedly safer). Creating large objects on the stack can cause problems (in the old days, dare I say, stack overflows), and so is usually done on the heap. In this case, it's not very big, so it doesn't really matter. Alternatively, a form creating big objects might itself be created on the heap.

Related

Read a file in background to update Qjsonvalue

I need to update the content of a field on my QWidget via a JSON file (updated in real time). I've read about functions readLine() and readAll() of QFile, but when I try a loop like :
while(true):
jsfile.readLine()
creation of objects, update of values, display etc ...
I lost the focus on my window. But I want to keep the control of the application with my buttons and obviously to watch the evolution of the JSON values.
I have thought that Qt manages itself the events and keeps the focus on the current window, but like I've said, it's not the case.
Is there a good solution (multi threads maybe) to use my window while the application reads the file (with new informations in real time)?
(With the constraint "real time" I can't read the whole file every time and I've no choice about the format of this file)
Update
I tried the thread method.
So, I choose to create my thread instance into the main (with my main window) and connect here. But, when I run the program, I've this error :
no matching member function for call to 'connect'
Reader reader;
QObject::connect(controler, SIGNAL(ready()),
reader, SLOT(received()));
According to this error, I've thought that the reason was main don't inherits of Object, and so, I've move the connection ans the creation of thread instance into my main window.
Reader reader;
QObject::connect(reader, SIGNAL(newobject(QJsonObject)),
this, SLOT(displayJSON(QJsonObject)));
With this one, I've the same error while I've already connect lot of widget into this class without any error.
What can be the problem ?
Update 2
I've a solution when I give as argument my main window (controler) in reader's constructor and connect into this one but, if possible, I would an explanation for the previous problem.
The current problem that I have is that signals are emit well but slots are executed after the end the application (so after the end of the thread's execution and not during)
This isn't really the subject of this topic so we can close this one.
You can use QThread (Qt documentation: QThread) class to create a thread, which will read your file. The main thread will execute your GUI application and it will be available during file reading.
You can find a simple example in documentation for creating your thread:
class WorkerThread : public QThread
{
Q_OBJECT
void run() Q_DECL_OVERRIDE {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
You can modify this example for your purpose. For example, WorkerThread for your task may be something like this:
class WorkerThread : public QThread
{
Q_OBJECT
void run() Q_DECL_OVERRIDE {
while(!stopFlag)
{
// read JSON file to QByteArray. Use QFile and QTextStream
// use QJsonDocument to read JSON content
// find what is new in JSON
emit signalSomethingNew(/*parameters*/);
QThread::currentThread()->msleep(/*timeout*/);
}
}
signals:
void signalSomethingNew(/*parameters*/);
};
At the end you must implement slot on your QWidget for signalSomethingNew(/*parameters*/) and make connection:
connect(yourThread, &WorkingThread::signalSomethingNew, youWidget, &YouWidget::yourSlot);
For working with JSON data: QJsonDocument
I'm interpreting your question as "my application is unresponsive whilst doing work" rather than "my focus jumped to another window" - please comment if you meant something different.
You have a choice of options:
Create and run a background QThread to do the work. Have it emit signals (connected to your widgets using Qt::QueuedConnection - the default) when it has results to display.
This is a good solution when the worker has a lot of computation to do, or needs all the input to be read before it can start. It works very well when the target system has processors available with no other work to do.
Use a QSocketNotifier to signal your GUI thread when some of the input becomes available (note that the name is misleading - it actually works on all kinds of file descriptor, not just sockets).
This is appropriate when the algorithm is simple and incremental - i.e. if a small chunk of input can be read and processed quickly.
Incorporate periodic calls to processEvents() in your algorithm:
auto *const dispatcher = QThread::currentThread()->eventDispatcher;
while (line = json.readLine()) {
doSomethingWith(line);
if (dispatcher)
dispatcher->processEvents();
}
This won't work unless you can modify the algorithm like this - if the loop is in somebody else's (closed) code, then you'll need one of the other solutions.

Application crashes when using global QNetworkCookieJar for multiple windows

I am new at Qt and using Qt5.5 to create a http client application which will load a window with a fixed URL. After login in this window the other windows of same site should get the same session. At this purpose I have used global pointer jar of QNetworkCookieJar class and implemented on the following code for every window
Window1::Window1(QWidget *parent) :
QWidget(parent),
ui(new Ui::Window1)
{
ui->setupUi(this);
QUrl webURL("http://someURL");
ui->webView->show();
ui->webView->load(webURL);
ui->webView->page()->networkAccessManager()->setCookieJar(jar);
}
It solves the multiple authentication problem, but when I close any window and reopen it instantly it causes appcrash.
Please give me a suggestion on my problem. Thanks in advance.
From the Qt5 docs (http://doc.qt.io/qt-5/qnetworkaccessmanager.html#setCookieJar):
Note: QNetworkAccessManager takes ownership of the cookieJar object.
So the accessmanager will delete your jar instance. There might be your problem! I don't know enough about the webview/page/accessmanager to be sure about the lifetime of the manager, but it seems to be bound to the webview/your ui, so when it's closed+destroyed, your cookiejar will have gone, too.
As QNetworkCookieJar inherits from QObject, you might want to use a guarded QPointer<QNetworkCookieJar> jar instead of a simple QNetworkCookieJar* jar variable. Then, you will notice that your guarded pointer becomes null after the first window is closed/destroyed. That would a) verify my claim from the last paragraph and b) guard you against stale pointer accesses in the future (in fact, your program would no longer crash).
Thanks ThorngardSO..
I have found a solution using your answer. Here is my solution,
if(jar.isNull()==true){
qDebug()<<"object null";
QPointer <QNetworkCookieJar> jar_new = new QNetworkCookieJar(0);
ui->webView->page()->networkAccessManager()->setCookieJar(jar_new);
jar=jar_new;
}
else
ui->webView->page()->networkAccessManager()->setCookieJar(jar);

What's the quickest way to return the contents of a webpage in Qt?

so, I'm trying to run some simple code in Qt to return the contents of a given web page. After doing quick research, I was able to develop my own class to simplify the process:
WebFetch::WebFetch()
{
nam = new QNetworkAccessManager(this);
connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*)));
}
QString WebFetch::get(QString url)
{
nam->get(QNetworkRequest(QUrl(url)));
}
void WebFetch::finished(QNetworkReply* reply)
{
QByteArray data = reply->readAll();
QString str(data);
}
However, there big problem that I'm finding with the above code is that the call is asynchronous. I would like the "get" function to simply return the string after it is retrieved, which seems impossible on the account that it needs to wait for the finished signal, at which point there's no way of having "get" return whatever content is retrieved by the "finished" slot. Is there any alternative to the above method or is there a way I can get "get" to return the content retrieved by "finished"? Any help would be greatly appreciated. Thanks!
The call being asynchronous is not a problem - it's a big win. With a synchronous call, you're essentially wasting potentially hundreds ok KB of RAM, and an entire thread, just idly waiting for something to come back. You can't write such code while pretending that things happen synchronously or even "quickly" for that matter. I won't even comment on the insanity of running such synchronous code in the GUI thread. It's also a very bad idea to run a local event loop, since suddenly all of your GUI code becomes reentrant. My bet is that you neither design nor test for that.
You have to break down whatever code is expecting the result into two parts: the first part needs to place the request. The second part, in a slot, is notified when the request is finished and continues doing whatever is to be done.
If you wish to have it all in a single method, use C++11:
QNetworkAccessManager * mgr = ...;
QObject::connect(mgr, &QNetworkAccessManager::finished,
[this, mgr](QNetworkReply * reply){
// here you can do things with the reply
});
mgr->get(QNetworkRequest("....");
For a complete example, see this 300-line photographic mosaic generator that pulls random images from imgur. It extensively uses asynchronous, multithreaded processing and lambdas in the above style.

C++ - Qt QObject::connect GET request usage across classes

A quick overview of what's happening: I am trying to do a GET request using Qt's QNetworkAccessManager, but the callback function on my QObject::connect(..) function is not being called. My questions is can I call QObject::connect from one object, but connect to a slot of another object (given that I have a pointer to both the object and the slot) - See below for more details.
My ultimate goal is to POST the data (seeing as it's a login function), I had POST Request code that was ultimately suffering from the same issue - callback function not being called. So I would like to be able to do a simple GET request first, once I have that, I think I'll be fine on my own from there.
I currently have a QMainWindow LoginWindow, with a button that calls a slot doLogin() in the LoginWindow class. This all works as you would expect. LoginWindow also has a public slots function called loginResponse(QNetworkReply* response).
//---LoginWindow.h
...
public slots:
void doLogin();
void loginResponse(QNetworkReply* response)
...
//---LoginWindow.cpp
LoginWindow::LoginWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::LoginWindow)
{
ui->setupUi(this);
ui->username_le->setFocus();
}
void LoginWindow::doLogin()
{
MyProduct::Network network(this);
qDebug() << "Logging in...";
//Here I call network.login from LoginWindow and pass
//references to the Slot I want to use and the LoginWindow itself
network.login(
ui->username_le->text(), //username
ui->password_le->text(), //password
this, //reference to this object (LoginWindow*)
SLOT(loginResponse(QNetworkReply*)) //loginResponse slot
);
}
void LoginWindow::loginResponse(QNetworkReply* response)
{
qDebug() << "Log in complete";
}
Next I have another class, under the MyProduct namespace, called Network. As you can see above, Network has a function called login. Here it is:
void MyProduct::Network login(QString username, QString password, QObject *receiver, const char *slot)
{
QNetworkRequest request(QUrl(API_ROOT + LOGIN_PATH)); //"http://localhost/basic/login.php"
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
//nam = QNetworkAccessManager* declared in the constructor
QObject::connect(nam,SIGNAL(finished(QNetworkReply*)), receiver, slot);
qDebug() << "Posting login data...";
nam->get(request);
}
The goal here is to create a login function in my Network class that can be used and connected in any number of windows (as users may log in from multiple places). But I'm getting no response - LoginWindow::loginResponse is not run.
I see "Logging in..." and "Posting login data" output in the console, but not "Log in complete".
Can anyone please point me in the right direction or tell me I'm crazy or that this is a bad idea?
Thanks in advance!
Note that QNetworkAccessManager operates asynchronously. The get() method does not block while the network operation occurs; it returns immediately. (See the Detailed Description section of the documentation for more info.)
This is pretty typical of Qt's network-related APIs, because you usually don't want your application to freeze while waiting for data to move across a network.
What this means is what your instance, nam, isn't alive long enough for the GET request to actually finish. Your instance of the Product::Network class is deleted immediately after the call to login() because it's allocated on the stack. Although I can't see the code, I'm guessing it cleans up the QNetworkAccessManager as well.
If you extend the lifetime of your network object, you may find that your slot will eventually be invoked.
Also, this is more a matter of preference, but I think it would be cleaner to avoid passing a receiver and a slot to your login() function. I'd recommend declaring your own signals in the Network class as part of its API, and to connect to those in the LoginWindow class.

How to set QNetworkReply's content

This is my first post here, and I'm fairly new to Qt.
I am using Qwebkit in order to load a web page, and I'm interested in NOT fully load some resources from web. To be specific, I'm trying to get only the size of jpg files and not the image data from within the jpg's binary data (not HTML tags). For doing so, I have re-implemented the createRequest method of QNAM to do as follow:
QNetworkReply *NetworkAccessManager::createRequest(Operation op,const QNetworkRequest & req,QIODevice * outgoingData )
{
if (req.url().path().endsWith("jpg"))
{
CustomReply *reply = new CustomReply(QNetworkAccessManager::createRequest(op, req, outgoingData));
return reply->getQNR();
}else{
return QNetworkAccessManager::createRequest(op, req, outgoingData);
}
}
Then I connect some signal in my CustomReply class to append the coming data into a QByteArray, then I process the QByteArray to see if I have the marker I'm looking for. Now here I don't know how to proceed. What I want to do after this is closing the connection (to not download more) and passing the reply with the data I have received through CustomReply::getQNR(). I need to implement a function to set the content of my reply to the QByteArray I stored, and I have read this and that but couldn't solve my problem.
Thank you in advance.
I'm not 100% clear on your question but if you're trying to return the value of the reply you received to another QByteArray just set up an additional signal which is either fired on completion or called with emit and pass the value across that way.
I did as described here and solved my problem. I was missing the offset, so every time readData() was called, the data was read from the beginning. In order to close the connection I connect the finish signal of CustomReply into original QNReply. When I get enough data, I emit finish signal to close the connection. Calling abort or close will result in ERROR 5 (although you can handle the error but I find working with signal a bit cooler).
Thank you everybody.