Looping through QNetworkAccessManager get() routines, retrieve order on finish - c++

I have a QNetworkAccessManager as a member of my class. I connect the finished signal from this manager to the replyFinished function I have written.
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),this,SLOT(replyFinished(QNetworkReply*)));
In a separate routine, I loop through a get call from the manager
for (int si = 0; si<numLines; si++)
{
QString line = lines[si];
manager->get(QNetworkRequest(QUrl(line)));
}
In my replyFinished slot routine, I know I may not receive the signals in the order they were performed in the loop, but is there any way I can obtain that information? That is, is there a clever way I can obtain "si" in my replyFinished routine? Thanks for the help!

QNetworkAccessManager::get() returns a pointer to the QNetworkReply object. This pointer is the same one that is passed your replyFinished() slot. You can use a QMap to store pairings of QNetworkReply* pointers and integers (si in your code).
Here is a working example;
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QMap>
#include <QtDebug>
QNetworkAccessManager am;
void finished(QNetworkReply* reply);
QMap<QNetworkReply*, int> requests;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QObject::connect(&am, &QNetworkAccessManager::finished, finished);
QStringList links;
links << "http://google.com";
links << "http://taobao.com";
links << "http://stackoverflow.com";
links << "http://stackexchange.com";
links << "http://bing.com";
for (int i=0; i < links.size(); i++)
{
requests.insert(am.get(QNetworkRequest(QUrl(links[i]))), i);
}
return a.exec();
}
void finished(QNetworkReply* reply)
{
qDebug() << requests[reply];
}

The slot replyFinished(QNetworkReply*) receives pointer to the related reply object. This reply object contains all information about that reply (error code, headers, downloaded data, the URL of the content) and also it contains initial request (QNetworkReply::request()). So, it is possible to check the URL of the request or the URL of actual downloaded content. Note that those URLs may be different.
QNetworkReply::url():
Returns the URL of the content downloaded or uploaded. Note that the
URL may be different from that of the original request.
QNetworkReply::request():
Returns the request that was posted for this reply. In special, note
that the URL for the request may be different than that of the reply.
void MainWindow::replyFinished(QNetworkReply* reply)
{
qDebug() << reply->url();
qDebug() << reply->request().url();
}

Related

Multiple get/post calls using a wrapper class with Qt

I am working on a Qt project with a team. I have two functions — one retrives the numerical coordinates of a place, the other downloads the map of the place — that I want to merge in one wrapper class, so that my teammates can call it easily.
#include <QCoreApplication>
#include <QFile>
#include <QHttpMultiPart>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <iostream>
class OpenStreetMapWrapper: public QObject{
Q_OBJECT
public:
OpenStreetMapWrapper(QObject *parent=nullptr):QObject(parent){
connect(&manager, &QNetworkAccessManager::finished, this, &OpenStreetMapWrapper::handle_finished);
}
void download(const std::string &region, const std::string &department, const QFile& outfile){
QNetworkRequest request;
QUrl url = QUrl(QString::fromStdString("https://download.openstreetmap.fr/extracts/europe/france/" + region + "/" + department + ".osm.pbf"));
request.setUrl(url);
request.setAttribute(QNetworkRequest::User, outfile.fileName());
manager.get(request);
}
void searchCSV(QFile& file, QFile& outfile){
QNetworkRequest request(QUrl("https://api-adresse.data.gouv.fr/search/csv/")); // Free API provided by the French government
request.setAttribute(QNetworkRequest::User, outfile.fileName());
QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart postpart;
postpart.setHeader(QNetworkRequest::ContentDispositionHeader,
QString("form-data; name=%1; filename=%2")
.arg("data", file.fileName()));
postpart.setBodyDevice(&file);
multipart->append(postpart);
file.setParent(multipart);
manager.post(request, multipart);
}
private:
QNetworkAccessManager manager;
void handle_finished(QNetworkReply *reply){
if(reply->error() == QNetworkReply::NoError){
QByteArray read = reply->readAll();
std::cout << read.toStdString() << std::endl; // For debugging
QString filename = reply->request().attribute(QNetworkRequest::User).toString();
QFile out(filename);
if(out.open(QIODevice::WriteOnly)){
out.write(read);
out.close();
}
}
else{
qDebug() << reply->error() << reply->errorString();
}
reply->deleteLater();
// QCoreApplication::quit(); This is done somewhere else?
}
};
#include <main.moc>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
OpenStreetMapWrapper A;
QFile file("./search.csv");
file.open(QIODevice::ReadWrite);
QFile outfile("./output.csv");
outfile.open(QIODevice::ReadWrite);
// Search
A.searchCSV(file, outfile); // 1st call works
A.searchCSV(file, outfile); // 2nd call -> makes both calls fail.
// Downloader
std::string region = "corse";
std::string department = "haute_corse";
return a.exec();
}
The problem with the code above, is that when for example searchCSV is called it displays the output as needed, but if it is called twice in the code, there is no output at all. After some debugging I think the problem is that the manager and handle_finished are not connected properly, because the execution never reaches there. Is there a simple way to solve this issue? Ideally, there is just one class instant, and any method can be called any number of times.
I don't know much about Qt, but it looks like you're trying to read from file twice and my guess is that when it reaches the end of the file after the first call to A.searchCSV it is done and you can't read from it anymore - unless you reposition the QFile to the beginning of the file.
Possible solution:
A.searchCSV(file, outfile);
file.unsetError(); // possibly needed
file.seek(0); // rewind to the start of the file
A.searchCSV(file, outfile);
The two QFile (input, output) are shared between two asynchronous calls (searchCSV) that might give an undefined behavior. The input file (stream) contents will be load and push only after the connection was made (like curl does).
You should:
Make searchCSV a blocking function (wait until handle_finished() done), input file pointer should be reinitialized before an other call.
OR: Use separated input/output QFile instances

How to run a post request in qt main()

I would like to do a simple post request int main.cpp. it would seem the when i run the application it would not execute the code and just skips it.
I tried using the qt debugger but the code below after the debugger start it just finishes right after.
I have tested my api with postman and knows it works
main.cpp
#include <iostream>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QNetworkInterface>
using namespace std;
int main()
{
QByteArray jsonString = "{\"ipaddr\": "+ QByteArray::number(9) + ",\"transactionType\":"+QByteArray::number(10) + ",\"idEmployee\":"+QByteArray::number(10) +"}";
QNetworkRequest request(QUrl("http://192.168.1.25:3000/classlog/pi"));
request.setRawHeader("Content-Type", "application/json");
QNetworkAccessManager * manager = new QNetworkAccessManager();
manager->post(request, jsonString);
}
.pro
TEMPLATE = app
CONFIG += console c++11
CONFIG -= app_bundle
QT += network core
SOURCES += \
main.cpp
I expect that i would be able to receive the request in my server, but i am not receiving any. Thank you
Qt uses an event system. Your the network manager will only schedule a request that will be handled in an event loop. This is also where the response is received.
You need a running event loop (and in fact, a QCoreApplication object, you sould get a warning when executing your code).
#include <QtNetwork>
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QNetworkAccessManager mgr;
QNetworkRequest req(QUrl("http://stackoverflow.com"));
auto *resp = mgr.get(req);
QObject::connect(resp, &QNetworkReply::finished, [&]() {
qDebug() << "FINISHED";
if (resp->error() != QNetworkReply::NoError)
qDebug() << "Error: " << resp->errorString();
else
qDebug() << "Status: " << resp->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
// Stop when a response is received
app.quit();
});
// This will start the event loop that will eventually send the request and receive the response.
// It will run until you call app.quit()
return app.exec();
}
you are almost there:
connect the signals:
QtObject::connect(your_manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onResult(QNetworkReply *)));
send something:
QNetworkRequest request(your_URL);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QByteArray byteArray;
byteArray.append(your_json);
your_manager->post(request, byteArray);
read the answer in the slot:
void FOO_CLASS::onResult(QNetworkReply* reply)
{
QString resp = QString::fromUtf8(reply->readAll());
}
edit
:
QObject::connect(your_manager, &QNetworkAccessManager::finished, [](QNetworkReply * r){
QString x{r->readAll()};
//foo1
auto l{x.length()};
//foo2
});

Memory access error when using QNetworkManager

I'm fairly new to C++ (though I have some experience with C) as well as QT. I'm trying to make a program that POSTs to a website when the user clicks a button, but whenever I try to access QNetworkManager I get a memory access error.
The code for my request object is as follows (trimmed slightly to show the important bits):
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include "cJSON.h"
class unifiedRequests: public QObject {
Q_OBJECT
public:
// Members
QString access_token = "";
bool admin = false;
// Methods
explicit unifiedRequests(QObject *parent=0);
QNetworkReply* login_request(QString *email, QString *password);
signals:
public slots:
void login_complete(QNetworkReply *reply);
void sslErrorHandler(QNetworkReply*, const QList<QSslError> & );
private:
QNetworkRequest make_headers(QByteArray endpoint);
QNetworkRequest make_headers(QByteArray endpoint, QByteArray *access_token);
};
QNetworkRequest unifiedRequests::make_headers(QByteArray endpoint) {
QString url = endpoint.prepend("https://dali.vpt.co.uk");
QNetworkRequest request = QNetworkRequest(url);
qDebug() << "Setting Headers";
request.setRawHeader("User-Agent", "Desktop Client Debug");
request.setRawHeader("Content-Type", "application/json");
qDebug() << "Set headers successfully.";
return request;
}
void unifiedRequests::sslErrorHandler
(QNetworkReply* reply, const QList<QSslError> & errors) {
qDebug() << "Ignoring SSL Errors";
};
QNetworkReply* unifiedRequests::login_request
(QString *email, QString *password) {
QNetworkRequest request = make_headers("/api/auth");
qDebug() << "Making JSON";
cJSON *login_json; //The body of the request
login_json = cJSON_CreateObject();
cJSON_AddStringToObject(login_json, "email", email->toUtf8());
cJSON_AddStringToObject(login_json, "password", password->toUtf8());
qDebug() << "Made JSON: ";
qDebug() << cJSON_Print(login_json);
QNetworkAccessManager *manager = new QNetworkAccessManager;
//The object we use to send the request and receive the reply
qDebug() << "Turning off SSL";
connect(manager,
SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )),
this,
SLOT(sslErrorHandler(QNetworkReply*, const QList<QSslError> & )));
qDebug() << "POSTing login.";
QNetworkReply *reply = manager->post(request, cJSON_Print(login_json));
qDebug() << "Connecting signal to slot.";
QAbstractSocket::connect(manager, SIGNAL(finished(QNetworkReply * )),
this, SLOT(login_complete(QNetworkReply * )));
cJSON_Delete(login_json);
return reply;
}
I'm creating the unifiedRequests object by calling:
unifiedRequests requestObj;
in a different file. It crashes out on the line where I try to turn off SSL (we're using a self-signed certificate, so I need to do this in order to make the request). Any thoughts?
Thank you!
You create the unifiedRequests object by calling "unifiedRequests requestObj;", this object will be deleted when the variable "requestObj" goes out of scope.
So, when the signal will be received, the object will be already destroyed.
Try to create your unifiedRequests object by calling "unifiedRequests* requestObj = new unifiedRequests();".
Of course, you need to call "delete requestObj;" somewhere to destroy this object. Where and when depend on your application (when you don't need this object anymore).
To understand the difference, look at here : http://www.tutorialspoint.com/cplusplus/cpp_dynamic_memory.htm
Or google for "C++ heap / stack / dynamic allocation"

Qt cpp cant send obj to connected function

I need to get a json file from an url, fill it in a QtNetworkReply *reply and send reply in a connected fonction to convert it in QbyteArray to pars my Json response.
But when i go in my connected function, i cant fill QByteArray with that reply (always empty)
Here's my code :
int main(int ac, char *av[])
{
Borne borne(ac, av);
reply myReply;
QNetworkAccessManager networkManager;
QUrl url("http://vps202498.ovh.net:8080/ws/rest/v.1/stores/categories/150/products");
QNetworkRequest request;
request.setUrl(url);
myReply._reply = networkManager.get(request);
QObject::connect(myReply._reply, SIGNAL(finished()), &myReply, SLOT(fonction()));
myReply._reply->finished();
exit(1);
if (borne.initialize() == false)
return (false);
return (borne._app->exec());
}
And here's my connected function :
IProduct *reply::fonction()
{
QByteArray List;
std::cout << "connected" << std::endl;
List = _reply->readAll();
if (List.isNull())
exit(6);
return (NULL);
}
My .H :
class reply : public QObject
{
Q_OBJECT
public:
reply() {};
~reply() {};
QNetworkReply *_reply;
public slots:
IProduct *fonction();
private :
};
I cant std::cout "connected", but always quit with error log '6'.
I dont really know where am i doing mistake (Iam used to C, not Cpp), i've read all the man of Qt about it, and cant figure what going wrong.
Any ideas?
Thank you and apologize for weak skill and english
You call the finish() function manually immediately after creation of the request. In that moment the request is not even started, so there is nothing to read from _reply->readAll(). The reply finished signal should be called by the even loop after calling application exec().
Remove lines:
myReply._reply->finished();
exit(1);
The request will be processed asyncronously in the event loop.
Other issues:
the slot reply::fonction() does not need any retrun value;
the event loop may be not started because of (borne.initialize() == false).

Signal-Slot makes a mess

I am trying to create a image-saving application using Qt. Now the stub
class ImageSaver:public QObject
{
int index;
QWebPage * main_Page;
QNetworkAccessManager * manager;
QNetworkReply * reply;
QString file_Name;
QSet<QString> image_Addresses;
QString web_Address;
Q_OBJECT
signals:
void image_Saved();
public slots:
void request_Image();
void on_Finished(bool status);
void got_Reply(QNetworkReply * reply);
public:
ImageSaver();
void start();
};
ImageSaver::ImageSaver()
{
index = 0;
manager = new QNetworkAccessManager;
reply = NULL;
connect(main_Page,SIGNAL(loadFinished(bool)),this,SLOT(on_Finished(bool)));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(got_Reply(QNetworkReply*)));
connect(this,SIGNAL(image_Saved()),this,SLOT(request_Image()));
}
void ImageSaver::start()
{
//loads the url
// In the end of the loading it will emit load_Finished(bool)
// So that signal will execute on_Finished(bool)
}
void ImageSaver::request_Image()
{
QString temp_Address = *(image_Addresses.begin()+index);
//makes a request to the server to give the image "temp_Address"
//When the server gives the reply signal finished(QNetworkReply*) will be emitted
// this in turn will call the got_Reply(QNetworkReply*)
}
void ImageSaver::on_Finished(bool status)
{
//collects all the images's url addresses, and pushes them in the list
//"image_Addresses"
//Then emits image_Saved();
//This signal will wake up the function request_Image()
}
void ImageSaver::got_Reply(QNetworkReply * reply)
{
//Image is extracted from the reply and got saved in the same name as in the page
//index got increased;
//emits the signal image_Saved();
//This signal will activate the function request_Image()
}
int main(int argc,char * argv[])
{
QApplication app(argc,argv);
ImageSaver a;
a.start();
return app.exec();
}
#include "main.moc"
In short First call is to "start".That calls "on_Finished" and there is no problem untill this. So all the image files's addresses got pushed in the list. Next is one by one request for image[i] made, and the reply image got saved. This thing is happening repeatedly. Here only I am getting problem. Crashes are appearing in this operation especially in saving the image.
My assumption is "signal-slot" is not like function call, thy are more or less like thread but operates on the same function( pointer). So when one signal requests for painter, which is already rendering something then the crash will appear.
Can anybody say the fact behind the crash and how to save all the images without crash?
EDIT:
Hi, This is the full code. Run this one, and click the message boxes contineously
#include <QApplication>
#include <QDir>
#include <QImage>
#include <QObject>
#include <QMessageBox>
#include <QPainter>
#include <QPixmap>
#include <QSet>
#include <QTimer>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QtWebKit/QWebElement>
#include <QtWebKit/QWebFrame>
#include <QtWebKit/QWebPage>
#include <QUrl>
class ImageSaver:public QObject
{
int index;
QWebPage * main_Page;
QNetworkAccessManager * manager;
QNetworkReply * reply;
QString file_Name;
QSet<QString> image_Addresses;
QString web_Address;
Q_OBJECT
signals:
void image_Saved();
public slots:
void request_Image();
void on_Finished(bool status);
void got_Reply(QNetworkReply * reply);
public:
ImageSaver();
void start();
protected:
//void show_Frame(QWebFrame * frame);
};
ImageSaver::ImageSaver()
{
index = 0;
this->main_Page = new QWebPage;
manager = new QNetworkAccessManager;
reply = NULL;
connect(main_Page,SIGNAL(loadFinished(bool)),this,SLOT(on_Finished(bool)));
connect(manager,SIGNAL(finished(QNetworkReply*)),this,SLOT(got_Reply(QNetworkReply*)));
connect(this,SIGNAL(image_Saved()),this,SLOT(request_Image()));
}
void ImageSaver::start()
{
web_Address = "yahoo.com";
QDir dir;
dir.mkdir(web_Address);
QUrl url = QUrl::fromUserInput(web_Address);
main_Page->mainFrame()->load(url);
}
void ImageSaver::request_Image()
{
QString temp_Address = *(image_Addresses.begin()+index);
int a = temp_Address.lastIndexOf("/");
file_Name = temp_Address.mid(a+1);
//Without the below message box, the program closes shortly
//This message box is slowing down that effect
QMessageBox hh;
hh.setText(file_Name);
hh.exec();
QNetworkRequest request= QNetworkRequest(QUrl(temp_Address));
request.setRawHeader("img","src");
manager->get(request);
}
void ImageSaver::on_Finished(bool status)
{
if(status)
{
QMessageBox mm;
mm.setText("Finished");
mm.exec();
QWebElementCollection temp_Collection= main_Page->mainFrame()->findAllElements("*");
for(int i=0;i<temp_Collection.count();++i)
{
QWebElement temp_Element = temp_Collection[i];
if(temp_Element.tagName().contains("img",Qt::CaseInsensitive) && temp_Element.attributeNames().contains("src",Qt::CaseInsensitive))
{
QString image_Web_Address = temp_Element.attribute("src");
if(!image_Addresses.contains(image_Web_Address))
image_Addresses.insert(image_Web_Address);
}
}
emit image_Saved();
QMessageBox kk;
kk.setText("Image is going to be saved");
kk.exec();
}
else
{
QMessageBox mm;
mm.setText("Not ready");
mm.exec();
}
QMessageBox mm;
mm.setText("Getting out of finished");
mm.exec();
}
void ImageSaver::got_Reply(QNetworkReply * reply)
{
QImage image;
if(image.load(static_cast<QIODevice *>(reply),0))
image.save(web_Address+QDir::separator()+file_Name,0);
++index;
emit image_Saved();
}
/*
void ImageSaver::show_Frame(QWebFrame * temp_Frame)
{
QImage image(temp_Frame->contentsSize(),QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setRenderHint(QPainter::TextAntialiasing,true);
painter.setRenderHint(QPainter::SmoothPixmapTransform,true);
temp_Frame->documentElement().render(&painter);
painter.end();
foreach(QWebFrame * temp_Frame0,temp_Frame->childFrames())
show_Frame(temp_Frame0);
}
*/
int main(int argc,char * argv[])
{
QApplication app(argc,argv);
ImageSaver a;
a.start();
return app.exec();
}
#include "main.moc"
This is the pro file
QT += webkit network
SOURCES += \
main.cpp
Your code crashes because you read beyond the boundaries of the image_Addresses set.
void ImageSaver::request_Image()
{
QString temp_Address = *(image_Addresses.begin()+index);
...
You increment index after every image received, but there isn't any check anywhere in the code whether index is still less than image_Addresses.size(), so it crashes once dereferencing image_Addresses.begin()+index for index == image_Addresses.size().
There may be many solutions to this problem. I think that you should have a look at The State Machine Framework. In easy situations you can just use boolean variable to check if you can go on. You should also think what to do when you're busy processing the image. You can queue request or just reject them. Also you can implement threading, so that new requests are served by new threads.
P.S. Signals are more like events than threads to me.
What's the error and why do you have a #include at the end?
FYI there is a QImage class you can use instead which includes saving and loading from a QIODevice* such as QNetworkReply for example. It's extremely rare to need to reinvent the wheel in the massive framework that is Qt.